Processing wrap_content, padding problem analysis of custom View

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:

<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>
Customized MyCircleView:

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);
	}
}
Running the program, showing the results as shown in Figure 1:

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:



At this point, I believe that you have a better understanding of the customized control to complete a more standardized inheritance View, Hey Hey (*^*)


Keywords: Android xml

Added by debuitls on Thu, 23 May 2019 00:17:23 +0300