We know that there are many ways to implement custom controls: 1: inherit View; 2: inherit ViewGroup; 3: inherit specific container controls (e.g. Linear Layout); 4: inherit a specific View (e.g. TextView).
Today, I'm going to show you the first scenario and list some issues that need to be addressed when dealing with custom controls that inherit Views. Next, we demonstrate a simple Demo, customize a MyCircleView, and show a circle. Later, we will put forward some problems that need to be paid attention to, and put forward solutions. The following code is pasted directly, as follows:
xml layout:
Customized MyCircleView:<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:textColor="#fff" android:background="#00f" android:text="The following drawing is my circle 1." /> <com.my.mycircleview.view.MyCircleView android:id="@+id/mcc" android:layout_width="match_content" android:layout_height="100dp" android:layout_below="@id/tv"/> </RelativeLayout>
Running the program, showing the results as shown in Figure 1:public class MyCircleView extends View { private Paint paint = new Paint(); public MyCircleView(Context context) { super(context); } public MyCircleView(Context context, AttributeSet attrs) { super(context, attrs); } public MyCircleView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //Get control circleView width and height int width = getWidth(); int height = getHeight(); //Center coordinates int cx = width / 2; int cy = height / 2; //Take the minimum of both as the diameter of a circle int minSize = Math.min(width, height); canvas.drawColor(Color.WHITE);//Setting the color of the canvas paint.setColor(Color.RED);//setpc paint.setStyle(Paint.Style.STROKE);//Set the circle to be hollow paint.setStrokeWidth(3.0f);//Set line width //cx,cy coordinate point is the top-left vertex coordinate relative to MyCircleView canvas.drawCircle(cx, cy, minSize / 2, paint); } }
Figure 1: Figure 2
Rewrite the onDraw method, draw circles with a brush in the canvas, and the code is annotated, so it should be clear.
Continue to try, change match_parent to wrap_content, add margin_top, add padding, and modify the layout file as follows:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:textColor="#fff" android:background="#00f" android:text="The following drawing is my circle 1." /> <com.my.mycircleview.view.MyCircleView android:id="@+id/mcc" android:layout_width="wrap_content" android:layout_height="100dp" android:layout_below="@id/tv" android:layout_marginTop="20dp" android:paddingTop="20dp"/> </RelativeLayout>
After running, the results are shown in Figure 2 above. We found that the outer margin of 20dp had an effect, padding had no effect, wrap_content had the same effect as match_parent, and the width occupied the entire screen width (the color of the brush was set to white, so the white area was the width range of MyCircleView). Next, we will propose solutions for wrap_content and padding.
Processing wrap_content first, why is wrap_content as effective as match_parent? Simply put: In the View class, when the layout width and height of the View are wrap_content or match_parent, the final size measured by the View is the measurement size in MeasureSpec - > SpecSize. Therefore, when customizing View, you need to rewrite onMeasure(w,h) to handle wrap_content, and then call setMeasured Dimession (w, h) to complete the measurement. If you need to understand the principle from the source point of view, you can first understand the onMeasure comprehensively, then you can better understand the conclusions here. See article Measurement onMeasure for Android Custom Controls
Next, we set specSize to 400 PX when the layout width is wrap_content and 200 PX when the layout height is match_parent. So rewrite the onMeasure(w,h) code as follows:
//The two parameters are the measurement recommendation value MeasureSpec given by the parent View, and the code is executed to onMeasure(w,h), indicating that MyCircleView's measure(w,h) is in execution. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);//Wide measurement size, mode int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);//High measurement size, mode int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int w = widthSpecSize; //Define the measurement width, height (excluding measurement mode), and set the default value. See View#getDefaultSize int h = heightSpecSize; //Several special cases dealing with wrap_content if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { w = 400; //The unit is px. h = 200; } else if (widthSpecMode == MeasureSpec.AT_MOST) { //As long as the width layout parameter is wrap_content, the width is given to a fixed value of 200 dp. w = 400; //Look at View#getDefaultSize to see how View is processed h = heightSpecSize; } else if (heightSpecMode == MeasureSpec.AT_MOST) { w = widthSpecSize; h = 200; } //Set values for two fields to complete the final measurement setMeasuredDimension(w, h); }
Explain the above code: When the layout width is wrap_content, its measurement mode specMode must be AT_MOST. Then take out the measurement mode of width and height to judge. If the layout width and height is not wrap_content, it will be processed according to the way of View$onMeasure(w,h), that is to say, measure the size specSize with the suggestion given by the parent view as the final measurement value. Rewrite onMeasure(w,h) and execute the program. The results are shown in Figure 3 below.
3) Figure 4
Figure 3 shows that when the layout width is wrap_content, the width of MyCircleView is 400 PX instead of the entire screen. In other cases, interested brothers can verify for themselves that the layout of wide and high wrap_content is what we want.
Note: The above processing may be problematic because widthSpecMode== MeasureSpec.AT_MOST is a necessary and insufficient condition for a layout parameter width of wrap_content. See the article for specific reasons.( Measurement onMeasure for Android Custom Controls ) The blogger's self-comment is a small correction for me. Judging the value of getLayoutParams().width as wrap_content, wrap_content is more rigorous. But when studying some books, the author uses the above method to judge. Xiao Wang boldly thinks it is not strict. (* *
Next, we deal with padding. To make padding work, we need to deal with padding when we rewrite onDraw(canvas) method. Here is mainly: modify the position of the center of the circle, modify the radius of the circle. After modification, the onDraw(canvas) code is as follows:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //Get control circleView width and height int width = getWidth(); int height = getHeight(); //Center coordinates int cx = width / 2; int cy = height / 2; //Take the minimum of both as the diameter of a circle int minSize = Math.min(width, height); canvas.drawColor(Color.WHITE);//Setting the color of the canvas paint.setColor(Color.RED);//setpc paint.setStyle(Paint.Style.STROKE);//Set the circle to be hollow paint.setStrokeWidth(3.0f);//Set line width //Start dealing with padding //Get the value of padding int paddingLeft = getPaddingLeft(); int paddingRight = getPaddingRight(); int paddingTop = getPaddingTop(); int paddingBottom = getPaddingBottom(); //Calculate the available width of MyCircleView minus padding int canUsedWidth = width - paddingLeft - paddingRight; int canUsedHeight = height - paddingTop - paddingBottom; //Center coordinates cx = canUsedWidth / 2 + paddingLeft; cy = canUsedHeight / 2 + paddingTop; //The diameter of a circle minSize = Math.min(canUsedWidth, canUsedHeight); //cx,cy coordinate point is the top-left vertex coordinate relative to MyCircleView canvas.drawCircle(cx, cy, minSize / 2, paint); }
After running the program, the effect is shown in Figure 4 above. On the circle, there is a MyCircleView padding Top, which is 20 DP in size. This is exactly what we need. The above code is basically annotated, here is an additional diagram showing the position calculation of the center of the circle after adding padding, as follows: