美文网首页
学习自定义ViewGroup 实现tagView

学习自定义ViewGroup 实现tagView

作者: z淡 | 来源:发表于2018-12-14 15:53 被阅读6次

    看了上一篇,对view的绘制过程有了一定了解了。 下面动手做一个。

    学习自定义ViewGroup 实现tagView

    网上有很多flowlayout,可参考他们的实现。这里的目的是学习view的绘制过程。部分参考网上代码

    //简单的tagview实现就是,多个tag,计算每个tag的长宽,计算当前行能显示几个,不够长了就换下一行显示。

    顺序:measure()-onMeasure()-layout()-onLayout()-draw()-onDraw();
    这个顺序面试有见过考这个的。
    一般自定义都是重写onXxxx开头的,不带的不可以重写
    例:viewGoup:

        @Override
        public final void layout(int l, int t, int r, int b) {}
        @Override
        protected abstract void onLayout(boolean changed,
                int l, int t, int r, int b);
    

    重写onMeasure

    重要地方都有注释~
    需要for循环所有子类,计算子类的高度。-----此处默认高度都是一致的

     private int mTotalLength;
     private final int margin = 5;
     void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
            mTotalLength = 0;
            //此处计算的是当前view的大小
            int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
            int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
            int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
            int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
            int count = getChildCount();
            //计算每个子类的大小
            int maxCalcWidth = 0;//最大宽度,当前view的width为wrap_content时有效
            int singleWidth = 0;//当前行宽度
            int realMaxWidth = sizeWidth - getPaddingLeft() - getPaddingRight();//去除当前view的padding就是子类可使用的大小
            mTotalLength += getPaddingTop() + getPaddingBottom();
            for (int i = 0; i < count; i++) {
                InnerTv child = (InnerTv) getChildAt(i);//InnerTv继承textView啥都没做,可以不用重写子view的onMeasure。。
    
                if (child == null) {
                    continue;
                }
                measureChild(child, widthMeasureSpec, heightMeasureSpec);//计算子view的宽高
    
                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();
                if (i == 0) {
                    //首高
                    mTotalLength += childHeight;// 所有item高度一致,不换行
                }
    
                if (singleWidth + childWidth + margin > realMaxWidth) {//超出最大宽度
                    //下一行
                    maxCalcWidth = Math.max(maxCalcWidth, singleWidth);
                    singleWidth = childWidth;
                    mTotalLength += childHeight + margin;
    
                } else {
                    if (i != 0) {
                        singleWidth += margin;
                    }
                    singleWidth += childWidth;
                }
    
                //最后一行
                if (i == count - 1) {
                    maxCalcWidth = Math.max(maxCalcWidth, singleWidth);
                }
            }
            //设置最终高度
            setMeasuredDimension(
                    modeWidth == MeasureSpec.EXACTLY ? sizeWidth : maxCalcWidth,
                    modeHeight == MeasureSpec.EXACTLY ? sizeHeight : mTotalLength
            );
    
        }
    

    onLayout()

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int count = getChildCount();
            int realWidth = r - l - getPaddingLeft() - getPaddingRight();
            int left = getPaddingLeft();
            int top = getPaddingTop();
            int linew = 1;
            for (int i = 0; i < count; i++) {
                View view = getChildAt(i);
                int vw = view.getMeasuredWidth();
                int vh = view.getMeasuredHeight();
                int vr = left + vw;
                if (vr > realWidth) {//超过屏幕了,换行
                    linew++;
                    left = getPaddingLeft();
                    top += vh + margin;
                    vr = vw;
                }
                view.layout(left, top, vr, top + vh);
                left += vw + margin;//
            }
        }
    

    其他测试代码

        public void addAllTag() {
            for (int i = 0; i < contents.length; i++) {
                InnerTv tv = new InnerTv(getContext());
                LayoutParams lps = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    
                tv.setText(contents[i]);
                tv.setBackgroundColor(Color.GREEN);
                tv.setGravity(Gravity.CENTER);
                tv.setPadding(10, 5, 10, 5);
                addView(tv, lps);
            }
        }
    
    
        private String[] contents = new String[]{
                "我我我",
                "我我我们",
                "我我我哦",
                "急啊急啊就我",
                "我安安",
                "我烦烦烦1",
                "我烦烦烦2",
                "我烦烦烦3",
                "我烦烦烦4",
                "我烦烦烦55",
                "我烦烦烦11",
                "我烦烦烦22",
                "我烦烦烦33",
                "我烦烦烦44",
                "我烦烦烦655",
                "我烦烦烦666",
                "我烦烦烦677",
                "我烦烦烦88",
                "我烦烦烦99",
                "我烦烦烦00"
        };
    

    效果图如下

    image

    简单的tag就实现了,需要圆角的话,只需要给textview添加一个背景即可。

    其他扩展

    以上效果图,每行都有剩余,能不能让最后一行填满呢。
    实际就是让最后一个item,宽度变化

    尝试把宽度变长,
    修改InnerTv的onMeasure,super.onMeasure 后调用 setMeasuredDimension(reWidth,getMeasuredHeight());
    不过效果有点偏差, 那个textview是变宽了,但是文字没居中。。。
    TextView的onMeasure中应该已经计算好了文字位置所以这样修改文字没局中

    最终实现如下

        //上一个重新绘制
        int pre = i - 1;
        if (pre >= 0) {
             InnerTv prev = (InnerTv) getChildAt(pre);
             prev.reMeasure(realMaxWidth - singleWidth + prev.getMeasuredWidth());
             //重新计算
             measureChild(prev, widthMeasureSpec, heightMeasureSpec);
        }
                    
     
     
     InnerTv重写onMeasure
      @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int width = widthMeasureSpec;
            if (reWidth != 0) {
            //宽度固定是reWidth,所以模式也强制改成MeasureSpec.EXACTLY,得到新的width
                width = MeasureSpec.makeMeasureSpec(reWidth, MeasureSpec.EXACTLY);
            }
            super.onMeasure(width, heightMeasureSpec);
        }
    
        private int reWidth;
    
        public void reMeasure(int width) {
            this.reWidth = width;
        }
    

    效果图如下:

    image
    ==大功告成==~~~~

    说明:这代码只是学习,不够完善,margin都是直接写死的,已经LayoutParams也是不带margin,完善的需要再优化。
    完整代码如下:

    import android.content.Context;
    import android.graphics.Color;
    import android.util.AttributeSet;
    import android.view.Gravity;
    import android.view.View;
    import android.view.ViewGroup;
    
    public class VerticalLinearLayout extends ViewGroup {
        public VerticalLinearLayout(Context context) {
            super(context);
        }
    
        public VerticalLinearLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public VerticalLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        }
    
        private int mTotalLength;
        private final int margin = 5;
    
        void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
            mTotalLength = 0;
            //此处计算的是当前view的大小
            int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
            int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
            int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
            int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
            int count = getChildCount();
            //计算每个子类的大小
            int maxCalcWidth = 0;//最大宽度,当前view的width为wrap_content时有效
            int singleWidth = 0;//当前行宽度
            int realMaxWidth = sizeWidth - getPaddingLeft() - getPaddingRight();//去除当前view的padding就是子类可使用的大小
            mTotalLength += getPaddingTop() + getPaddingBottom();
            for (int i = 0; i < count; i++) {
                InnerTv child = (InnerTv) getChildAt(i);//InnerTv继承textView啥都没做,可以不用重写子view的onMeasure。。
    
                if (child == null) {
                    continue;
                }
                measureChild(child, widthMeasureSpec, heightMeasureSpec);//计算子view的宽高
    
                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();
                if (i == 0) {
                    //首高
                    mTotalLength += childHeight;// 所有item高度一致,不换行
                }
    
                if (singleWidth + childWidth + margin > realMaxWidth) {//超出最大宽度
                    //上一个重新绘制
                    int pre = i - 1;
                    if (pre >= 0) {
                        InnerTv prev = (InnerTv) getChildAt(pre);
                        prev.reMeasure(realMaxWidth - singleWidth + prev.getMeasuredWidth());
                        measureChild(prev, widthMeasureSpec, heightMeasureSpec);
                    }
                    //下一行
                    maxCalcWidth = Math.max(maxCalcWidth, singleWidth);
                    singleWidth = childWidth;
                    mTotalLength += childHeight + margin;
    
                } else {
                    if (i != 0) {
                        singleWidth += margin;
                    }
                    singleWidth += childWidth;
                }
    
                //最后一行
                if (i == count - 1) {
                    maxCalcWidth = Math.max(maxCalcWidth, singleWidth);
                }
            }
            //设置最终高度
            setMeasuredDimension(
                    modeWidth == MeasureSpec.EXACTLY ? sizeWidth : maxCalcWidth,
                    modeHeight == MeasureSpec.EXACTLY ? sizeHeight : mTotalLength
            );
    
        }
    
        /**
         * 尾部那个长度增加,满屏
         *
         * @param changed
         * @param l
         * @param t
         * @param r
         * @param b
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int count = getChildCount();
            int realWidth = r - l - getPaddingLeft() - getPaddingRight();
            int left = getPaddingLeft();
            int top = getPaddingTop();
            int linew = 1;
            for (int i = 0; i < count; i++) {
                View view = getChildAt(i);
                int vw = view.getMeasuredWidth();
                int vh = view.getMeasuredHeight();
                int vr = left + vw;
                if (vr > realWidth) {
                    linew++;
                    left = getPaddingLeft();
                    top += vh + margin;
                    vr = vw;
    
    
                }
                view.layout(left, top, vr, top + vh);
                left += vw + margin;//
            }
        }
    
        public void addAllTag() {
            for (int i = 0; i < contents.length; i++) {
                InnerTv tv = new InnerTv(getContext());
                LayoutParams lps = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    
                tv.setText(contents[i]);
                tv.setBackgroundColor(Color.GREEN);
                tv.setGravity(Gravity.CENTER);
                tv.setPadding(10, 5, 10, 5);
                addView(tv, lps);
            }
        }
    
    
        private String[] contents = new String[]{
                "我我我",
                "我我我们",
                "我我我哦",
                "急啊急啊就我",
                "我安安",
                "我烦烦烦1",
                "我烦烦烦2",
                "我烦烦烦3",
                "我烦烦烦4",
                "我烦烦烦55",
                "我烦烦烦11",
                "我烦烦烦22",
                "我烦烦烦33",
                "我烦烦烦44",
                "我烦烦烦655",
                "我烦烦烦666",
                "我烦烦烦677",
                "我烦烦烦88",
                "我烦烦烦99",
                "我烦烦烦00"
        };
    
    
    }
    
    

    InnerTv

    import android.content.Context;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.widget.TextView;
    
    public class InnerTv extends TextView {
        public InnerTv(Context context) {
            super(context);
        }
    
        public InnerTv(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public InnerTv(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int width = widthMeasureSpec;
            if (reWidth != 0) {
                width = MeasureSpec.makeMeasureSpec(reWidth, MeasureSpec.EXACTLY);
            }
            super.onMeasure(width, heightMeasureSpec);
        }
    
        private int reWidth;
    
        public void reMeasure(int width) {
            this.reWidth = width;
        }
    }
    

    其他

    VerticalLinearLayout mf = findViewById(R.id.myflowlayout);
    mf.addAllTag();
    

    上面所说到的完善一些, 还会用到另外一个方法
    generateDefaultLayoutParams()
    返回当前view(父容器) 添加子View时,给子view的默认LayoutParams,以上代码的获取到的getLayoutParams得到的是ViewGroup.LayoutParams,没有margin,所以需要改成MarginLayoutParams

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

    该方法的具体实现可以看看FrameLayout、LinearLayout等等常用viewgroup.

    //addview没有添加LayoutParams 就会默认生成一个
     public void addView(View child, int index) {
            if (child == null) {
                throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
            }
            LayoutParams params = child.getLayoutParams();
            if (params == null) {
                params = generateDefaultLayoutParams();
                if (params == null) {
                    throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
                }
            }
            addView(child, index, params);
        }
    

    以上就是大概的一个实现过程,动手学习~

    最后~~

    附上完整版
    TagFlowLayout
    https://github.com/zeng3234/TagFlowLayout

    当然github有很多比较成熟的,比如鸿洋的.. 看哪个顺手用哪个~~

    相关文章

      网友评论

          本文标题:学习自定义ViewGroup 实现tagView

          本文链接:https://www.haomeiwen.com/subject/fcughqtx.html