美文网首页
自定义View 自定义布局

自定义View 自定义布局

作者: 花椒人生 | 来源:发表于2018-12-23 23:42 被阅读0次

    自定义View布局

    1.确定每个View的位置和尺寸
    2.作用:为绘制和触摸范围做支持
        1.对于绘制:知道自己需要在哪里绘制。
        2.对于触摸反馈:知道用户的点是在哪里。
    

    自定义View布局的工作内容

    自定义View的工作分为两个阶段:测量阶段和布局阶段
    
    测量流程:从上到下递归调用每个View或者ViewGroup的measure()方法,测量他们的尺寸并计算他们的位置。
    布局阶段:从上到下递归地调用每个View或者ViewGroup的layout()方法,把测量得到的他们的尺寸和位置赋值给他们。
    

    View或者ViewGroup的布局过程

    1.测量阶段:measure()方法被父View调用,在measure()中做一些准备和优化工作后,调用onMeasure()来进行实际的自我测量。onMeasure()做的事情,在View和ViewGroup中是不一样的。
    
        1.View:View在onMeasure()中会计算出自己的尺寸然后保存;
        2.ViewGroup:ViewGroup在onMeasure()中会调用所有子View的measure()让他们进行自我测量,并根据子View计算出的期望尺寸来计算出他们的实际尺寸(实际上99.99%的父View都会使用子View给出的期望尺寸来作为实际尺寸),然后保存。
        
    2.布局阶段:layout()方法被父View调用,在layout()中他会保存父View传进来的自己的位置和尺寸,并且调用onLayout()来进行实际的内部布局。onLayout()做的事,View和ViewGroup也不一样:
    
        1.View:由于没有子View,所以View的onLayout()什么也不需要做。
        2.ViewGroup:ViewGroup在onLayout()中会调用自己的所有的子View的layout()方法,把它们的尺寸和位置传递给他们,并且让他们自己完成自己内部的布局。
    

    布局过程自定义的方式

    布局过程自定义的方式有三类
    1.重写onMeasure()来修改已有的View的尺寸。
    2.重写onMeasure()来全新定制自定义View的尺寸。
    3.重写onMeasure()和onLayout()来全新定制自定义ViewGroup的内部布局。
    
    具体做法

    继承已有的 View,简单改写它们的尺⼨寸:重写 onMeasure():SquareImageView
    1. 重写 onMeasure()
    2. ⽤用 getMeasuredWidth() 和 getMeasuredSize() 获取到测量量出的尺⼨寸 3. 计算出最终要的尺⼨寸
    4. ⽤用 setMeasuredDimension(width, height) 把结果保存

            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                int width = getMeasuredWidth();
                int height = getMeasuredHeight();
                int size = Math.min(width, height);
                setMeasuredDimension(size, size);
            }
    

    对⾃自定义 View 完全进⾏行行⾃自定义尺⼨寸计算:重写 onMeasure():CircleView

    1. 重写 onMeasure()

    2. 计算出⾃自⼰己的尺⼨寸

    3. ⽤用 resolveSize() 或者 resolveSizeAndState() 修正结果
      3.1resolveSize() / resolveSizeAndState() 内部实现(⼀一定读⼀一下代码,这个极少需要⾃自 ⼰己写,但⾯面试时很多时候会考):
      3.1.1⾸首先⽤用 MeasureSpec.getMode(measureSpec) 和 MeasureSpec.getSize(measureSpec) 取出⽗父 对⾃自⼰己的尺⼨寸限制类型和具体限制 尺⼨寸;
      3.1.2如果 measure spec 的 mode 是 EXACTLY,表示⽗父 View 对⼦子 View 的尺⼨寸做出 了了精确限制,所以就放弃计算出的 size,直接选⽤用 measure spec 的 size;
      3.1.3如果 measure spec 的 mode 是 AT_MOST,表示⽗父 View 对⼦子 View 的尺⼨寸只限 制了了上限,需要看情况:
      3.1.4.1如果计算出的 size 不不⼤大于 spec 中限制的 size,表示尺⼨寸没有超出限制, 所以选⽤用计算出的 size;
      3.1.4.2而如果计算出的 size ⼤大于 spec 中限制的 size,表示尺⼨寸超限了了,所以选⽤用 spec 的 size,并且在 resolveSizeAndState() 中会添加标志 MEASURED_STATE_TOO_SMALL(这个标志可以辅助⽗父 View 做测量量和布 局的计算;
      3.1.5如果 measure spec 的 mode 是 UNSPECIFIED,表示⽗父 View 对⼦子 View 没有任 何尺⼨寸限制,所以直接选⽤用计算出的 size,忽略略 spec 中的 size。

    4. 使⽤用 setMeasuredDimension(width, height) 保存结果

           protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
               int width = (int) ((PADDING + RADIUS) * 2);
               int height = (int) ((PADDING + RADIUS) * 2);
               setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec,
           0),
           }
      

    ⾃自定义 Layout:重写 onMeasure() 和 onLayout():TagLayout

        1. 重写 onMeasure()
            1. 遍历每个⼦子 View,⽤用 measureChildWidthMargins() 测量量⼦子 View
                • 需要重写 generateLayoutParams() 并返回 MarginLayoutParams 才能使⽤用 measureChildWithMargins() ⽅方法
                • 有些⼦子 View 可能需要重新测量量(⽐比如换⾏行行处)
                • 测量量完成后,得出⼦子 View 的实际位置和尺⼨寸,并暂时保存
            
             
                    protected void onMeasure(int widthMeasureSpec, int
                    heightMeasureSpec) {
                        for (int i = 0; i < getChildCount(); i++) {
                            View child = getChildAt(i);
                            Rect childBounds = childrenBounds[i];
                            // 测量量⼦子 View
                            measureChildWithMargins(child, widthMeasureSpec,
                    widthUsed,
                            heightMeasureSpec, heightUsed); // 保存⼦子 View 的位置和尺⼨寸
                            childBounds.set(childlLeft, childTop, childLeft
                                + child.getMeasuredWidth(), chiltTop
                                + child.getMeasuredHeight());
                            ...... 
                        }
                        // 计算⾃自⼰己的尺⼨寸,并保存
                        int width = ...;
                        int height = ...; setMeasuredDimension(resolveSizeAndState(width,
                    widthMeasureSpec, 0),
                                resolveSizeAndState(height,  heightMeasureSpec,
                    0)); }
                    
                • measureChildWidthMargins() 的内部实现(⼀一定读⼀一下代码,这个极少需要⾃自 ⼰己写,但⾯面试时很多时候会考):
                    通过 getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方 法计算出⼦子 View 的 widthMeasureSpec 和 heightMeasureSpec,然后调 ⽤用 child.measure() ⽅方法来让⼦子 View ⾃自我测量量;
                
                            // ViewGroup.measureChildWithMargins() 源码 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
                                    int parentHeightMeasureSpec, int heightUsed) {
                                final MarginLayoutParams lp =
                                        (MarginLayoutParams) child.getLayoutParams();
                                final int childWidthMeasureSpec =
                             
                            getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方法的内部 实现是,结合开发者设置的 LayoutParams 中的 width 和 height 与⽗父 View ⾃自 ⼰己的剩余可⽤用空间,综合得出⼦子 View 的尺⼨寸限制,并使⽤用 MeasureSpec.makeMeasureSpec(size, mode) 来求得结果:
                            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);
                            }
                            
                        getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方法的内部 实现是,结合开发者设置的 LayoutParams 中的 width 和 height 与⽗父 View ⾃自 ⼰己的剩余可⽤用空间,综合得出⼦子 View 的尺⼨寸限制,并使⽤用 MeasureSpec.makeMeasureSpec(size, mode) 来求得结果:
    

    相关文章

      网友评论

          本文标题:自定义View 自定义布局

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