Customize View in three steps

Custom View three steps: onMeasure(), onLayout(), onDraw().

onMeasure()

First we need to figure out why custom views need to be remeasured. Normally, we define the width and height of the View directly in the XML layout file, and then let the custom View display in this width and height area. But to better accommodate different screen sizes, The Android system provides wrap_content and match_parent properties to regulate the display rules of controls. They represent the adaptive size and the size of the fill superview, respectively, but these two attributes do not specify a specific size, so we need to filter these two cases in the onMeasure method to really measure the width and height of the custom View.

    /** * measure *@paramWidthMeasureSpec contains measurement mode and width information *@paramHeightMeasureSpec contains measurement mode and height information * int data, in binary, accounting for 32 bits. The first two bits are the measurement mode. The last 30 bits are measured data (size). * The size measured here is not the final size of the View, but the reference size provided by the parent View. * /
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e("TAG"."onMeasure()");

    }
Copy the code

MeasureSpec:

  • During measurement, the system converts the View’s LayoutParams into a MeasureSpec according to the rules imposed by the parent container, and then measures the View’s width and height according to this MeasureSpec. Just measuring the width does not necessarily equal the actual width.
  • MeasureSpec represents a 32-bit int value (to avoid excessive object memory allocation), with the higher 2 bits representing SpecMode and the lower 30 bits representing SpecSize. The method of packing and unpacking is also provided.
SpecMode instructions
UNSPECIFIED The parent container has no restrictions on the current View, which can take any size, such as item in the ListView. This situation is generally used within the system to indicate a state of measurement.
EXACTLY The parent container has detected the exact size required by the View, which is the value specified by SpecSize. It corresponds to the Match_parent and concrete value modes in LayoutParams.
AT_MOST The parent container specifies SpecSize, and the View cannot be larger than this value. It corresponds to wrap_content in LayoutParams.

MeasureSpec = MeasureSpec = LayoutParams

  • When measuring, the system converts LayoutParams into a MeasureSpec under the constraints of the parent container, and then determines the width and height of the View according to MeasureSpec. (Note that two things determine MeasureSpec. LayoutParams and parent container constraints)
  • MeasureSpec transforms slightly differently for a top-level View(DecorView) than for a normal View. In addition to its own LayoutParams, the former is constrained by the size of the window and the latter by the parent container’s MeasureSpec. MeasureSpec specifies the width and height of the onMeasure View.

When inheriting a View or ViewGroup, if the onMeasure method is not copied, the parent class is used by default, that is, the implementation in View. The default implementation of onMeasure in View is as follows:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  // setMeasuredDimension is a very important method. The value passed in to setMeasuredDimension directly determines the View width and height. If setMeasuredDimension(100,200) is called, The final View displays a rectangle range of 100 by 200.
  // getDefaultSize returns the default size, which is the free space of the superview.
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code

Check out the setMeasuredDimension method. Super.onmeasure (widthMeasureSpec, heightMeasureSpec) for onMeasure methods of other existing controls; After a series of calculations, the setMeasuredDimension method is also called.

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    boolean optical = isLayoutModeOptical(this);
    if(optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets();int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;

        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
Copy the code

In one case, wrAP_content is specified in the XML, but the actual width and height used is the remaining free space of the parent view, as shown in the getDefaultSize method, which is the width and height of the entire screen. The solution is simply to duplicate the onMeasure, filter out wrAP_content, and actively call setMeasuredDimension to set the correct width and height:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        // Judgment is the measurement mode of WRAP_content
        if (MeasureSpec.AT_MOST == widthMode || MeasureSpec.AT_MOST == heightMode){
            int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
            int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
// int size = measuredWidth > measuredHeight ? measuredHeight : measuredWidth;
            // Set the width height to the minimum width height passed in
            int size = Math.min(measuredWidth, measuredHeight);
            // Set the View sizesetMeasuredDimension(size,size); }}Copy the code

The ViewGroup onMeasure

If the custom control is a container, the onMeasure method is more complex. Before a ViewGroup can measure its width and height, it needs to determine the size of its internal child views before it can determine its own size. For example, the width of the LinearLayout is wrAP_content, which is determined by the size of the child controls. The final width of the LinearLayout is determined by the maximum internal width of the child View.

onLayout()

    /** * layout *@paramChanged The size and position of the current View changed@paramLeft Left position (relative to the superview) *@paramTop Top position (relative to the superview) *@paramRight Right position (relative to the superview) *@paramBottom Bottom position (relative to the superview) */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e("TAG"."onLayout()");
        // This is usually used when customizing viewgroups to define the position of sub-views.

    }
Copy the code

Here are some more details about the position of a View:

  • View position parameters: determined by the View’s four attributes; Left, right, top, bottom. It’s kind of relative coordinates, relative to the parent container. Four parameters corresponding to the View source mLeft and other four member variables, through getLeft () and other methods to obtain.
  • Width =right-left; Height = bottom to top;
  • Starting with Android3.0, add four additional parameters: x, y, translationX, translationY. The first two are the coordinates of the top left corner of the View, and the last two are the offset of the top left corner of the View relative to the parent container, and default to 0. As with the four basic positional parameters, get/set methods are provided.
  • The conversion relationship is as follows; X = left + translationX; Y = top + translationY; Note; During a View translation, top and left represent the position of the original upper-left corner, and the value does not change. What has changed is; The four parameters are x, y, translationX, translationY.

It is an abstract method, which means that each custom ViewGroup must actively implement how to arrange its child views by traversing each child View and calling the child.(l, t, r, b) method to set the specific layout position for each child View. Four parameters respectively represent the coordinates of the upper left and lower right, a simple FlowLayout implementation is as follows:

FlowLayout is often used in the search interface of most apps to display historical search records or popular search terms. The number of items on each row of the FlowLayout may not be the same. If the total width of items on each row exceeds the total available width, restart the row to place items. Therefore, we need to actively calculate the final height of the FlowLayout in the onMeasure method, as shown below:

public class FlowLayout extends ViewGroup {

    // Store all views in the container
    private List<List<View>> mAllViews = new ArrayList<List<View>>();
    // Store the height of each View line
    private List<Integer> mPerLineMaxHeight = new ArrayList<>();

    public FlowLayout(Context context) {
        super(context);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        super.generateLayoutParams(p);
        return new MarginLayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams(a) {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    The onMeasure method has two main purposes: * 1. Call measureChild recursively to measure subView; * 2. Calculate the totalHeight of the final FlowLayout by adding the height of each line. * / 
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Get the measurement mode and measurement value of the width and height
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        // Get the number of neutron views in the container
        int childCount = getChildCount();
        // Record the total width of each View line
        int totalLineWidth = 0;
        // Record the height of the highest View in each row
        int perLineMaxHeight = 0;
        // Record the total height of the current ViewGroup
        int totalHeight = 0;
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            // Measure the subview
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            // Get the measurement width of the child View
            int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            // Get the measured height of the child View
            int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            if (totalLineWidth + childWidth > widthSize) {
                // Count the total height
                totalHeight += perLineMaxHeight;
                // Open a new line
                totalLineWidth = childWidth;
                perLineMaxHeight = childHeight;
            } else {
                // Record the total width of each line
                totalLineWidth += childWidth;
                // Compare the highest View in each row
                perLineMaxHeight = Math.max(perLineMaxHeight, childHeight);
            }
            // When the View is the last View, add the maximum height of the line to totalHeight
            if (i == childCount - 1) { totalHeight += perLineMaxHeight; }}// If the height is EXACTLY the measured value, otherwise use the calculated total height (wrap_content)
        heightSize = heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight;
        setMeasuredDimension(widthSize, heightSize);
    }

    // Place the controls
    //1. Indicates whether the size or position of the ViewGroup changes
    //2.3.4.5. Control position
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        mAllViews.clear();
        mPerLineMaxHeight.clear();

        // Store subviews for each row
        List<View> lineViews = new ArrayList<>();
        // Record the total width of views stored in each row
        int totalLineWidth = 0;

        // Record the height of the highest View in each row
        int lineMaxHeight = 0;

        / * * * * * * * * * * * * * iterate through all the View, the View is added to the List < List < View > > the collection * * * * * * * * * * * * * * * /
        // Get the total number of child Views
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            if (totalLineWidth + childWidth > getWidth()) {
                mAllViews.add(lineViews);
                mPerLineMaxHeight.add(lineMaxHeight);
                // Open a new line
                totalLineWidth = 0;
                lineMaxHeight = 0;
                lineViews = new ArrayList<>();
            }
            totalLineWidth += childWidth;
            lineViews.add(childView);
            lineMaxHeight = Math.max(lineMaxHeight, childHeight);
        }
        // Process the last line separately
        mAllViews.add(lineViews);
        mPerLineMaxHeight.add(lineMaxHeight);
        /************ iterates through all views in the collection and displays *****************/
        // Represents the distance between a View and the left of its parent container
        int mLeft = 0;
        // Represents the distance between the View and the top of the parent container
        int mTop = 0;

        for (int i = 0; i < mAllViews.size(); i++) {
            // Get all views for each row
            lineViews = mAllViews.get(i);
            lineMaxHeight = mPerLineMaxHeight.get(i);
            for (int j = 0; j < lineViews.size(); j++) {
                View childView = lineViews.get(j);
                MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
                int leftChild = mLeft + lp.leftMargin;
                int topChild = mTop + lp.topMargin;
                int rightChild = leftChild + childView.getMeasuredWidth();
                int bottomChild = topChild + childView.getMeasuredHeight();
                // Four parameters represent the upper left and lower right corner of the View
                childView.layout(leftChild, topChild, rightChild, bottomChild);
                mLeft += lp.leftMargin + childView.getMeasuredWidth() + lp.rightMargin;
            }
            mLeft = 0; mTop += lineMaxHeight; }}}Copy the code

Now that you have a custom layout defined, you can add child views of the desired style.

onDraw()

The onDraw method takes a Canvas parameter. Canvas can be understood as a Canvas on which various types of UI elements can be drawn.

    /** * draw *@paramCanvas canvas * /
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("TAG"."onDraw()");
    }
Copy the code

The system provides a series of Canvas operation methods as follows:

void drawRect(RectF rect,Paint paint): // Draw the rectangle area
void drawOval(RectF oval,Paint paint): // Draw the ellipse
void drawCircle(float cx,float cy,float radius,Paint paint): // Draw a circle
void drawArc(RectF oval,float startAngle,float sweepAngle,boolean useCenter,Paint paint): // Draw an arc
void drawPath(Path path,Paint paint): // Draw Path Path
void drawLine(float startX,float startY,float stopX,float stopY,Paint paint): // Draw a line
void drawPoint(float x,float y,Paint paint): / / draw point
Copy the code

Paint

Each drawing operation on the Canvas requires passing in a Paint object. Paint is like a paintbrush, because the Canvas itself is just a vehicle for rendering, and the actual effect of drawing depends on the Paint. You can set various properties of the brush to achieve different drawing effects.

setStyle(Style style): // Set the drawing mode
setColor(int color) : // Set the color
setAlpha(int a): // Set transparency
setShader(Shader shader): // Set the fill effect for Paint
setStrokeWidth(float width): // Set the line width
setTextSize(float textSize): // Set the text size
setAntiAlias(boolean aa): // Set the anti-aliasing switch
setDither(boolean dither): // Set the anti-jitter switch
Copy the code

For example, Canvas. drawCircle(centerX, centerY, R, paint); Draw a circle of radius R at coordinates centerX and centerY, but the exact shape of the circle is determined by paint.

Example: Draw a simple circular progress bar control.

public class PieImageView extends View {

    private static final int MAX_PROGRESS = 100;
    private Paint mArcPaint;
    private RectF mBound;
    private Paint mCirclePaint;
    private int mProgress = 0;

    public PieImageView(Context context) {
        this(context, null.0);
    }

    public PieImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PieImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void setProgress(@IntRange(from = 0, to = MAX_PROGRESS) int mProgress) {
        this.mProgress = mProgress;
        ViewCompat.postInvalidateOnAnimation(this);
    }

    private void init(a) {
        mArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mArcPaint.setStrokeWidth(dpToPixel(0.1 f, getContext()));
        mArcPaint.setColor(Color.RED);

        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setStrokeWidth(dpToPixel(2, getContext()));
        mCirclePaint.setColor(Color.argb(120.0xff.0xff.0xff));
        mBound = new RectF();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        // Judgment is the measurement mode of WRAP_content.
        If the PieImageView is set to wrAP_content (i.e. adaptive), the PieImageView will not display properly. It takes up screen space.
        if (MeasureSpec.AT_MOST == widthMode || MeasureSpec.AT_MOST == heightMode) {
            int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
            int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
            // Set the width height to the minimum width height passed in
            int size = measuredWidth > measuredHeight ? measuredHeight : measuredWidth;
            // Call setMeasuredDimension to set the actual View sizesetMeasuredDimension(size, size); }}@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int min = Math.min(w, h);
        int max = w + h - min;
        int r = Math.min(w, h) / 3;
        mBound.set((max >> 1) - r, (min >> 1) - r, (max >> 1) + r, (min >> 1) + r);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mProgress ! = MAX_PROGRESS && mProgress ! =0) {
            float mAngle = mProgress * 360f / MAX_PROGRESS;
            canvas.drawArc(mBound, 270, mAngle, true, mArcPaint);
            canvas.drawCircle(mBound.centerX(), mBound.centerY(), mBound.height() / 2, mCirclePaint); }}private float scale = 0;

    private int dpToPixel(float dp, Context context) {
        if (scale == 0) {
            scale = context.getResources().getDisplayMetrics().density;
        }
        return (int) (dp * scale); }}Copy the code
public class PieImageActivity extends AppCompatActivity {

    PieImageView pieImageView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pie_image);

// pieImageView = findViewById(R.id.pieImageView);
// pieImageView.setProgress(45);}}Copy the code

The sample

Effect:

Complete code:


      
<LinearLayout
    android:orientation="vertical"
    android:gravity="center"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray"
    tools:context="com.example.xwxwaa.myapplication.MainActivity">

    <! -- Record two questions -->
    <! 1. Here the parent is a LinearLayout-->
    <! If you're using a RelativeLayout, it doesn't look right. -->
    <! --2.MyCustomViewGroup width and height if wrap_content-->
    <! The width and height of the child View set to match_parent is invalid. -->

    <com.example.xwxwaa.myapplication.MyCustomViewGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimaryDark"
        android:layout_marginLeft="2dp"
        android:layout_marginTop="2dp"
        android:paddingRight="5dp"
        android:paddingBottom="5dp">

        <TextView
            android:layout_marginLeft="2dp"
            android:layout_marginTop="2dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Custom View"
            android:background="@color/colorAccent"/>

        <! App is a namespace, in order to use custom attributes -->
        <com.example.xwxwaa.myapplication.MyCustomView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="2dp"
            android:layout_marginTop="2dp"
            android:paddingRight="5dp"
            android:paddingBottom="5dp"
            app:default_size="100dp"
            app:default_color="@color/colorPrimaryDark"
            />
    </com.example.xwxwaa.myapplication.MyCustomViewGroup>

</LinearLayout>

Copy the code
public class MyCustomView extends View{

    private int defaultSize;
    private int defaultColor;
    private Paint paint ;

    /** * requires two construction arguments *@param mContext
     */
    public MyCustomView(Context mContext){
        super(mContext);
        init();
    }

    public MyCustomView(Context mContext, AttributeSet attributeSet){
        super(mContext,attributeSet);
        // It fetches the attribute values defined by the namespace in XML
        // The second argument is our 
      
        tag in the styles. XML file
      
        // The label of the collection of attributes, named r.styleable +name in the R file
        TypedArray a = mContext.obtainStyledAttributes(attributeSet, R.styleable.MyCustomView);

        R file name: r.styleable + attribute name + underscore + attribute name
        // The second argument is the default value to set if this property is not set
        defaultSize = a.getDimensionPixelSize(R.styleable.MyCustomView_default_size, 100);
        defaultColor = a.getColor(R.styleable.MyCustomView_default_color,Color.BLUE);

        // Finally reclaim the TypedArray object
        a.recycle();

        init();
    }
    private void init(a){
        // Initialize Paint
        paint = new Paint();
        paint.setColor(defaultColor);
        paint.setStyle(Paint.Style.STROKE);// Set the circle to hollow
        paint.setStrokeWidth(3.0 f);// Set the line width
    }
    /** * measure *@paramWidthMeasureSpec contains measurement mode and width information *@paramHeightMeasureSpec contains measurement mode and height information * Int data in 32 bits. The first two bits are the measurement mode. The last 30 bits are measured data (size). * The size measured here is not the final size of the View, but the reference size provided by the parent View. * /
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Define the width and height dimensions
        int width = getSize(widthMeasureSpec);
        int height = getSize(heightMeasureSpec);

        // Implement a square with a small value
        int sideLength =Math.min(width,height);

        // Set the View width and height
        setMeasuredDimension(sideLength,sideLength);
    }

    private int getSize(int measureSpec){
        int mySize = defaultSize;

        // You can use the following method to obtain the measurement mode and size.
        // Note that the unit of specSize is px, whereas in XML it is dp.
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode){
            case MeasureSpec.UNSPECIFIED:
                // The parent container does not have any restrictions on the View. This situation is generally used within the system to indicate a state of measurement.
                // We usually do not need to deal with. You can look at ScrollView or list related components.
                Log.e("TAG"."Measurement mode; MeasureSpec.UNSPECIFIED");
                break;
            case MeasureSpec.EXACTLY:
                // The parent container has detected the exact size required by the View, which is specified by SpecSize.
                // In XML, when width or height is specified as match_parent or a specific value, this is where it goes.
                mySize = specSize;
                Log.e("TAG"."Measurement mode; MeasureSpec.EXACTLY");
                break;
            case MeasureSpec.AT_MOST:
                // The size of the View cannot be larger than the SpecSize specified by the parent View.
                // This is where the width or height specified in XML is wrap_content.
                mySize = specSize/2;
                Log.e("TAG"."Measurement mode; MeasureSpec.AT_MOST");
                break;
            default:
                break;
        }
        return mySize;
    }
    /** * draw *@paramCanvas canvas * /
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Next draw a perfect circle.
        // We need to know the radius of the circle and the coordinates of the dot.
        int r = getMeasuredWidth() ;
        int centerX ;
        int centerY ;

        int paddingL = getPaddingLeft();
        int paddingT = getPaddingTop();
        int paddingR = getPaddingRight();
        int paddingB = getPaddingBottom();

        // Calculate the width of View minus the padding
        int canUsedWidth = r - paddingL - paddingR;
        int canUsedHeight = r - paddingT - paddingB;

        // Center coordinates
        centerX = canUsedWidth / 2 + paddingL;
        centerY = canUsedHeight / 2 + paddingT;
        // Take the minimum of both as the diameter of the circle
        int minSize = Math.min(canUsedWidth, canUsedHeight);
        // Draw a circle
        canvas.drawColor(Color.WHITE);// Set the canvas color
        canvas.drawCircle(centerX,centerY,minSize / 2,paint);
    }
    /** * layout *@paramChanged The size and position of the current View changed@paramLeft Left position (relative to the superview) *@paramTop Top position (relative to the superview) *@paramRight Right position (relative to the superview) *@paramBottom Bottom position (relative to the superview) */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        // This is usually used when customizing viewgroups to define the position of sub-views.}}Copy the code
public class MyCustomViewGroup extends ViewGroup{

    / / padding
    private int paddingL ;
    private int paddingT ;
    private int paddingR ;
    private int paddingB ;
    / / from the outside
    private int marginL;
    private int marginT;
    private int marginR;
    private int marginB;

    public MyCustomViewGroup (Context mContext){
        super(mContext);

    }

    public MyCustomViewGroup(Context mContext, AttributeSet attributeSet){
        super(mContext,attributeSet);

    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Width and height measurement mode and size
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        // Get the inner margin
        paddingL = getPaddingLeft();
        paddingT = getPaddingTop();
        paddingR = getPaddingRight();
        paddingB = getPaddingBottom();
        // Initialize the margin, because the measurement is more than once.
        marginL = 0;
        marginT = 0;
        marginR = 0;
        marginB = 0;

        // Measure the width and height of all child views. It triggers the onMeasure() of each child View.
        // measureChildren(widthMeasureSpec,heightMeasureSpec);

        MeasureChild is measured on a single View.
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            marginL = Math.max(0,lp.leftMargin);// Find the maximum left margin in this example
            marginT += lp.topMargin;// In this case, find the sum of all the top distances
            marginR = Math.max(0,lp.rightMargin);// Find the maximum right margin in this example
            marginB += lp.bottomMargin;// In this case, find the sum of all the lower distances
        }

        if (childCount == 0) {// No child View
            setMeasuredDimension(0.0);
        }else {
            // Maximum width, plus inside and outside margins
            int viewGroupWidth = paddingL + getChildMaxWidth() + paddingR +marginL+marginR;
            // Add the height, plus the inner and outer margins
            int viewGroupHeight = paddingT + getChildTotalHeight() + paddingB+marginT+marginB;
            / / choose a small value
            int  resultWidth = Math.min(viewGroupWidth, widthSize);
            int  resultHeight = Math.min(viewGroupHeight, heightSize);

            if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
                // If the parent layout width and height are wrap_content, this method is used only.
                // The width and height are wrapped contents, used to handle the wrap_content case of the ViewGroup
                setMeasuredDimension(resultWidth,resultHeight);
            }else if (widthMode == MeasureSpec.AT_MOST){
                // Width is the content of the package
                setMeasuredDimension(resultWidth,heightSize);
            }else if (heightMode == MeasureSpec.AT_MOST){
                // Height is the content of the package
                setMeasuredDimension(widthSize,resultHeight);
            }
            // If not, super.onMeasure() will call setMeasuredDimension(), which will use up all available space by default.}}/** * Get the maximum width of all child views *@return* /
    private int getChildMaxWidth(a){
        int count = getChildCount();
        int maxWidth = 0;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if(child.getMeasuredWidth() > maxWidth){ maxWidth = child.getMeasuredWidth(); }}return maxWidth;
    }

    /** * Get the sum of the heights of all child Views *@return* /
    private int getChildTotalHeight(a){
        int count = getChildCount();
        int totalHeight = 0;
        for (int i = 0; i < count; i++) {
            View view = getChildAt(i);
            totalHeight += view.getMeasuredHeight();
        }
        return totalHeight;
    }


    @Override
    protected void onLayout(boolean c, int l, int t, int r, int b) {
        int count = getChildCount();
        int coordHeight = paddingT;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
            int width = child.getMeasuredWidth();
            int height = child.getMeasuredHeight();
            intcoordWidth = paddingL+ lp.leftMargin; coordHeight += lp.topMargin; child.layout(coordWidth,coordHeight,coordWidth+width,coordHeight+height); coordHeight+=height+lp.bottomMargin; }}@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return newMarginLayoutParams(getContext(),attrs); }}Copy the code

      
<resources>
    <! -- Custom attributes -->
    <declare-styleable name="MyCustomView">
        <! -- Dimension is a dimension containing units (dp, sp, px, etc.) that can be used to define a view's width, size, etc. -->
        <attr name="default_size" format="dimension" />
        <attr name="default_color" format="color" />
    </declare-styleable>
</resources>
Copy the code

To optimize the

For example, repeat drawing, and optimization of large and long images.

Loading long picture and large picture optimization:

  • The compressed image
  • Scale diagonally
  • Load the visible area of the screen
  • Reuse memory from the previous bitmap region
  • Deal with sliding

For views of overlay areas, it is important to avoid redrawing. For example, competitive chess and card apps. When fighting the Lord, many cards are covered, so it is not possible to draw every picture, be sure to calculate the display area first, cut off the unnecessary, and then draw.


note

References:

Android development art exploration

Hook education -Android engineer advanced 34 talks

Portal: GitHub

Welcome to follow wechat official account:No reason also