zoukankan      html  css  js  c++  java
  • Android View measure (一) 流程分析


     本篇模拟三个角色:Android 架构师-小福、Android  控件开发project师-小黑、 Android 开发project师-小白。以下依照三个角色不同角度分析measure过程。


    小福负责分享

    •     measure的本质
    •     measure代码流程
    •     onMeasure方法与MeasureSpec
    •     提出问题




    Android 架构师-小福的分享


    一、Measure本质


    小福:我今天分享是的measure架构设计相关的,先问一个问题。measure的本质是什么?

    小黑:这个我知道,是Android系统创建UI界面的measure、layout、draw三步骤的第一步,主要用于測量视图大小。更具体点说是把“相对值”(WRAP_CONTENT, FILL_PARENT, MATCH_PARENT)转换为具体指的过程。



    小福:小黑说的对,再问一个问题。视图大小指的是什么?

    小白:视图大小是在视图在屏幕上显示的大小。也就是开发的时候通过layout_width与layout_heigh设置的?

    小福:小白说的仅仅是当中一个作为开发者的角度。Android系统设计中Canvas是无穷大的,假如一个屏幕的大小是320 * 480 。可是layout_width="480px" , layout_heigh="800px",非常明显视图的宽高大于实际屏幕大小。
              问题来了。视图的大小究竟是屏幕上显示的大小。还是视图的实际大小(即使是超过了屏幕大小)?

    小黑:详细视图显示大小是由开发者设置,之后由我控件开发project师在onMeasure中决定。假设向小福说的尺寸。即使超过屏幕我能够决定是width= 320, heigh = 480 还是widt= 480, heigh = 800 ,决定权在我这里,一会在我分享的时候会写一个Demo来演示。
              (视图依据绘制大小不同分类:内容型视图、图形型视图)



    小白:Canvas是什么?
    小福:这个在之后分享draw过程的时候在具体讨论,能够笼统的理解为画画时使用的画布。

    小福:Measure的本质是把视图布局时使用的相对值转换为详细值的过程。

    二、Measure代码流程


    小福:先从源代码看下measure运行流程,看看这些过程中都做了些什么。

    下面都是android.view.ViewRootImpl.java类中的源代码


    public final class ViewRootImpl extends Handler implements ViewParent,
            View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    
        // 1 全部子视图的requestLayout方法,最总都会触发根视图此方法
        public void requestLayout() {
            checkThread();
            // 须要又一次布局
            mLayoutRequested = true;
    
            scheduleTraversals();
        }
    	
        // 调度遍历
        public void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
    			
                .....
    
                // 当前类继承自Handler。发出一个空消息。目的是增加Message队列
                sendEmptyMessage(DO_TRAVERSAL);
            }
        }
    	
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
    
            ...
    		
            case DO_TRAVERSAL:
                ...
                // 处理DO_TRAVERSAL消息
                performTraversals();
                ...
                break;
    			
    			.....
            }
        }
    	
        // 运行遍历
        private void performTraversals() {
    
            final View host = mView;
    		
            int desiredWindowWidth;
            int desiredWindowHeight;
            int childWidthMeasureSpec;
            int childHeightMeasureSpec;
            ......
    		
            if (mLayoutRequested && !mStopped) {
                ......
                childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                ......
                // host是一个View对象
                host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                ......
            }
    		
            ......
        }
    }

    注意:以上代码中getRootMeasureSpec方法能够或者跟视图中childWidthMeasureSpec与childHeightMeasureSpec。感兴趣的能够自己看下desiredWindowWidth变量的赋值其获取的是窗体的宽高。

    上面的代码一共分为5个步骤
    1 requestLayout() -> 2 scheduleTraversals() -> 3 handleMessage() -> 4 performTraversals() -> 5 host.measure(childWidthMeasureSpec, childHeightMeasureSpec);

    1. 界面中全部视图运行requestLayout。又一次布局请求会逐步向上传递。终于传运行当前ViewRootImpl的requestLaout()
    2. 步骤1中会运行scheduleTraversals,当中发送一个空的消息,把又一次布局的请求通过Handler发送到主线程的MeassQueue等待运行(详细能够学习Handler)。
    3. 由于当前ViewRootImpl是继承自Handler,所以直接查找覆写的handleMessage方法,由于传递的消息是DO_TRAVERSAL,分支调用performTraversals
    4. performTraversals方法中调用host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
    5. 由于host是View对象所以接下来须要查看View.measure方法,才干进一步分析measure流程


        接着上面的measure流程的第五步走下去,下面是android.view.View.java文件里的源代码:

    public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,
            AccessibilityEventSource {
    	
    		
        // 方法是final类型。说明不能被覆写或者重载
        public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    	
            // 假设有又一次请求标志。或者宽高发生改变	
            if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                    widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec) {
    
                ......
    
                // 真正运行測量视图大小操作
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
    
                ......
    
                // 加入又一次请求子视图布局标志
                mPrivateFlags |= LAYOUT_REQUIRED;
            }
    
            ......
        }
    	
        /**
         * Call this when something has changed which has invalidated the
         * layout of this view. This will schedule a layout pass of the view
         * tree.
         */
        public void requestLayout() {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
            }
    
            // 加入又一次请求布局标志
            mPrivateFlags |= FORCE_LAYOUT;
            mPrivateFlags |= INVALIDATED;
    
            if (mParent != null) {
                if (mLayoutParams != null) {
                    mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
                }
                if (!mParent.isLayoutRequested()) {
                    mParent.requestLayout();
                }
            }
        }	
    	
    
    }

    上面代码的measure流程能够分为4个步骤
    1 measure与requestLayout -> 2 onMeasure

    1. measure方法是final类型,说明此方法不能被改动。

      当中推断条件(mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT的值是在requestLayout 进行赋值的。

      仅仅要測量的宽高等发生改变都会触发第二步。

    2. 运行当前的onMeasure方法。通过Hierarchy Viewer等工具能够获知根视图是FrameLayout类型(这里就不从源代码验证了)

        紧接着看下android.widget.FrameLayout类的onMeasure总都做了什么?



    三、onMeasure方法与MeasureSpec

        上面显示的代码中參数int widthMeasureSpec, int heightMeasureSpec都是通过MeasureSpec类进行统一处理。
     
        MeasureSpec是一个android.view.View的内部类,封装了从父类传送到子类的布局要求信息。每一个MeasureSpec对象描写叙述了空间的高度或宽度。 MeasureSpec由size和mode组成。

    1. MeasureSpec的方法介绍:
    类名.方法名 解释
    MeasureSpec.getMode(int measureSpec) 依据提供的測量值(格式)提取模式(上述三个模式之中的一个)
    MeasureSpec.getSize(int measureSpec) 依据提供的測量值(格式)提取大小值
    MeasureSpec.makeMeasureSpec(int size,int mode) 依据提供的大小值和模式创建一个測量值(格式)


    2. MeasureSpec有三种mode。分别说明并描写叙述模式与layout參数值的相应关系
    模式 翻译 模式与Layout參数相应关系  模式描写叙述
    UNSPECIFIED 无限制   parent view不约束child view的大小
    AT_MOST 最多的 wrap_content child view能够在parent view范围内取值
    EXACTLY 准确的 fill_parent(比如50dip) parent view为child view指定固定大小

    3. MeasureSpec通过位执行从int类型的值中获取mode与sieze

    四、提出问题

    为什么布局须要父视图与子视图共同决定? 为什么不直接设置宽和高?
    假设不这样就像HTML那样指定固定大小。这样会造成一个过大把另外一个挤出去。由于HTML是具有整个页面的控制权。
    而Android是拆分开的,这样做保证了最末端的(界面开发)影响其它层级的布局,须要依照(控件开发)的规则来。

    view.setWidth, view.setHeight ? 不是
    须要通过measureChilde(view, width, height), 或者childView.measure();
    还有哪些?
    View.getWidth(), View.getHight()
    View.getMeasureWidth(), View.getMeasureHiegh()
     一个未加入到视图中的? 可是有时getMesureHeight 依旧是返回0。为什么?


  • 相关阅读:
    python3+selenium框架设计04-封装测试基类
    python3+selenium框架设计02-自动化测试框架需要什么
    python3+selenium框架设计01-Page Object
    python3+selenium入门16-窗口截图
    python3+selenium入门15-执行JavaScript
    爬虫入门
    NLP整体流程的代码
    NLTK与NLP原理及基础
    NLTK词性标注解释
    [hdu4355]Party All the Time(三分)
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7099076.html
Copyright © 2011-2022 走看看