精华内容
下载资源
问答
  • 打开Profiler,发现程序cpu占用居高不下, record之后,发现占用cpu90%以上时间的是onmeasure和childLayout方法。开始分析,最终定位到一个自定义的view上。 这个自定义view在onDraw方法中,调用了一个很不起眼的...
  • 主要介绍了Android 重写ViewGroup 分析onMeasure()和onLayout()方法的相关资料,需要的朋友可以参考下
  • 主要介绍了Android ListView 和ScroolView 出现onmeasure空指针的解决办法的相关资料,需要的朋友可以参考下
  • 近期做项目碰到ScrollView与Listview冲突的情况,查看了网上一些解决listview和scollView的冲突的方法,最终选择了重写onMeasure的方法来解决这个问题。 在此对各种方法做一个个人的总结评价。 主要的方法有四种: ...
  • 文章目录Android onMeasure()测量流程解析前言组件测量的那些结论一、MeasureSpec:测量规则二、查看测量流程源码2.1 查看ViewRootImpl的PerformTraveals()方法2.2 View类的默认onMeasure()方法2.3 从FrameLayout...

    Android onMeasure()测量流程解析


    前言

    众所周知,Android 中组件显示到屏幕上需要经历测量、布局、绘制三个阶段,我们今天来了解一下测量的流程。

    布局与绘制流程文章

    Android onLayout()布局流程解析
    Android onDraw()绘制流程解析

    组件测量的那些结论

    先看结论再看分析

    1. )测量流程的起点是ViewRootImpl的PerformTraveals()方法 ,该方法调用performMeasure()方法,performMeasure()方法会调用根View(DecorView)的measure()方法,开启测量流程。也就是说测量流程是从根View开始的,准确来说是从根View的measure()方法开始的。因为根View是ViewGroup(FrameLayout),所以测量流程是从ViewGroup开始的。

    2. )View 类有默认的onMeasure()实现,如果我们想实现自己的测量,需要重写onMeasure()方法。
    根View(DecorView)是ViewGroup(FrameLayout), ViewGroup类继承自View类,View类的measure(int widthMeasureSpec, int heightMeasureSpec) 方法会调用onMeasure(widthMeasureSpec, heightMeasureSpec)方法,根据自身规则测量自己的宽高。而ViewGroup类并没有重写其父类View的onMeasure方法,Android提供给我们的系统组件(比如FrameLayout、LinearLayout等等)和我们自定义继承自View或ViewGroup的组件,一般都需要重写onMeasure()方法,如果不重写onMeasure()方法,组件是默认填满父布局的。

    3. ) 对于根View(DecorView),其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同决定的,对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定的。

    4. ) 自定义的ViewGroup测量一般会循环自身所有的子View,调用相应的方法(measureChildWithMargins()、measureChildren方法,这两个方法都会调用getChildMeasureSpec()方法)根据自己的MeasureSpec和子View的ViewGroup.LayoutParams生成子View的MeasureSpec,并传给子View的measure(int widthMeasureSpec, int heightMeasureSpec) 方法让子View进行自身的测量。等所有的子View完成测量后,ViewGroup会根据自己的布局规则(测量规则)和子View的测量宽高完成自身的测量。

    一、MeasureSpec:测量规则

    想要了解测量流程我们需要先了解MeasureSpec类,MeasureSpec是View类的一个内部类。MeasureSpec的作用在于:在Measure流程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后在onMeasure方法中根据这个MeasureSpec来确定View的测量宽高,view可以有自己的宽高要求,但我们要考虑MeasureSpec的规则。

    我们查看MeasureSpec的源码:

    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    
        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}
    
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
    
        public static final int EXACTLY     = 1 << MODE_SHIFT;
    
    
        public static final int AT_MOST     = 2 << MODE_SHIFT;
    
     
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
        @UnsupportedAppUsage
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }
    
         @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
    
    
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
             
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }
    
    
    }
    
    

    MeasureSpec是一个32位的int值,前2位是SpecMode,表示测量模式,后30位是SpecSize,表示在某种测量模式下的规格大小。MeasureSpec将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包的方法makeMeasureSpec,SpecMode和SpecSize也是一个int值,MeasureSpec也可以通过方法getMode和getSize得到原始的SpecMode和SpecSize。

    SpecMode有三种值,如下所示。

    1. ) UNSPECIFIED
      父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部, 我们在开发过程中基本用不到。
    2. )EXACTLY
      父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。
    3. )AT_MOST
      父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

    二、查看测量流程源码

    2.1 查看ViewRootImpl的PerformTraveals()方法

    DecorView是视图的顶级View,我们添加的布局文件是它的一个子布局,而ViewRootImpl则负责渲染视图,它调用了一个performTraveals方法使得ViewTree开始三大工作流程,然后使得View展现在我们面前。该方法会依次调用:
    performMeasure、performLayout、performDraw 进行测量布局绘制三大流程。
    我们摘取其中一部分performMeasure代码查看:

    
    WindowManager.LayoutParams lp = mWindowAttributes;
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    
    

    根布局的MeasureSpec是怎么来的呢?是的,就是上面的getRootMeasureSpec,查看该方法。

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
    
        case ViewGroup.LayoutParams.MATCH_PARENT:
     
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
    
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
    
    

    参数windowSize窗口大小,rootDimension就是根布局ViewGroup.LayoutParams,我们会使用这两个参数通过MeasureSpec.makeMeasureSpec构建一个根布局的MeasureSpec。

    然后我们看到在performMeasure方法中,调用了根布局的measure(int widthMeasureSpec, int heightMeasureSpec) 方法。根布局是ViewGroup ,而ViewGroup是没有measure方法的,只有ViewGroup的父类View有measure()方法。View的measure()方法中会调用onMeasure(widthMeasureSpec, heightMeasureSpec);方法进行测量。
    我们继续查看View类的默认onMeasure()方法

    2.2 View类的默认onMeasure()方法

    View类的默认onMeasure()方法会进行默认的测量,测量结果是通过getDefaultSize()方法获取的。

    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
            
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
    
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
    
    

    可以看到,即使测量模式是AT_MOST,也返回了测量大小,这就是为什么我们自定义View时如果没有重写onMeasure()方法,设置自定义View是wrap_content,我们的组件会充满父布局的原因。
    最后通过setMeasuredDimension方法存储我们测量的组件宽高。

    根View 是FrameLayout,我们查看FrameLayout重写的onMeasure()方法。

    2.3 从FrameLayout的onMeasure()方法了解自定义ViewGroup的测量行为

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
    
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();
    
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
    
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }
    
        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
    
        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    
        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
    
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
    
        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }
    
                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
    
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
    
    

    可以看到该方法先循环子View,调用ViewGroup类的measureChildWithMargins()方法,对子View进行自身测量,在所有测量的子View中找到最大宽高,然后调用resolveSizeAndState处理后用setMeasuredDimension设置为自身的宽高,最后对MATCH_PARENT布局参数的子View进行重新测量。
    这里我们不详细展开,有兴趣的可以自行查看,包括其他系统的布局组件,像LinearLayout、RelativeLayout等等,测量过程都会测量子View,然后按照自身的布局特点(布局规则)进行自身的测量。

    我们主要看measureChildWithMargins()方法,相同作用的还有measureChild()方法,作用都是传入ViewGroup的测量规则和子View,调用getChildMeasureSpec 生成子View的测量规则。最后将生成的子View的测量规则传入child.measure()方法方法,让子View进行自身的测量。区别在于measureChildWithMargins会考虑Margins的影响,而measureChild方法不会。

    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);
    }
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
    
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
    
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    
    
    

    查看ViewGroup类的getChildMeasureSpec方法:通过父ViewGroup的测量规则和子View的布局参数,生成子View的测量规则,最后传递到子View的onMeasure()方法供自View进行自身大小的测量。
    该方法展示的也就是网上流传最多的一张图片

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
    
        int size = Math.max(0, specSize - padding);
    
        int resultSize = 0;
        int resultMode = 0;
    
        switch (specMode) {
    
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
       
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
       
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
    
    
        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;
    
    
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
    
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
           
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
     
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    
    
    

    经过上面的查看,我们验证了开头的结论,Android组件整个测量的基本流程还是非常清晰的。

    展开全文
  • 在自定义View的过程中,通常不需要重写onMeasure(),但到了ViewGroup中,常常需要自定义子View的排列,遇到LinearLayout或RelativeLayout很难甚至没办法实现的需求时,就必须要重写onMeasuer。我也曾查阅过网上许多的...

    目录

    1.引言

    2.谁来调用onMeasure()?

    3.widthMeasureSpec和heightMeasureSpec

    4.wrap_content之谜

    5.总结


     

    1.引言

    刚入职不满一年的Android新人,如有错漏请轻拍。在自定义View的过程中,通常不需要重写onMeasure(),但到了ViewGroup中,常常需要自定义子View的排列,遇到LinearLayout或RelativeLayout很难甚至没办法实现的需求时,就必须要重写onMeasure。我也曾查阅过网上许多的文章,但总感觉不够系统和完善,于是决定自己摸索一番。

    2.谁来调用onMeasure()?

    稍微看一下源码应该都能看到,onMeasure是被View#measure()调用的,那么我们为什么还要去探索这个问题呢。因为我很想知道onMeasure(int widthMeasureSpec, int heightMeasureSpec),两个参数是怎么来的。我们在XML放置自定义的ViewGroup时候,往往会放置为顶级布局。那么这两个参数,在文档上说是父布局对自定义ViewGroup的约束,是哪里传递过来的呢。

     XML文件

    <?xml version="1.0" encoding="utf-8"?>
    
    <com.example.customsizeview.ViewGroupTouch.ViewPagerCustomSize
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <com.example.customsizeview.ViewGroupTouch.CustomView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/darker_gray" />
    
    </com.example.customsizeview.ViewGroupTouch.ViewPagerCustomSize>

        布局文件仅仅是自定义ViewGroup嵌套一个自定义View,这个自定义View只重写了onMeasure()用于输出。

        其实通过AndroidStudio的Layout Inspector可以看出来即使在XML中是顶级布局,在实际的布局中也是子布局。

    从onMeasure()的定义看,我们需要先找出自定义ViewGroup的父ViewGroup也就是ContentFrameLayout,然后一层层往上追溯,直到DecorView怎么生成widthMeasureSpec、heightMeasureSpec两个参数才行。最后再一直处理和传递到自定义View Group才是,但这样未免过于复杂,而且看DecorView的源码,我并没用完全看懂。但至少我们现在清楚了两点,一个是XML的顶部布局拿到的widthMeasureSpec和heightMeasureSpec也是从上层传入的,一个是DecorView其本身代表的是手机界面的大小,比如我的小米6就是1920*1080,是个确切的数值。

    3.widthMeasureSpec和heightMeasureSpec

    widthMeasureSpec和heightMeasureSpec一个是宽一个是高,只要研究一个另一个是一样的。我们就来分析widthMeasureSpec。既然没办法很容易的搞清楚widthMeasureSpec怎么产生的,那么我们看看widthMeasureSpec到底是什么吧。widthMeasureSpec其实是包含了父ViewGroup对子View 宽度模式和宽度尺寸两个要求。

    可以通过MeasureSpec.getMode(widthMeasureSpec)和MeasureSpec.getSize(widthMeasureSpec)获取到 mode和size。

     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
            measureChildren(widthMeasureSpec, heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

    然后我们将mode和size打印出来(我通过debug打断点来打印的)。。。。。

    发现size还很好理解,mode是个啥?别急,我们看看官方控件中是怎么处理这个widthMeasureSpec的吧。measureChildren()就是ViewGroup自带的处理子View布局的方法。让我们来看看measureChildren()是怎么处理widthMeasureSpec的。

     

    首先进入ViewGroup#measureChildren()

    
        /**
         * Ask all of the children of this view to measure themselves, taking into
         * account both the MeasureSpec requirements for this view and its padding.
         * We skip children that are in the GONE state The heavy lifting is done in
         * getChildMeasureSpec.
         *
         * @param widthMeasureSpec The width requirements for this view
         * @param heightMeasureSpec The height requirements for this view
         */
        protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
            final int size = mChildrenCount;
            final View[] children = mChildren;
            for (int i = 0; i < size; ++i) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);
                }
            }
        }

    viewgroup会遍历自己的子View,对每个子view都设置一次measureChild(),并将widthMeasureSpec作为参数之一传入。

     

    再进入ViewGroup#measureChild()

    /**
         * Ask one of the children of this view to measure itself, taking into
         * account both the MeasureSpec requirements for this view and its padding.
         * The heavy lifting is done in getChildMeasureSpec.
         *
         * @param child The child to measure
         * @param parentWidthMeasureSpec The width requirements for this view
         * @param parentHeightMeasureSpec The height requirements for this view
         */
        protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
            final LayoutParams lp = child.getLayoutParams();
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom, lp.height);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }

    这里会获取到刚才传入的child,通过child.getLayoutParam获取布局属性,最后通过lp.width获取child的layout_width作为getChildMeasureSpec()的参数之一。

     

    再进入viewGroup#getChildMeasureSpec()

     /**
         * Does the hard part of measureChildren: figuring out the MeasureSpec to
         * pass to a particular child. This method figures out the right MeasureSpec
         * for one dimension (height or width) of one child view.
         *
         * The goal is to combine information from our MeasureSpec with the
         * LayoutParams of the child to get the best possible results. For example,
         * if the this view knows its size (because its MeasureSpec has a mode of
         * EXACTLY), and the child has indicated in its LayoutParams that it wants
         * to be the same size as the parent, the parent should ask the child to
         * layout given an exact size.
         *
         * @param spec The requirements for this view
         * @param padding The padding of this view for the current dimension and
         *        margins, if applicable
         * @param childDimension How big the child wants to be in the current
         *        dimension
         * @return a MeasureSpec integer for the child
         */
        public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
    
            int size = Math.max(0, specSize - padding);
    
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            //noinspection ResourceType
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }

    spec就是measureChildren()传入的widthMeasureSpec,然后获取widthMeasureSpec的mode和size,先判断mode,再判断childDimension,而childDimension就是child的layout_width。然后会给resultSize和resultMode赋值,你会发现,这两个值似乎也很熟悉,似乎和widthMeasureSpec的size和mode是一样的。的确是的,MeasureSpec.makeMeasureSpec(resultSize, resultMode)会将resultSize和resultMode组装成一个MeasureSpec(其实就是两个int通过位运算放进要给int里面,算是节省了空间),也放一下代码吧。

    View#makeMeasureSpec

            /**
             * Creates a measure specification based on the supplied size and mode.
             *
             * The mode must always be one of the following:
             * <ul>
             *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
             *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
             *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
             * </ul>
             *
             * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
             * implementation was such that the order of arguments did not matter
             * and overflow in either value could impact the resulting MeasureSpec.
             * {@link android.widget.RelativeLayout} was affected by this bug.
             * Apps targeting API levels greater than 17 will get the fixed, more strict
             * behavior.</p>
             *
             * @param size the size of the measure specification
             * @param mode the mode of the measure specification
             * @return the measure specification based on size and mode
             */
            public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                              @MeasureSpecMode int mode) {
                if (sUseBrokenMakeMeasureSpec) {
                    return size + mode;
                } else {
                    return (size & ~MODE_MASK) | (mode & MODE_MASK);
                }
            }

    现在我们了解了ViewGroup测量子View的操作后,会发现其实MeasureSpec的mode只会有三个值:

     /**
             * Measure specification mode: The parent has not imposed any constraint
             * on the child. It can be whatever size it wants.
             */
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
            /**
             * Measure specification mode: The parent has determined an exact size
             * for the child. The child is going to be given those bounds regardless
             * of how big it wants to be.
             */
            public static final int EXACTLY     = 1 << MODE_SHIFT;
    
            /**
             * Measure specification mode: The child can be as large as it wants up
             * to the specified size.
             */
            public static final int AT_MOST     = 2 << MODE_SHIFT;

    通过打印出来会发现对应的是

    UNSPECIFIED = 0

    EXACTLY = 1073741824

    AT_MOST = -2147483648

    回到最开始,自定义ViewGroup的widthMeasureSpec的mode 其实就是EXACTLY,通过注释会发现,这是父ViewGroup对子View尺寸的精确要求。子View的大小完全按照父ViewGroup的要求来设置。(这里是自定义ViewGroup的父ViewGroup对自定义ViewGroup的要求)。

    4.wrap_content之谜

    尝试过在xml文件中,子View的layout属性设置为wrap而父ViewGroup设置为match时,你往往会发现,有时候wrap_content代表的是不占空间,有时候则和设置为match_parent一样,这使得我这种看不怪表里不一的人很是头大,你咋能一天一个样呢?那么我们来看看wrap_content是如何影响测量的。

    我们将XML中CustomView的layout属性设置为wrap_content

        <com.example.customsizeview.ViewGroupTouch.CustomView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/darker_gray" />

    从之前的分析,我们看到现在我们自定义的ViewGroup收到的widthMeasureSpec mode = EXACTLY,而其调用measureChildren()时,将收到的widthMeasureSpec直接传入,最后getChildMeasureSpec()判断widthMeasureSpec的mode = EXACTLY,而子View的lp.width = wrap_content ,于是resultSize = 1080 ,resultMode = AT_MOST,最后会通过 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);将子View的测量结果保存到子View的全局变量中。

    这里child.measure()就是View#measure(),最后会执行View#onMeasure()

        /**
         * <p>
         * Measure the view and its content to determine the measured width and the
         * measured height. This method is invoked by {@link #measure(int, int)} and
         * should be overridden by subclasses to provide accurate and efficient
         * measurement of their contents.
         * </p>
         *
         * <p>
         * <strong>CONTRACT:</strong> When overriding this method, you
         * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
         * measured width and height of this view. Failure to do so will trigger an
         * <code>IllegalStateException</code>, thrown by
         * {@link #measure(int, int)}. Calling the superclass'
         * {@link #onMeasure(int, int)} is a valid use.
         * </p>
         *
         * <p>
         * The base class implementation of measure defaults to the background size,
         * unless a larger size is allowed by the MeasureSpec. Subclasses should
         * override {@link #onMeasure(int, int)} to provide better measurements of
         * their content.
         * </p>
         *
         * <p>
         * If this method is overridden, it is the subclass's responsibility to make
         * sure the measured height and width are at least the view's minimum height
         * and width ({@link #getSuggestedMinimumHeight()} and
         * {@link #getSuggestedMinimumWidth()}).
         * </p>
         *
         * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         * @param heightMeasureSpec vertical space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         *
         * @see #getMeasuredWidth()
         * @see #getMeasuredHeight()
         * @see #setMeasuredDimension(int, int)
         * @see #getSuggestedMinimumHeight()
         * @see #getSuggestedMinimumWidth()
         * @see android.view.View.MeasureSpec#getMode(int)
         * @see android.view.View.MeasureSpec#getSize(int)
         */
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }

    别看这么多其实就一行代码,就做了一件事情,将getDefaultSize()的结果作为setMeasuredDimension()的参数。(这里就不细看setMeasuredDimension()了,很简单,就是将其做一些处理后保存为view的全局变量mMeasuredWidth,getSuggestedMinimumWidth()则是判断若有背景,则返回背景图的最小值),我们看getDefaultSize()

        /**
         * Utility to return a default size. Uses the supplied size if the
         * MeasureSpec imposed no constraints. Will get larger if allowed
         * by the MeasureSpec.
         *
         * @param size Default size for this view
         * @param measureSpec Constraints imposed by the parent
         * @return The size this view should be.
         */
        public static int getDefaultSize(int size, int measureSpec) {
            int result = size;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            }
            return result;
        }

    判断子View#onMeasure()的widthMeasureSpec,其mode = AT_MOST和EXACTLY的时候,result都 = specSize,specSize就是widthMeasureSpec的size。最后返回result。那么最后setMeasuredDimension()的其实就是widthMeasureSpec的size,也就是铺满父容器。那么如何才能让wrap_content变成宽度为0呢,当然可以自己仿照getDefaultSize()来重写onMeasure(),也可以看看RelativeLayout,它用了resolveSize()来设置wrap_content的宽度。

    5.总结

    最后总结一下,自定义ViewGroup重写onMeasure()的时候,onMeasure()的两个参数也是由父ViewGroup的child.measure()传过来的,而XML中自定义ViewGroup的layout属性,在父ViewGroup执行getChildMeasureSpec()的时候会和父ViewGroup给出的widthMeasureSpec一起影响,最终生成自定义ViewGroup#onMeasure()的两个参数。概括下就是View#onMeasure()的两个参数,受父ViewGruop的MeasureSpec和View在xml中设置的layout属性一起影响。这里还有一个问题,就是顶级的布局,其父ViewGroup给顶级布局的MeasureSpec的mode到底是什么?

     

    表中 父ViewGoup代表的是顶级布局(自定义布局)。通过这样测试,我发现其实MeasureSpec的mode就是EXACTLY(结合getChildMeasureSpec反推出来的)。

    onMeasure的重写其实比较有限,主要就是要完善wrap_content的功能才去重写。重写的步骤很简单:

    • ViewGroup要给每个子View发布测量命令并配置他们的MeasureSpec(用ViewGroup已经给出的方法也是可以的)。
    • 根据getChildCount()和getChildAt()来遍历子View,然后通过view#getMeasuredWidth()来获取子View测量好的宽高。
    • 设置自定义ViewGroup的宽高,setMeasuredDimension()
          @Override
          protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
              int widthMode = MeasureSpec.getMode(widthMeasureSpec);
              int widthSize = MeasureSpec.getSize(widthMeasureSpec);
              int heightMode = MeasureSpec.getMode(heightMeasureSpec);
              int heightSize = MeasureSpec.getSize(heightMeasureSpec);
      
              measureChildren(widthMeasureSpec, heightMeasureSpec);
      
              View view = getChildAt(0);
              int viewWidth = view.getMeasuredWidth();
              int viewHeight = view.getMeasuredHeight();
              Log.d("debug","viewWidth---:"+viewWidth+"---viewHeight---:"+viewHeight);
              super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          }

       

    展开全文
  • onMeasure、onLayout 可以说是自定 View 的核心,但是很多开发者都没能理解其含义与作用,也不理解 onMeasure 、 xml 指定大小这二者的关系与差异,也不能区分 getMeasureWidth 与 getWidth 的本质区别又是什么。...

    Android 高手核心知识点笔记(不断更新中🔥)点击查看 PS:各位童鞋不要忘记给我 star 一波哦~~在这里插入图片描述
    我的 Android 知识体系,欢迎 Star https://github.com/daishengda2018/AndroidKnowledgeSystem

    自定义View —— onMeasure、 onLayout

    布局过程的作用

    • 确定每个View的尺寸和位置
    • 作用:为绘制和触摸范围做支持
      • 绘制:知道往哪里了画
      • 触摸返回:知道用户点的是哪里

    布局的流程

    从整体看

    • 测量流程:从根 View 递归调用每一级子 View 的 measure 方法,对它们进行测量。
    • 布局流程:从根 View 递归调用每一级子 View 的 layout 方法,把测量过程得出的子 View 的位置和尺寸传给子 View,子 View 保存。

    从个体看

    对于每一个 View:

    1. 运行前,开发者会根据自己的需求在 xml 文件中写下对于 View 大小的期望值

    2. 在运行的时候,父 View 会在 onMeaure()中,根据开发者在 xml 中写的对子 View 的要求, 和自身的实际可用空间,得出对于子 View 的具体尺寸要求

    3. 子 View 在自己的 onMeasure中,根据 xml 中指定的期望值和自身特点(指 View 的定义者在onMeasrue中的声明)算出自己的**期望*

      如果是 ViewGroup 还会在 onMeasure 中,调用每个子 View 的 measure () 进行测量.

    4. 父 View 在子 View 计算出期望尺寸后,得出⼦ View 的实际尺⼨和位置

    5. ⼦ View 在自己的 layout() ⽅法中将父 View 传进来的实际尺寸和位置保存

      如果是 ViewGroup,还会在 onLayout() ⾥调用每个字 View 的 layout() 把它们的尺寸 置传给它们

    为啥需要两个过程呢?

    原因一

    measure 的测量过程可能不止一次,比如有三个子 View 在一个 ViewGroup 里面,ViewGroup 的宽度是 warp_content,A 的宽度是 match_parent, B 和 C 是 warp_content, 此时 ViewGroup 的宽度是不固定的,怎么确定 A 的 match_parent 到底有多大呢?此时是如何测量的呢?

    以 LinearLayout 为例:第一次测量 LinearLayout 的大小也是没有确定的,所以无法确定 A 的 match_parent 到底有多大,这时候的 LinearLayout 会对 A 直接测量为 0 ,然后测量 B、C 的宽度,因为 B、C 的大小是包裹内容的,在测量后就可以确定 LinearLayout 的宽度了:即为最长的 B 的宽度。

    [外链图片转存失败(img-io4cWzMo-1566741015606)(assets/image-20190816011514042.png)]

    这时候再对 A 进行第二次测量,直接设置为与 LinearLayout 相同的宽度,至此达到了 match_parent 的效果。

    [外链图片转(img-1YxB5phjh66741015609)(assets/image-20190816011559286.png)(assets/image-20190816011559286.png)]

    如果将 measure 和 layout 的过程糅合在一起,会导致两次测量的时候进行无用的 layout,消耗了更多的资源,所以为了性能,将其二者分开。

    原因二

    也是二者的职责相互独立,分为两个过程,可以使流程、代码更加清晰。

    拓展

    上面例子中的情况仅仅存在于 LinearLayout中,每种布局的测量机制是不同的。那么如果 A B C 三个 View 都是 match_parent LinearLayout 是如何做的呢?

    • 第一轮测量:LinearLayout 无法确定自己的大小,所以遇到子 View match_parent 都会测量为 0

    [外链图片转存失败(img-yJZK4eWe-1566741015610)(assets/image-20190816013740231.png)]

    • 第二轮测量:都没有大小,LinearLayout 会让所有子 View 自由测量(父 View 不限制宽度)。每个测量之后都会变为和最宽的一样的宽度。

      [外链图片转存失败(img-OAccRlTA-1566741015611)(assets/image-20190816013805676.png)]

    注意:

    • onMeasure 与 measure() 、onDraw 与 draw 的区别

      onXX 方法是调度过程,而 measure、draw 才是真正做事情的。可以从源码中看到 measure 中调用了 onMeasure 方法。

      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
             // ……………
                  if (cacheIndex < 0 || sIgnoreMeasureCache) {
                      // measure ourselves, this should set the measured dimension flag back
                      onMeasure(widthMeasureSpec, heightMeasureSpec);
                   // ………………
                  }
      }
          
      
    • 为什么不把对于尺寸的要求直接交个子 View 而是要交给父 View 呢?

      因为有些场景子 View 的大小需要父 View 进行规划,例如上面的例子中 LinearLayout 的子 View 设置了 weight。

    • layout() 很少被使用到,因为他的改变没有通知父 View,这可能会导致布局重叠等问题 。在下面的「综合演练 —— 简单改写已有 View 的尺寸」中会有一个证明。

    ##onMeasure 方法

    要明确的一个问题是: 什么时候需要我们自己实现 onMeasure 方法呢?

    答:具体开发的时候有以下三种场景:

    • 当我们继承一个已有 View 的时候,简单改写他们的尺寸,比如自定义一个正方形的 ImageView,取宽高中较大的值为边长。
    • 完全进行自定义尺寸的计算。比如实现一个绘制圆形的 View 我们需要在尺寸为 warp_content 时指定一个大小例如下文中的「综合演练 —— 完全自定义 View 的尺寸」。
    • 自定义 Layout,这时候内部所有的子 View 的尺寸和位置都需要我们自己控制,需要重写 onMeasure()onLayout()方法。例如下文中的「综合演练 —— 自定义 Layout」

    onLayout 方法

    onLayout 方法是 ViewGroup 中用于控制子 View 位置的方法。放置子 View 位置的过程很简单,只需重写 onLayout 方法,然后获取子 View 的实例,调用子 View 的 layout 方法实现布局。在实际开发中,一般要配合 onMeasure 测量方法一起使用。在下文「综合演练 —— 自定义 Layout」中会详细演示。

    综合演练

    简单改写已有 View 的尺寸实现方形 ImageView

    • 首先来证明一下改写 layout 方法会存在的问题
    /**
     * 自定义正方形 ImageView
     *
     * Created by im_dsd on 2019-08-24
     */
    public class SquareImageView extends android.support.v7.widget.AppCompatImageView {
    
        public SquareImageView(Context context) {
            super(context);
        }
    
        public SquareImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public SquareImageView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
       @Override
        public void layout(int l, int t, int r, int b) {
            // 使用宽高的最大值设置边长
            int width = r - l;
            int height = b - t;
            int size = Math.max(width, height);
            super.layout(l, t, l + size, t + size);
        }
    }
    

    代码很简单,获取宽与高的最大值用于设置正方形 View 的边长。再看一下布局文件的设置

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        tools:context=".MainActivity">
    
    
        <com.example.dsd.demo.ui.custom.measure.SquareImageView
            android:background="@color/colorAccent"
            android:layout_width="200dp"
            android:layout_height="300dp"/>
    
        <View
            android:background="@android:color/holo_blue_bright"
            android:layout_width="200dp"
            android:layout_height="200dp"/>
    </LinearLayout>
    

    通过布局文件的描述如果是普通的 View 显示的状态应该是这样的

    [外链图片转存失败(img-aPRLST3i-1566741015611)(assets/image-20190824182806710.png)]

    而我们期待的状态应该是这样的:SquareImageView 的宽高均为 300dp。

    [外链图片转存失败(img-kRU6VS9V-1566741015612)(assets/image-20190824184207686.png)]

    但是最终的结果却是下图,虽然我们使用了 LinearLayout 但是我们通过layout() 方法改变了 SquareImageView 的大小,对于这个变化LinearLayout 并不知道,所以会发生布局重叠的问题。可见一般情况下不要使用 layout()方法

    [外链图片转存失败(img-2AwQ6d3I-1566741015612)(assets/image-20190824183744689.png)]

    • 通过 onMeasure 方法更改尺寸。
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // super.onMeasure 中已经完成了 View 的测量
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            // 获取测量的结果比较后得出最大值
            int height = getMeasuredHeight();
            int width = getMeasuredWidth();
            int size = Math.max(width, height);
            // 将结果设置回去
            setMeasuredDimension(size, size);
        }
    
    

    总结

    简单来说,更改已有 View 的尺寸主要分为以下步骤

    1. 重写 onMeasure()
    2. getMeasureWidthgetMeasureHeight()获取测量尺寸
    3. 计算最终要的尺寸
    4. setMeasuredDimension(width, height)把结果保存

    完全自定义 View 的尺寸

    此处用绘制圆形的 CircleView 做一个例子。对于这个 View 的期望是:View 的大小有内部的圆决定。

    [外链图片转存失败(img-ANAQF9RH-1566741015613)(assets/image-20190824191402384.png)]

    首先画一个圆形看看

    /**
     * 自定义 View 简单测量
     * Created by im_dsd on 2019-08-15
     */
    public class CircleView extends View {
        private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        /**
         * 为了方便简单,固定尺寸
         */
        private static final float PADDING = DisplayUtils.dp2px(20);
        private static final float RADIUS = DisplayUtils.dp2px(80);
    
        public CircleView(Context context) {
            super(context);
        }
    
        public CircleView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPaint.setColor(Color.RED);
            canvas.drawCircle(PADDING + RADIUS, PADDING + RADIUS, RADIUS, mPaint);
        }
    }
    
        <com.example.dsd.demo.ui.custom.layout.CircleView
            android:background="@android:color/background_dark"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    

    此时将大小设置为 wrap_content 包裹布局,结果会是怎么样的呢?

    [外链图片转存失败(img-KFaFriMr-1566741015613)(assets/image-20190824192535840.png)]
    竟然填充了屏幕!根本就没有包裹内容,此时就需要我们大展身手了

      @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // 没有必要再让 view 自己测量一遍了,浪费资源
            // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            
            // 计算期望的 size
            int size = (int) ((PADDING + RADIUS) * 2);
            // 获取父 View 传递来的可用大小
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    
            // 开始计算
            int result = 0;
            switch (widthMode) {
                // 不超过
                case MeasureSpec.AT_MOST:
                    // 在 AT_MOST 模式下,取二者的最小值
                    if (widthSize < size) {
                        result = widthSize;
                    } else {
                        result = size;
                    }
                    break;
                // 精准的
                case MeasureSpec.EXACTLY:
                    // 父 View 给多少用多少
                    result = widthSize;
                    break;
                // 无限大,没有指定大小
                case MeasureSpec.UNSPECIFIED:
                    // 使用计算出的大小
                    result = size;
                    break;
                default:
                    result = 0;
                    break;
            }
            // 设置大小
            setMeasuredDimension(result, result);
        }
    

    [外链图片转存失败(img-0xlvjzrm-1566741015614)(assets/image-20190824193549345.png)]

    上面的代码就是 onMeasure(int,int) 的模板代码了,要注意一点的是需要注释 super.onMeasure 方法,此处面试的时候普遍会问。

     // 没有必要再让 view 自己测量一遍了,浪费资源
     // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    

    这段模版代码其实 Android SDK 里面早就有了很好的封装 : resolveSize(int size, int measureSpec)resolveSizeAndState(int size, int measureSpec, int childMeasuredState) ,两行代码直接搞定。

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // 没有必要再让 view 自己测量一遍了,浪费资源
            // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            
            // 计算期望的 size
            int size = (int) ((PADDING + RADIUS) * 2);
            // 指定期望的 size
            int width = resolveSize(size, widthMeasureSpec);
            int height = resolveSize(size, heightMeasureSpec);
            // 设置大小
            setMeasuredDimension(width, height);
        }
    
    

    使用的时候完全可以这样做,但是非常建议大家都自己手写几遍理解其中的含义,因为面试会问到其中的细节。

    还有一点很遗憾,就是 resolveSizeAndState(int, int, int) 不好用。不好用的原因不是方法有问题,而是很多自定义 View 包括原生的 View 都没有使用 resolveSizeAndState(int, int, int) 方法,或者没用指定 sate (state 传递父 View 对于子 View 的期望,相比resolveSize(int, in) 方法对于子 View 的控制更好)所以就算设置了,也不会起作用。

    总结

    完全自定义 View 的尺寸主要分为以下步骤:

    1. 重写 onMeasure()
    2. 计算自己期望的尺寸
    3. resolveSize() 或者 resolveSizeAndState()修正结果
    4. setMeasuredDimension(width, height)保存结果

    自定义 Layout

    源码地址

    以 TagLayout 为例一步一步实现一个自定义 Layout。具体期望的效果如下图:

    [外链图片转存失败(img-Iz6Sy7Gd-1566741015614)(assets/image-20190824202927270.png)]

    重写 onLayout()

    在继承 ViewGroup 的时候 onLayout() 是必须要实现的,这意味着子 View 的位置摆放的规则,全部交由开发者定义。

    /**
     * 自定义 Layout Demo
     *
     * Created by im_dsd on 2019-08-11
     */
    public class TagLayout extends ViewGroup {
    
        public TagLayout(Context context) {
            super(context);
        }
    
        public TagLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                // 此时所有的子 View 都和 TagLayout 一样大
                child.layout(l, t, r, b);
            }
        }
    }
    
    

    实验一下是否和期望的效果一样呢

    <?xml version="1.0" encoding="utf-8"?>
    <com.example.dsd.demo.ui.custom.layout.TagLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:padding="5dp"
            android:background="#ffee00"
            android:textSize="16sp"
            android:textStyle="bold"
            android:text="音乐" />
    
    </com.example.dsd.demo.ui.custom.layout.TagLayout>
    

    [外链图片转存失败(img-9grUdnPa-1566741015615)(assets/image-20190824203849801.png)]

    的确和期望一致。如果想要 TextView 显示为 TagLayout 的四分之一呢?

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                // 子 View 显示为 TagLayout 的 1/4
                child.layout(l, t, r / 4, b / 4);
            }
        }
    

    效果达成!!!很明显onLayout可以非常灵活的控制 View 的位置

    [外链图片转存失败(img-i9K9gBGp-1566741015616)(assets/image-20190824204034040.png)]

    再尝试让两个 View 呈对角线布局呢?

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (i == 0 ){
                    child.layout(0, 0, (r - l) / 2, (b - t)  / 2);
                } else {
                    child.layout((r - l) / 2, (b - t)  / 2, (r - l), (b - t));
                }
            }
        }
    

    [外链图片转存失败(img-L7XEOX1q-1566741015616)(assets/image-20190824204701652.png)]

    onLayout的方法还是很简单的,但是在真正布局中怎么获取 View 的位置才是难点!如何获取呢,这时候就需要 onMeasure 的帮助了!

    计算

    在写具体的代码之前,先来搭建大体的框架。主要的思路就是在 onMeasure()方法中计算好子 View 的尺寸和位置信息包括 TagLayout 的具体尺寸,然后在onLayout()方法中摆放子 View。

    在计算过程中涉及到三个难点,具体请看注释

    private List<Rect> mChildRectList = new ArrayList<>();
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // 没有必要让 View 自己算了,浪费资源。 
            // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                // 难点1: 计算出对于每个子 View 的尺寸
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                // 难点2:计算出每一个子 View 的位置并保存。
                Rect rect = new Rect(?, ?, ?, ?);
                mChildRectList.add(rect);
            }
            // 难点3:根据所有子 View 的尺寸计算出 TagLayout 的尺寸
            int measureWidth = ?;
            int measureHeight = ?;
            setMeasuredDimension(measureWidth, measureHeight);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (mChildRectList.size() == 0) {
                return;
            }
            for (int i = 0; i < getChildCount(); i++) {
                if (mChildRectList.size() <= i) {
                    return;
                }
                View child = getChildAt(i);
                // 通过保存好的位置,设置子 View
                Rect rect = mChildRectList.get(i);
                child.layout(rect.left, rect.top, rect.right, rect.bottom);
            }
        }
    
    难点1 :如何计算子 View 的尺寸。

    主要涉及两点:开发者对于子 View 的尺寸设置和父 View 的具体可用空间。获取开发者对于子 View 尺寸的设置就比较简单了:

    // 获取开发者对于子 View 尺寸的设置
    LayoutParams layoutParams = child.getLayoutParams();
    int width = layoutParams.width;
    int height = layoutParams.height;
    

    获取父 View (TagLayout) 的可用空间要结合两点:

    1. TagLayout 的父 View 对于他的尺寸限制
    2. TagLayout 的剩余空间。我们用 width 为例用伪代码简单分析一下如何计算子 View 的尺寸
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    // TagLayout 已经使用过的空间,此处的计算是个难点,此处不是本例子重点,一会儿讨论
    int widthUseSize = 0;
    for (int i = 0; i < getChildCount(); i++) {
    	View child = getChildAt(i);
      // 获取开发者对于子 View 尺寸的设置
      LayoutParams layoutParams = child.getLayoutParams();
      int childWidthMode;
      int childWidthSize;
      // 获取父 View 具体的可用空间
      switch (layoutParams.width) {
      // 如果说子 View 被开发者设置为 match_parent
      	case LayoutParams.MATCH_PARENT:
        	switch (widthMode) {
          	case MeasureSpec.EXACTLY:
            // TagLayout 为 EXACTLY 模式下,子 View 可以填充的部位就是 TagLayout 的可用空间
            case MeasureSpec.AT_MOST:
            // TagLayout 为 AT_MOST 模式下有一个最大可用空间,子 View 要是想 match_parent 其实是和 
            // EXACTLY 模式一样的
            childWidthMode = MeasureSpec.EXACTLY;
            childWidthSize = widthSize - widthUseSize;
            break;
            case MeasureSpec.UNSPECIFIED:
            // 当 TagLayout 为 UNSPECIFIED 不限制尺寸的时候,意味着可用空间无限大!空间无限大还想
            // match_parent 二者完全是悖论,所以我们也要将子 View 的 mode 指定为 UNSPECIFIED
            childWidthMode = MeasureSpec.UNSPECIFIED;
            // 此时 size 已经没有作用了,写 0 就可以了
            childWidthSize = 0;
            break;
            }
          case LayoutParams.WRAP_CONTENT:
           break;
          default:
          // 具体设置的尺寸
          break;
    }
    // 获取 measureSpec
    int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode);
    

    补充一下什么时候会是 UNSPECIFIED 模式呢?比如说横向或纵向滑动的 ScrollView,他的宽度或者高度的模式就是 UNSPECIFIED

    伪代码仅仅模拟了开发者将子 View 的 size 设置为 match_parent 的情况,其他的情况读者要是感兴趣可以自己分析一下。笔者就不做过多的分析了!因为 Android SDK 早就为我们提供好了可用的 API: measureChildWithMargins(int, int, int, int)一句话就完成了对于子 View 的测量。

    难点2:计算出每一个子 View 的位置并保存。
    难点3:根据所有子 View 的尺寸计算出 TagLayout 的尺寸

    有了 measureChildWithMargins 方法,对于子 View 的测量就很简单啦。 一口气解决难点 2 3。

      @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int lineHeightUsed = 0;
            int lineWidthUsed = 0;
            int widthUsed = 0;
            int heightUsed = 0;
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                // 测量子 View 尺寸。TagLayout 的子 view 是可以换行的,所以设置 widthUsed 参数为 0
                // 让子 View 的尺寸不会受到挤压。
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed);
                if (widthMode != MeasureSpec.UNSPECIFIED && lineWidthUsed + child.getMeasuredWidth() > widthSize) {
                    // 需要换行了
                    lineWidthUsed = 0;
                    heightUsed += lineHeightUsed;
                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed);
                }
                Rect childBound;
                if (mChildRectList.size() >= i) {
                    // 不存在则创建
                    childBound = new Rect();
                    mChildRectList.add(childBound);
                } else {
                    childBound = mChildRectList.get(i);
                }
                // 存储 child 位置信息
                childBound.set(lineWidthUsed, heightUsed, lineWidthUsed + child.getMeasuredWidth(),
                                heightUsed + child.getMeasuredHeight());
                // 更新位置信息
                lineWidthUsed += child.getMeasuredWidth();
                // 获取一行中最大的尺寸
                lineHeightUsed = Math.max(lineHeightUsed, child.getMeasuredHeight());
                widthUsed = Math.max(lineWidthUsed, widthUsed);
            }
    
            // 使用的宽度和高度就是 TagLayout 的宽高啦
            heightUsed += lineHeightUsed;
            setMeasuredDimension(widthUsed, heightUsed);
        }
    

    终于写完代码啦,运行起来瞧瞧看。

    [外链图片转存失败(img-4b1o6kXK-1566741015617)(assets/image-20190825204518467.png)]

    竟然奔溃了!通过日志可以定位到是

      // 对于子 View 的测量
      measureChildWithMargins(child, widthMeasureSpec, widthUsed, 
                                                              heightMeasureSpec, heightUsed);
    

    这一句出了问题,通过源码得知measureChildWithMargins方法会有一个类型转换导致了崩溃

    protected void measureChildWithMargins(int, int ,int, int) {
    	final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    	………………
    }
    

    解决办法就是在 TagLayout 中重写方法 generateLayoutParams(AttributeSet) 返回一个 MarginLayoutParams 就可以解决问题了。

        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new MarginLayoutParams(getContext(), attrs);
        }
    

    再次运行达到最终目标!

    [外链图片转存失败(img-Z60DY2Gw-1566741015617)(assets/image-20190825214300636.png)]

    总结

    自定义 Layout 的主要步骤分为以下几点:

    1. 重写 onMeasure()
      • 遍历每一个子 View,用 measureChildWidthMargins() 测量 View
        • MarginLayoutParams 和 generateLayoutParams()
        • 有些子 View 可能需要多次测量
        • 测量完成后,得出子 View 的实际尺寸和位置,并暂时保存
      • 测量出所有子 View 的位置和尺寸后,计算出自己的尺寸,并用setMeasuredDimension(width, height)保存
    2. 重写 onLayout()
      • 遍历每个子 View,调用它们的 layout() 方法来将位置和尺寸传递给它们。

    getMeasureWidth 与 getWidth 的区别

    getMeasureXX 代表的是 onMeasure 方法结束后(准确的说应该是测量结束后)测量的值,而 getXX 代表的是 layout 阶段 right - left、bottom - top 的真实显示值,所以第一个不同点就是赋值的阶段不同,可见 getXXX 在 layout() 之前一直为 0, 而 getMeasureXX 可能不是最终值( onMeasure 可能会被调用多次),但是最终的时候二者的数值都会是相同的。使用那个还需要看具体的场景。

    总结: getMeasureXX 获取的是临时的值,而 getXX 获取的时候最终定稿的值,一般在绘制阶段、触摸反馈阶段使用 getXXX,在 onMeasure 阶段被迫使用 getMeasureXX 。

    本文所有源码地址

    我的 Android 知识体系,欢迎 Star https://github.com/daishengda2018/AndroidKnowledgeSystem

    展开全文
  •    View绘制过程就好比你向银行贷款,  在执行onMeasure的时候,好比银行告诉你大概贷款额度有多少?你根据自己的需求,进行各方面的计算,计算出一个自己大概需要的金额,然后告诉询问需要多少贷款。贷款额度...
  • onMeasure

    2015-12-11 15:07:41
    View在屏幕上显示出来要先经过measure(计算)和layout(布局).1、什么时候调用onMeasure方法? 当控件的父元素正要放置该控件时调用.父元素会问子控件一个问题,“你想要用多大地方啊?”,然后传入两个参数——...

    转自:http://blog.sina.com.cn/s/blog_61fbf8d10100zzoy.html
    View在屏幕上显示出来要先经过measure(计算)和layout(布局).

    1、什么时候调用onMeasure方法?
    当控件的父元素正要放置该控件时调用.父元素会问子控件一个问题,“你想要用多大地方啊?”,然后传入两个参数——widthMeasureSpec和heightMeasureSpec.
    这两个参数指明控件可获得的空间以及关于这个空间描述的元数据.
    更好的方法是你传递View的高度和宽度到setMeasuredDimension方法里,这样可以直接告诉父控件,需要多大地方放置子控件.

      接下来的代码片段给出了如何重写onMeasure.注意,调用的本地空方法是来计算高度和宽度的.它们会译解widthHeightSpec和heightMeasureSpec值,并计算出合适的高度和宽度值.
    java代码:
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int measuredHeight = measureHeight(heightMeasureSpec);
    int measuredWidth = measureWidth(widthMeasureSpec);
    setMeasuredDimension(measuredHeight, measuredWidth);
    }

    private int measureHeight(int measureSpec) {

    // Return measured widget height.
    }

    private int measureWidth(int measureSpec) {

    // Return measured widget width.
    }
    边界参数——widthMeasureSpec和heightMeasureSpec ,效率的原因以整数的方式传入。在它们使用之前,首先要做的是使用MeasureSpec类的静态方法getMode和getSize来译解,如下面的片段所示:

    java代码:
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    依据specMode的值,(MeasureSpec有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST)
    如果是AT_MOST,specSize 代表的是最大可获得的空间;
    如果是EXACTLY,specSize 代表的是精确的尺寸;
    如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。
    2、那么这些模式和我们平时设置的layout参数fill_parent, wrap_content有什么关系呢?
    经过代码测试就知道,当我们设置width或height为fill_parent时,容器在布局时调用子 view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。
    而当设置为 wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY, 而MeasureSpec的UNSPECIFIED模式目前还没有发现在什么情况下使用。
    View的onMeasure方法默认行为是当模式为UNSPECIFIED时,设置尺寸为mMinWidth(通常为0)或者背景drawable的最小尺寸,当模式为EXACTLY或者AT_MOST时,尺寸设置为传入的MeasureSpec的大小。
    有个观念需要纠正的是,fill_parent应该是子view会占据剩下容器的空间,而不会覆盖前面已布局好的其他view空间,当然后面布局子 view就没有空间给分配了,所以fill_parent属性对布局顺序很重要。以前所想的是把所有容器的空间都占满了,难怪google在2.2版本里把fill_parent的名字改为match_parent.

      在两种情况下,你必须绝对的处理这些限制。在一些情况下,它可能会返回超出这些限制的尺寸,在这种情况下,你可以让父元素选择如何对待超出的View,使用裁剪还是滚动等技术。
      接下来的框架代码给出了处理View测量的典型实现:

    java代码:
    @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int measuredHeight = measureHeight(heightMeasureSpec);

    int measuredWidth = measureWidth(widthMeasureSpec);

    setMeasuredDimension(measuredHeight, measuredWidth);

    }

    private int measureHeight(int measureSpec) {

    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    // Default size if no limits are specified.

    int result = 500;
    if (specMode == MeasureSpec.AT_MOST){

    // Calculate the ideal size of your
    // control within this maximum size.
    // If your control fills the available
    // space return the outer bound.

    result = specSize;
    }
    else if (specMode == MeasureSpec.EXACTLY){

    // If your control can fit within these bounds return that value.
    result = specSize;
    }

    return result;
    }

    private int measureWidth(int measureSpec) {
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    // Default size if no limits are specified.
    int result = 500;
    if (specMode == MeasureSpec.AT_MOST){
    // Calculate the ideal size of your control
    // within this maximum size.
    // If your control fills the available space
    // return the outer bound.
    result = specSize;
    }

    else if (specMode == MeasureSpec.EXACTLY){
    // If your control can fit within these bounds return that value.

    result = specSize;
    }

    return result;
    }

    展开全文
  • onMeasure多次调用问题

    千次阅读 2018-09-26 16:11:52
    一般在自定义控件的时候getMeasuredWidth/getMeasuredHeight它的赋值在View的setMeasuredDimension中,所以有时可以在onMeasure方法中看到利用getMeasuredWidth/getMeasuredHeight初始化别的参数。而getWidth/...
  • 我想分享一下自定义View中onMeasure、onLayout、onDraw这几个方法中,我认为有趣的地方,尤其是onMeasure方法对视图的测量。 onMeasure:测量视图大小 首先,这个方法是用于测量我们的View的大小的。要用好这个方法,...
  • 接着上一篇自定义view 相关的,揭秘 Android Graphics2D 实现动态效果之——invalidate() 内容的介绍,这一篇主要介绍下自定义view 中的 onMeasure()方法的使用。 在介绍前,先简单回顾下自定义view 中的 onDraw()...
  • NULL 博文链接:https://hz-chenwenbiao-91.iteye.com/blog/2082286
  • View自身的onMeasure方法就是把MeasureSpec的Size设为最终的测量结果,这样的测量问题就是match_parent和wrap_content是一样的结果(因为wrap_content的Size是最大可用Size),所以如果自定义View直接继承自View,就...
  • 在自定义view中多半都会去重写onMeasure方法,进行view的测量,测量出大小后,再在onDraw方法中进行绘制,下面是一段简易的自定义view的代码: public class MyTextView extends View { //在new一个MyTextView...
  • Android onMeasure

    2018-01-17 11:14:38
    Android onMeasure import android.content.Context; import android.graphics.Color; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; public class ...
  • 说起Android的自定义view,各位老油条肯定都不陌生了,我也是在最近重温《安卓开发艺术探索》的时候才发现的这个问题,**onMeasure的参数 MeasureSpec 到底表示了自身的属性还是父view的属性?**我问了身边的小伙伴...
  • Android开发中偶尔会用到自定义View,一般情况下,自定义View都需要继承View类的onMeasure方法,那么,为什么要继承onMeasure()函数呢?什么情况下要继承onMeasure()?系统默认的onMeasure()函数行为是怎样的 ?本文...
  • 自定义View主要实现onMeasure和onDraw方法(一个view不需要考虑布局的问题) 2.自定义ViewGroup: 自定义ViewGroup主要实现onMeasure和onLayout方法(ViewGroup通常不需要考虑绘制问题,绘制交给View就行) 接下来...
  • 一个View从创建到最终绘制出来,有三个方法是不得不提到的,那就是onMeasure测量,onLayout定位,onDraw绘制 onMeasure 对于一个View绘制前,首先需要测量出来这个View的宽高,而这步工作就是由onMeasure完成的了...
  • 前一篇文章主要讲了自定义View为什么要重载onMeasure()方法http://blog.csdn.net/tuke_tuke/article/details/73302595 那么,自定义ViewGroup又都有哪些方法需要重载或者实现呢 ? Android开发中,对于自定义View,...
  • 下面通过源码来一步步分析 onMeasure(int widthMeasureSpec, int heightMeasureSpec)函数,尤其是传过来的两个参数到底是从哪里来的。 首先看下MainActivity里面的setContentView,进入该函数后,其对应的代码如下...
  • 当你确定要返回false的时候,LayoutManager的子类需要重写onMeasure方法,因为RecyclerView的measure工作会全权交个LayoutManager的onMeasure方法去做。如果返回true,则你实现的LayoutManager的子类不需要实现...
  • 前言 玩过自定义View的小伙伴都知道,在View的绘制过程中,有一个类叫做Path,Path可以帮助我们实现很多自定义形状的View(总有...2、顺带简单的讲解一下onMeasure方法宽高约束   1.moveTo moveTo表示将绘制...
  • 自定义View控件之onMeasure方法详解

    千次阅读 2017-07-25 23:12:30
    前言转载请注明出处! 这类的文章很多很多,其实我也是不想写的.但是说起来我虽然看了很多很多的... View控件中的measure方法被父容器调用,会引发测量的整个过程,也就有了onMeasure方法 父容器调用measure方法放在下
  • .onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw (Canvas canvas) { Log.d( "MyView" , "onDraw: " ); super .onDraw(canvas); } @Override protected ...
  • Android View.onMeasure方法详解及实例 View在屏幕上显示出来要先经过measure(计算)和layout(布局). 1、什么时候调用onMeasure方法? 当控件的父元素正要放置该控件时调用.父元素会问子控件一个问题,“你想...
  • onMeasure()、onLayout()

    2019-08-12 17:06:03
    测量: onMeasure(): 测量自己的大小,为正式布局提供建议 布局: onLayout(): 使用layout()函数对所有子控件布局 绘制: onDraw(): 根据布局的位置绘图 onDraw() 里面是绘制的操作,可以看下其他的文章,下面...
  • Android自定义控件之onMeasure详解

    千次阅读 2018-03-30 14:57:32
    在Android开发中往往需要根据需求对原生控件进行自定义,其中主要涉及到的就是onMeasure,onLayout和onDraw三个方法的重写与使用,其中onMeasure是其中最复杂的一个方法,很多程序员仅仅知道该方法用来测量大小,却不知道...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 42,128
精华内容 16,851
关键字:

onmeasure