Android自定义任务奖励控件

废话不多说先上图

文章设计图1.png

文章设计图2.png

文章设计图3.png

看到这样的设计图你会怎么实现呢,三秒钟思考…

指导思想

你可以用popupWindow去做,拿到当前任务View的位置,调用popupWindow的showAtLocation()设置popupWindow显示在指定View的位置,然后让其点击不消失基本就可以实现了。但是如果这个控件位于可滑动的列表中,你还要计算popWindow在竖直方向的位置,由于滑动时不断的计算位置,popupWindow还会出现闪烁的情况(这也是我放弃popwindow的原因),当然如果在不可滑动的页面,完全可以用popwindow去实现。

我这里用的是自定义ViewGroup实现的,
指导思想就是拿到当前任务View的位置后,将任务文案View摆放好就行了。

talk is cheap,show me the code

测量

由于的控件上面是一个列表,下面是一个文案,所以我们继承了ViewGroup。不论是自定义View还是自定义ViewGroup都需要测量你要自定义的View的大小,即宽高,在自定义ViewGroup时还要多测一步就是子View的宽高,因为只有子View的宽高确定了,ViewGroup的宽高才能确定。测量的宽高的时候要注意,控件的宽高不光是控件本身的宽高,还包括你在xml文件里设置的margin和padding。


    @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);

    }

复制代码

布局

测量完之后,就到了摆放子View的环节了,也就是onLayout,摆放的时候其实和测量宽高步骤差不多,需要拿到控件的宽高以及边距,这里提一嘴child.onlayout(left,top,right,bottom),left、top、right、bottom指的是距离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);
        }
    }
复制代码

到这里基本完成了自定义任务奖励控件,我们在拿到任务文案需要展示在第几个任务编号的位置后,重新layout的就好。

    public void setPopPosition(int position) {
        mPopLeft = position;
        requestLayout();
    }
复制代码

这里再提一个注意点,只有重写 generateLayoutParams(AttributeSet attrs) 才可以获取 MarginLayoutParams,从而获取在xml里如果设置了margin或padding,否则会报错。

    @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() {
        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);
        }
    }
复制代码

最后在xml文件里,定义一下这个布局

           <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="加油,还需4次!"
                        android:textColor="@color/black"
                        android:textSize="@dimen/dp_12" />


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

复制代码

以上就是自定义任务组件的全流程,覆盖了自定义ViewGroup非常重要的点。自定义控件可以很简单也可很复杂,但万变不离其宗,用到的还是onMeasure()、onLayout()、onDraw()以及动画的展示和触摸反馈,多加练习自定义控件,会越来越得心应手。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享