美文网首页常用汇总
Android 进阶之 View 的绘制(二)

Android 进阶之 View 的绘制(二)

作者: Kevin_小飞象 | 来源:发表于2019-04-09 11:10 被阅读0次

    自定义 View 之 Measure 过程

    作用

    测量 View 的宽 / 高

    View 的 measure 过程

    • 应用场景
      在没有现成的控件满足需求、需自己实现时,则使用自定义 View。如:制作一个支持加载网络图片的 ImageView 控件。
    • 使用
      继承自 View、SurfaceView 或其他 View;不包含子 View。
    • 流程


      View 的 measure 过程
    /**
      * 源码分析:measure()
      * 定义:Measure过程的入口;属于View.java类 & final类型,即子类不能重写此方法
      * 作用:基本测量逻辑的判断
      **/ 
    
        public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    
            // 参数说明:View的宽 / 高测量规格
    
            ...
    
            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);
    
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                // 计算视图大小 ->>分析1
    
            } else {
                ...
          
        }
    
    /**
      * 分析1:onMeasure()
      * 作用:a. 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
      *      b. 存储测量后的View宽 / 高:setMeasuredDimension()
      **/ 
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        // 参数说明:View的宽 / 高测量规格
    
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                             getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
        // setMeasuredDimension() :获得View宽/高的测量值 ->>分析2
        // 传入的参数通过getDefaultSize()获得 ->>分析3
    }
    
    /**
      * 分析2:setMeasuredDimension()
      * 作用:存储测量后的View宽 / 高
      * 注:该方法即为我们重写onMeasure()所要实现的最终目的
      **/
        protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
    
        //参数说明:测量后子View的宽 / 高值
    
            // 将测量后子View的宽 / 高值进行传递
                mMeasuredWidth = measuredWidth;  
                mMeasuredHeight = measuredHeight;  
              
                mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;  
            } 
        // 由于setMeasuredDimension()的参数是从getDefaultSize()获得的
        // 下面我们继续看getDefaultSize()的介绍
    
    /**
      * 分析3:getDefaultSize()
      * 作用:根据View宽/高的测量规格计算View的宽/高值
      **/
      public static int getDefaultSize(int size, int measureSpec) {  
    
            // 参数说明:
            // size:提供的默认大小
            // measureSpec:宽/高的测量规格(含模式 & 测量大小)
    
                // 设置默认大小
                int result = size; 
                
                // 获取宽/高测量规格的模式 & 测量大小
                int specMode = MeasureSpec.getMode(measureSpec);  
                int specSize = MeasureSpec.getSize(measureSpec);  
              
                switch (specMode) {  
                    // 模式为UNSPECIFIED时,使用提供的默认大小 = 参数Size
                    case MeasureSpec.UNSPECIFIED:  
                        result = size;  
                        break;  
    
                    // 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值 = measureSpec中的Size
                    case MeasureSpec.AT_MOST:  
                    case MeasureSpec.EXACTLY:  
                        result = specSize;  
                        break;  
                }  
    
             // 返回View的宽/高值
                return result;  
            }    
    

    ViewGroup 的 measure 过程

    • 应用场景
      利用现有的组件根据特定的布局方式来组成新的组件。
    • 使用
      继承自 ViewGroup 或各种 Layout;含有子 View。
    • 流程


      ViewGroup 的 measure 过程
    /**
      * 源码分析:measure()
      * 作用:基本测量逻辑的判断;调用onMeasure()
      * 注:与单一View measure过程中讲的measure()一致
      **/ 
      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
    
            // 调用onMeasure()计算视图大小
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            ...
    }
    
    /**
      * 根据自身的测量逻辑复写onMeasure(),分为3步
      * 1. 遍历所有子View & 测量:measureChildren()
      * 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值(自身实现)
      * 3. 存储测量后View宽/高的值:调用setMeasuredDimension()  
      **/ 
    
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    
            // 定义存放测量后的View宽/高的变量
            int widthMeasure ;
            int heightMeasure ;
    
            // 1. 遍历所有子View & 测量(measureChildren())
            // ->> 分析1
            measureChildren(widthMeasureSpec, heightMeasureSpec);
    
            // 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值
             void measureCarson{
                 ... // 自身实现
             }
    
            // 3. 存储测量后View宽/高的值:调用setMeasuredDimension()  
            // 类似单一View的过程,此处不作过多描述
            setMeasuredDimension(widthMeasure,  heightMeasure);  
      }
      // 从上可看出:
      // 复写onMeasure()有三步,其中2步直接调用系统方法
      // 需自身实现的功能实际仅为步骤2:合并所有子View的尺寸大小
    
    /**
      * 分析1:measureChildren()
      * 作用:遍历子View & 调用measureChild()进行下一步测量
      **/ 
    
        protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
            // 参数说明:父视图的测量规格(MeasureSpec)
    
                    final int size = mChildrenCount;
                    final View[] children = mChildren;
    
                    // 遍历所有子view
                    for (int i = 0; i < size; ++i) {
                        final View child = children[i];
                         // 调用measureChild()进行下一步的测量 ->>分析1
                        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                            measureChild(child, widthMeasureSpec, heightMeasureSpec);
                        }
                    }
                }
    
    /**
      * 分析2:measureChild()
      * 作用:a. 计算单个子View的MeasureSpec
      *      b. 测量每个子View最后的宽 / 高:调用子View的measure()
      **/ 
      protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
    
            // 1. 获取子视图的布局参数
            final LayoutParams lp = child.getLayoutParams();
    
            // 2. 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
            // getChildMeasureSpec() 请看上面第2节储备知识处
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec
                    mPaddingLeft + mPaddingRight, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSpec
                    mPaddingTop + mPaddingBottom, lp.height);
    
            // 3. 将计算好的子View的MeasureSpec值传入measure(),进行最后的测量
            // 下面的流程即类似单一View的过程,此处不作过多描述
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
        // 回到调用原处
    
    
    

    相关文章

      网友评论

        本文标题:Android 进阶之 View 的绘制(二)

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