美文网首页
自定义ViewGroup都要做些啥

自定义ViewGroup都要做些啥

作者: 有点健忘 | 来源:发表于2018-02-24 15:25 被阅读23次

    一个是斜体

    两个是加粗

    三个是斜体加粗

    以前都是继承一个线性布局,相对布局,帧布局做点简单修改,从来没有继承过viewGroup来写一个,今天顺道写个测试下,结果发现各种坑啊,我以为就实现onLayout这个抽象方法就完事了,结果。。。

    下边代码就是简单实现了下线性布局类似的顺序显示,其他pading,margin都不考虑。就先看下功能

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            System.err.println(tag+"========onLayout======"+changed+"============="+l+"="+r+"=="+t+"==="+b);
            int top=0;
            for(int i=0;i<getChildCount();i++) {
                View child=getChildAt(i);
                if(child!=null) {
                    child.layout(l, top, r,top+child.getMeasuredHeight());
                    top+=child.getMeasuredHeight();
                }
            }
        }
    

    代码如上,结果测试发现里边的child都看不见,打印日志可以发现child.getMeasuredHeight()这个返回的一直是0,自然看不见了。完事我就把child.getMeasuredHeight()改成一个固定的比如200的值,发现是可以看到的,那原因就是child没有进行测量。

    那就去看下FrameLayout咋弄的。代码比较多,咱就抽出关键的代码即可,也就是调用了measureChildWithMargins这个方法

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            System.err.println(tag+"===========onMeasure===========h="+getMeasuredHeight()+"===w="+getMeasuredWidth());
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            for(int i=0;i<getChildCount();i++) {
                View child=getChildAt(i);
                if(child!=null) {
                     measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                    //android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
                }
            }
        }
    

    这下以为好了,运行下直接挂了。异常
    android.view.ViewGroup.LayoutParams cannot be cast to android.view.ViewGroup.MarginLayoutParams

    原因就是如下的代码,可以看到parama进行了强转

        protected void measureChildWithMargins(View child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                            + heightUsed, lp.height);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    

    继续看ViewGroup里有如下的几个方法,里边的LayoutParams就是ViewGroup里的一个静态类public static class LayoutParams

        /**
         * Returns a new set of layout parameters based on the supplied attributes set.
         *
         * @param attrs the attributes to build the layout parameters from
         *
         * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
         *         of its descendants
         */
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LayoutParams(getContext(), attrs);
        }
    
        /**
         * Returns a safe set of layout parameters based on the supplied layout params.
         * When a ViewGroup is passed a View whose layout params do not pass the test of
         * {@link #checkLayoutParams(android.view.ViewGroup.LayoutParams)}, this method
         * is invoked. This method should return a new set of layout params suitable for
         * this ViewGroup, possibly by copying the appropriate attributes from the
         * specified set of layout params.
         *
         * @param p The layout parameters to convert into a suitable set of layout parameters
         *          for this ViewGroup.
         *
         * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
         *         of its descendants
         */
        protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
            return p;
        }
    
        /**
         * Returns a set of default layout parameters. These parameters are requested
         * when the View passed to {@link #addView(View)} has no layout parameters
         * already set. If null is returned, an exception is thrown from addView.
         *
         * @return a set of default layout parameters or null
         */
        protected LayoutParams generateDefaultLayoutParams() {
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        }
    

    那咋办了,修改下这几个方法呗,让默认的child的params就是我们需要的MarginLayoutParams即可

        @Override
        protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
            // TODO Auto-generated method stub
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        }
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LayoutParams(getContext(), attrs);
        }
        @Override
        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return p instanceof LayoutParams;
        }
    
        @Override
        protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
            return new LayoutParams(p);
        }
    
        @Override
        public CharSequence getAccessibilityClassName() {
            return TouchEventViewGroup.class.getName();
        }
        
        public static class LayoutParams extends MarginLayoutParams {
        //省略掉构造方法了
    }
    

    最后说明下如下的关系,marginLayoutParams就是多了写margin参数而已
    public static class MarginLayoutParams extends ViewGroup.LayoutParams

    继续研究,难道一定要用marginLayoutParams,当然不一定了,如果你的child不需要设置margin那就不用了。
    如下修改,剔除掉margin相关的,自然也就不要MarginLayoutParams
    其实,核心代码就是child.measure(childWidthMeasureSpec, childHeightMeasureSpec);来测量child的大小

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            System.err.println(tag+"===========onMeasure===========h="+getMeasuredHeight()+"===w="+getMeasuredWidth());
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            for(int i=0;i<getChildCount();i++) {
                View child=getChildAt(i);
                if(child!=null) {
    //               measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                    //android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
                    ViewGroup.LayoutParams lp=child.getLayoutParams();
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeft() + getPaddingRight(), lp.width);
                    final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTop()+getPaddingBottom(), lp.height);
    
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
            }
        }
    

    如果没有特殊的参数,那么直接用MarginLayoutParams即可,如果有自己需要额外添加的参数,那么可以继承这个,自己写一个
    看下几个常用的
    LinearLayout,多了比重和重心

     public static class LayoutParams extends ViewGroup.MarginLayoutParams
    
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
                TypedArray a =
                        c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
    
                weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
                gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
    
                a.recycle();
            }
    

    RelativeLayout
    多了一对,相对位置的参数,都放在下边的数组里

    private int[] mRules = new int[VERB_COUNT];
    

    FrameLayout
    多了个child的layout_gravity

    public int gravity = UNSPECIFIED_GRAVITY;
    

    总结下其实自定义ViewGroup的核心代码就2行

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //对childView进行大小测量

    child.layout(int l, int t, int r, int b) 对child的位置进行处理,上下左右4个点已经决定了这个child的位置拉。

    简单不,嗯,简单,再复杂不会了。

    反正知道了基础就这两行,以后看别人写的自定义ViewGroup就好理解了。

    相关文章

      网友评论

          本文标题:自定义ViewGroup都要做些啥

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