Without further ado, picture above

What would you do with a design like this? Three seconds to think…

The guiding ideology

You can do this with popupWindow, get the location of the current task View, call showAtLocation() of popupWindow and set the popupWindow to be displayed at the specified View location, and then keep it clickable. However, if the control is in a slideable list, you need to calculate the vertical position of the popWindow. PopupWindow will also blink due to the constant calculation of position while sliding (which is why I abandoned popWindow). Of course, if you are on a non-slideable page, You can do this with popWindow.

Here I use a custom ViewGroup implementation, the guiding idea is to get the position of the current task View, put the task copy View on the line.

talk is cheap,show me the code

measurement

We inherit the ViewGroup because the control is a list above and a copywriting below. Whether it is a custom View or a custom ViewGroup, you need to measure the size of the View you want to customize, that is, the width and height, in the custom ViewGroup also need to measure one more step is the width and height of the child View, because only the width and height of the child View is determined, the width and height of the ViewGroup can be determined. When measuring the width of a control, note that the width of a control is not only the width of the control itself, but also the margin and padding that you set in your XML file.


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int totalWidth = 0;
        int totalHeight = 0;


        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            MarginLayoutParams mParams = (MarginLayoutParams) child.getLayoutParams();

            if (i == 0) {
                totalHeight += getPaddingTop();
                totalWidth = getPaddingStart() + getPaddingEnd() + mParams.leftMargin +
                        mParams.rightMargin + child.getMeasuredWidth();
            } else if (i == childCount - 1) {
                totalHeight += getPaddingBottom();
            }

            int cHeight = child.getMeasuredHeight();
            totalHeight += cHeight + mParams.topMargin + mParams.bottomMargin;
        }

        int measureWidth = widthMode == MeasureSpec.EXACTLY ? widthSize : totalWidth;
        int measureHeight = heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight;
        setMeasuredDimension(measureWidth, measureHeight);

    }

Copy the code

layout

After measuring, it is time to place the sub-view, that is, onLayout. When placing, it is almost the same as measuring the width and height. You need to get the width and height of the control and the margin. Child. Onlayout (left,top,right,bottom),left,top,right,bottom refers to the distance from the top, bottom,left,right, and left boundaries of the ViewGroup.

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int childLeft = 0;
        int childRight = 0;
        int childTop = 0;
        int childBottom = 0;
        int lastTotalHeight = 0;
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            MarginLayoutParams cParams = (MarginLayoutParams) child.getLayoutParams();
            if(i ! =0) {
                childLeft = mPopLeft;
            } else {
                childLeft = cParams.leftMargin + getPaddingLeft();
            }
            childRight = childLeft + childWidth + getPaddingRight();

            if (i == 0) {
                childTop = cParams.topMargin + getPaddingTop();
            } else {
                childTop = lastTotalHeight + cParams.topMargin;
            }

            if (i == getChildCount() - 1) {
                childBottom = childTop + childHeight + getPaddingBottom();
            } else{ childBottom = childTop + childHeight; } lastTotalHeight = childBottom; child.layout(childLeft, childTop, childRight, childBottom); }}Copy the code

By now, we have basically completed the custom task reward control. After we get the task copy which needs to be displayed in the position of the task number, we just need to re-layout.

    public void setPopPosition(int position) {
        mPopLeft = position;
        requestLayout();
    }
Copy the code

The only way to get MarginLayoutParams is to override generateLayoutParams(AttributeSet Attrs) to get margin or padding in the XML.

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

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams lp) {
        return new MyLayoutParams(lp);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams(a) {
        return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    public static class MyLayoutParams extends MarginLayoutParams {

        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public MyLayoutParams(int width, int height) {
            super(width, height);
        }

        public MyLayoutParams(LayoutParams lp) {
            super(lp); }}Copy the code

Finally, define the layout in the XML file

           <com.ziroom.housekeeperazeroth.mall.MallTaskView
                    android:id="@+id/mtv"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/dp_14">

                    <androidx.recyclerview.widget.RecyclerView
                       android:id="@+id/rv_task"
                       android:layout_width="match_parrent"
                       android:layout_height="wrap_content"/>

                
                    <TextView
                        android:id="@+id/tv_pop_content"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="@dimen/dp_5"
                        android:background="@drawable/bg_pop_mid"
                        android:gravity="bottom|center_horizontal"
                        android:paddingBottom="@dimen/dp_6"
                        android:text="Come on, four more!"
                        android:textColor="@color/black"
                        android:textSize="@dimen/dp_12" />


           </com.ziroom.housekeeperazeroth.mall.MallTaskView>

Copy the code

This is the whole process of customizing task components, covering the very important points of customizing viewGroups. Custom controls can be very simple or very complex, but all changes remain the same, using onMeasure(), onLayout(), onDraw() and animation display and touch feedback, more practice custom controls, will be more and more handy.