对自定义View的Measure和onMeasure的一点心得

写的好的文章:

Android 自定义 view(四)—— onMeasure 方法理解 - 易术军 - 博客园

ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解_大苞米的博客-CSDN博客

Android开发之自定义控件(一)---onMeasure详解_奋斗之路的博客-CSDN博客

建议大家按顺序看完然后再把第一个小Demo自己敲一遍,最好先自己思考再对照这样理解的深刻,当然也要对照View和ViewGrope的安卓源码

public class CustomView extends View {
    public CustomView(Context context) {
        this(context,null);
    }

    public CustomView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();

    }

    Paint paint;
    String str;
    private Rect mbound;
    private void init() {
        str = "水电费水电费";
        paint = new Paint();
        paint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mbound = new Rect();
        paint.setTextSize(100);
        paint.setColor(Color.parseColor("#0000ff"));

//        paint.getTextBounds(str,0,str.length(),mbound);

    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final  int minimumWidth = getSuggestedMinimumWidth();
        final  int minimumHeight = getSuggestedMinimumHeight();
        Log.e("YView", "---minimumWidth = " + minimumWidth + "");
        Log.e("YView", "---minimumHeight = " + minimumHeight + "");

        int width = measureWidth(minimumWidth,widthMeasureSpec);
        int height = measureHeight(minimumHeight,heightMeasureSpec);

        setMeasuredDimension(width,height);

    }

    private int measureHeight(int minimumHeight, int heightMeasureSpec) {

        int result = minimumHeight;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.e("YViewHeight", "---speSize = " + specSize + "");

        switch (specMode){

            case MeasureSpec.AT_MOST:

                result = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top + getPaddingTop() + getPaddingBottom());

                break;

            case MeasureSpec.EXACTLY:

                result = specSize;

                break;


            case MeasureSpec.UNSPECIFIED:

                result = Math.max(minimumHeight,specSize);

                break;

        }


       return result;
    }

    private int measureWidth(int minimumWidth, int widthMeasureSpec) {

        int result= minimumWidth;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        Log.e("YViewWidth", "---speSize = " + specSize + "");

        switch (specMode){

            case MeasureSpec.AT_MOST:
                result = (int) (paint.measureText(str) + getPaddingLeft() + getPaddingRight());

                Log.e("YViewWidth", "---speMode = AT_MOST");
                break;

            case MeasureSpec.EXACTLY:
                result = specSize;
                Log.e("YViewWidth", "---speMode = EXACTLY");
                break;

            case MeasureSpec.UNSPECIFIED:
                Log.e("YViewWidth", "---speMode = UNSPECIFIED");
                result = Math.max(minimumWidth,specSize);
                break;


        }

        return result;
    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawColor(Color.GRAY);
//        canvas.drawText(str,getWidth()/2-paint.measureText(str) / 2,getHeight()/2+(paint.descent()-paint.ascent())/2,paint);

        canvas.drawText(str,getWidth()/2-paint.measureText(str) / 2,-paint.getFontMetrics().top,paint);
//        Log.e("descent()-ascent()",(paint.getFontMetrics().bottom - paint.getFontMetrics().top)+"");

    }


}

image

在学习Measure和onMeasure之前的困惑:

1.如何测量或者测量的原理?:

a.ViewGrope通过调用子View的Measure方法来告诉子View它的模式和它最大能获得的大小

好多大神的博客都讲到了测量是从最外层的ViewGrope开始调用子类的Measure方法,并通过(一些逻辑(例如ViewGrope源码中的measureChild、getChildMeasureSpec,当然也可以自己写,但是没有特殊需求没必要重复造轮子))获得子View的specMode和SpecSize然后组成MeasureSpec,把MeasureSpec通过子View.Measure()传下去告诉子View,它的模式和它最大能获得的大小。

b.ViewGrop和View都是通过View的onMeasure方法测量自己,最终起效果的是setMeasuredDimension

起初我不理解,源码有测量方法为什么还要重写,或者说如何重写(没有一点头绪),究其原因是对MeasureSpec和自定义的不理解

   对MeasureSpec的理解:开始知道有Measurespec但是不知道其意义:告诉子View模式和可用的大小,MeasureSpec是用来维护测量模式和测量大小的,MeasureSpec把测量模式和大小维护成了一个32位的二进制数,最高位的前两位代表测量模式,后30位代表大小。

   对自定义的理解:自定义就是在不同模式下例如AT_Most(wrapcontent)、EXACTLY(固定值或MatchParent),我们想要什么效果,因为源码中对它们的处理都是无论什么模式把父布局占满。

2.测量的意义:

主要是给onLayout(只在ViewGrope中有效,因为onLayout的目的是确定子View在父View中的位置,那么这个步骤肯定是由父View来决定的)和onDraw使用,最后这个控件大小和位置由onLayout决定想想LinearLayout他也是走onlayout的逻辑去布局的,最后这个控件长什么样子是靠onDraw方法决定的。

Android自定义View(一)-Measure原理篇_柚子君.的博客-CSDN博客_android view.measure

计算:子View的具体大小由父View的MeasureSpec值和子View的LayoutParams属性共同决定,即:

具体的计算封装在getChildMeasureSpec里,源码如下:

/**
  * 源码分析:getChildMeasureSpec()
  * 作用:根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
  * 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams属性 共同决定
  **/
 
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
 
         //参数说明
         * @param spec 父view的详细测量值(MeasureSpec) 
         * @param padding view当前尺寸的的内边距和外边距(padding,margin) 
         * @param childDimension 子视图的布局参数(宽/高)
 
            //父view的测量模式
            int specMode = MeasureSpec.getMode(spec);     
 
            //父view的大小
            int specSize = MeasureSpec.getSize(spec);     
          
            //通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)   
            int size = Math.max(0, specSize - padding);  
          
            //子view想要的实际大小和模式(需要计算)  
            int resultSize = 0;  
            int resultMode = 0;  
          
            //通过父view的MeasureSpec和子view的LayoutParams确定子view的大小  
 
 
            // 当父view的模式为EXACITY时,父view强加给子view确切的值
           //一般是父view设置为match_parent或者固定值的ViewGroup 
            switch (specMode) {  
            case MeasureSpec.EXACTLY:  
                // 当子view的LayoutParams>0,即有确切的值  
                if (childDimension >= 0) {  
                    //子view大小为子自身所赋的值,模式大小为EXACTLY  
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
 
                // 当子view的LayoutParams为MATCH_PARENT时(-1)  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                    //子view大小为父view大小,模式为EXACTLY  
                    resultSize = size;  
                    resultMode = MeasureSpec.EXACTLY;  
 
                // 当子view的LayoutParams为WRAP_CONTENT时(-2)      
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    //子view决定自己的大小,但最大不能超过父view,模式为AT_MOST  
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  
          
            // 当父view的模式为AT_MOST时,父view强加给子view一个最大的值。(一般是父view设置为wrap_content)  
            case MeasureSpec.AT_MOST:  
                // 道理同上  
                if (childDimension >= 0) {  
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  
          
            // 当父view的模式为UNSPECIFIED时,父容器不对view有任何限制,要多大给多大
            // 多见于ListView、GridView  
            case MeasureSpec.UNSPECIFIED:  
                if (childDimension >= 0) {  
                    // 子view大小为子自身所赋的值  
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                    // 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0  
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    // 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0  
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                }  
                break;  
            }  
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
        }

这里需要注意的是,顶级View,即DecorView的测量规格=自身布局参数+窗口尺寸

3.view.getMeasureWidth()和view.getwidth()的区别(hight也一样):

getMeasureWidth的源码:结果就是测量的width

    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

getWidth和getHight的源码:在layout方法完成后才能获取

    /**
     * Return the width of the your view.
     *
     * @return The width of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }

    /**
     * Return the height of your view.
     *
     * @return The height of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getHeight() {
        return mBottom - mTop;
    }

区别:在正常开发中这两个值总是一样的,但的确会出现一些情况会导致两者不一样:

如果重写了View的layout方法,代码如下

public void layout(int l,int t,int r,int b){
    super.layout(l,t,r+100,b+100);
}

上述代码会导致在任何情况下View的最终宽高总是比测量宽高大100px,虽然这样做会导致View显示不正常并且也没有实际意义,但是这证明了测量宽高的确可以不等于最终宽高。

4.measureChildWithMargins和measureChild方法区别:

measureChild方法是直接把父view所有的空间-padding给子view

measureChildWithMargins方法是把剩余的空间给子View

measureChildWithMargins源码:

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

上述源码中:heightUsed就是已经用了的空间,例子参考LinearLayout。

产生的区别就是,子View设置matchparent的时候measureChild是给整个父view的大小-padding,而子View是父view-其它子View用的-padding

5.测量从头到尾的一个逻辑:从ViewRootImpl的performTraversals的performMeasure方法开头

首先明确:

(1).最大父类ViewGrope(不是实现类LinearLayout等)中没有重写onMeasure和measure方法,首先ViewGrope的onMeasure的功能是测量子View并且最后设置自己的大小,这些都需要和实际挂钩所以ViewGrope没必要重写onMeasure直接用的View的,但是如果是具体实现类如LinearLayout这些有实际逻辑(竖排或者横排)的则需要重写onMeasure方法(测量子view并且最后设置自己的大小),而measure方法是给父类调用的一个中间方法,measure方法的主要作用是调用onMeasure方法,所以一般也不用重写,具体实现类LinearLayout等也不用重写。

(2).ViewGrope的onMeasure就是先测量子View然后再设置自己的大小

(3).View就是设置自己的大小

逻辑:

(1).从上到下:从把phonewindow的大小传给DecorView,调用的DecorView的measur(widthMeasureSpec,HightMeasureSpec)方法然后走onMeasure方法,然后DecorView是ViewGrope的具体实现类所有它重写了onMeasure方法,onMeasure方法是先测量子View的大小然后再设置自己的大小,如果子View是ViewGrope的子类就递归向下一直到底是view的时候

(2).从下到上:最底层子View大小设置了,然后继续执行这个子View的父View测量其他子View,子View都测量完了就设置这个父view的大小,然后递归向上直到ID为contentView的View就结束,结束点就是我们能插手最顶层的view-->main.xml。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值