自定义View 自定义布局

自定义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 自定义布局)