对于Android View绘制的一些思考

AT_MOST      表示最大是多大. UNSPECIFIED      不确定是多大, 你想多大就多大,我尽量满足你. EXACTLY      就这么大 已经指定了大小      MATCH_PARENT 为什么说MATCH_PARENT 因为 MATCH_PARENT 就是间接说 我要占据父控件剩下的那部分了。      这就相当于指定了确定的宽或高。      一个view 的绘制需要三个阶段 measure -> layout ->  draw      view的测量阶段      measure-> onMeasure() 我们就看单纯一个view的测量 测量自身的大小      它需要有两个参数      widthMeasureSpec      heightMeasureSpec      这两个参数 是由他们所在的父容器传给他们的,父控件是要传递多大呢? 这是一个给view自己测量时候的      参考值,肯定不能乱给。我的问题是 父容器会传多少?为什么会传递这么大?先记住这个问题(1)      在这里面我需要调用下面的方法,来设置测量的值,调用这个方法,就表示该view 在测量这一阶段完成了。      setMeasuredDimension(width, height)      注意在基类 View onMeasure() 方法调用是这样的:      setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),  widthMeasureSpec),                           getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));这里面 View 是一个通俗的实现,就是默认值,意思要么使用父控件指定的( xxxMeasureSpec),要么自己测量(getSuggestedMininumxxx)。然后,我们就看了一下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;
    }
从getDefaultSize() 的实现。 我们可以得到这么个结论。除了unspecified 模式之外 其他模式(exactly, at_most)都用了父容器传递过来的值。也就是父容器指定的值。原来父容器给的参考值这么重要。话又说回来 什么叫父指定的值,为什么要指定值,自己测量自己不就行了么?那么多事.这个问题也很好理解:同志们都知道 view(比如 常用的TextView等) 一般情况下 在xml 文件中定义中view 必然是有父布局的并且view的宽高也是必须指定(wrap_content、match_parent 、固定值) 不指定的话编译都过不了。而传递的参考值就是在这里设定的值。于是,对于问题1我基本是想通了。so 现在想一下,我们是如何动态创建一个view,并将其放到一个布局里面的,代码如下
LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
TextView text_view = new TextView(this);
ll.add(text_view);
TextView text_view = new TextView(this);
text_view.setText("计划好了再娶吧");
text_view.setBackgroundColor(Color.RED);
ll.addView(text_view);
此时你看到了所显示的。不要激动!注意对于LinearLayout 来说如果是VERTICAL 那么view默认生成的 LayoutParams 宽度是match_parent.这里为什么要说下动态生成与添加view,在这里我就是想说这个LayoutParams,我们可以为我们的view 设置LayoutParams 也可以用系统默认的,系统默认的就是上层父控件默认的,这里不设置就是用我们LinearLayout 默认给子view生成的。不管怎么实现,我们的父控件 会拿到了我们的LayoutParams 里面设置的各种参数。然后 呢 ?--requestLayout 继续三个过程,并且是在父控件(我们的LinearLayout)的3个过程measure -> layout -> draw于是我们就可以 开始 measure ,但是父控件调用measure 必然是为了测量自身,这是必须明白的。 measure->onMeasure真正的测量在onMeasure中开始了。在这里面会测量每一个子view 的大小。
protected void measureChildWithMargins(View child,
                       int parentWidthMeasureSpec,
                                    int widthUsed,
                      int parentHeightMeasureSpec,
                                   int heightUsed)
必须得看一下源码(因为不是太长)
 /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding
     * and margins. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    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);
    }
通过源码我们可以看出这个方法所做的事情1、得到子view 的MarginLayoutParams2、确定子view 长宽的参考值,调用getChildMeasureSpec(...)3、调用子view 的 measure 并传入参考值 child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 进入子view测量阶段

看一下getChildMeasureSpec(...)的源码(稍微有点长,但是人的好奇心促使我们继续前行,因为我们必须知道给子view的参考值是怎么来的,注释好好看,我都加粗了):
  /**
     * 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);
    }
public static int getChildMeasureSpec(int spec, int padding, int childDimension)spec: 父控件的测量值,主要是为得到 父控件相关的规格(mode)padding : 父控件中已经被使用的部分childDimension 我们通过LayoutParams 设定的值(match_parent、wrap_content 或者固定值) 注意仅仅是尺寸               这里并没有包括相关的mode值,因我就是为了得到子view 的mode值1、计算剩余空间 int size = Math.max(0, specSize - padding);2、拿到父控件的 specMode 与 specSizeint specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);对于 接下来代码,就是通过父控件的specMode 和 子view 自身的LayoutParams值来确定 子view大小的参考值和规格但是源码中的几句英文注释,对于我们来说理解分析at_most exactly unspecified 不同有很大关系。我理解是这样的:每一种mode都有 一个size 与之对应 mode 就是说明了size这个参数表示的意义at_most        at_most this sizeexactly        exactly this sizeunspecified    unspecified this size现在我们测量完成之后 每一个 子view 的大小,得到所需要参考值(mode + size)。然后测量完所有子view之后,我们就可以调用 child.getMeasuredWidth() 方法得到每一个子view宽度。通过计算各个子view 的宽度,和自身的padding 我们就得到了我们父容器的测量值。于是我们就可以大胆的setMeasuredDimension 来设置自己(LinearLayout)的测量的高度。但这仅仅是测量大小,还有layout 呢!layout 是如何开始的肯定是从layout(),在这里面如果发现位置发生了变化,那么就会onLayout()方法并且会通知所有注册 OnLayoutChangeListener 的类,又是观察模式。来到了onLayout() 发现是空的,当然是空的。这是给有子view 的来用的啊。于是看下ViewGroup 中源码的实现,不过还是抽象方法。因为这需要具体的布局来自己实现。于是选择LinearLayout来看onLayout的具体实现。分析留到下一次~

题外话:比如 对于一个TextView  TextView.measure(0, 0); 会知道TextView 的长宽。TextView 在测量的时候(测量很复杂,因为是自身的测量),是一定会参考一下父控件传过来的值,并且只有mode为exactly 或者 at_most 时候才会有所参考,否则就是,按照自己实际情况测量的结果。因为传递时俩0,那么mode 就是unspecified 因此就会自己测量自己的大小。也就是最小宽度和高度。最小高度和最小宽度。可以在xml中  android:minHeight 与 android:minWidth 来设置。不设置就用背景的宽度和高度。
时间: 2024-08-10 15:07:20

对于Android View绘制的一些思考的相关文章

简单研究Android View绘制一

2015-07-27 16:52:58 一.如何通过继承ViewGroup来实现自定义View?首先得搞清楚Android时如何绘制View的,参考Android官方文档:How Android Draws Views 以下翻译摘自:http://blog.csdn.net/linghu_java/article/details/23882681,这也是一片好文章,推荐大家看看- When an Activity receives focus, it will be requested to d

简单研究Android View绘制三 布局过程

2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: 1 /** 2 * Assign a size and position to a view and all of its 3 * descendants 4 * 5 * <p>This is the second phase of the layout mechanism. 6 * (The fir

[Android][转]Android View绘制13问13答

转自:http://www.androidchina.net/4458.html 1.view的绘制流程分几步,从哪开始?哪个过程结束以后能看到view? 答:从ViewRoot的performTraversals开始,经过measure,layout,draw 三个流程.draw流程结束以后就可以在屏幕上看到view了. 2.view的测量宽高和实际宽高有区别吗? 答:基本上百分之99的情况下都是可以认为没有区别的.有两种情况,有区别.第一种 就是有的时候会因为某些原因 view会多次测量,那

Android View绘制及实践

概述 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为: - 判断是否需要重新计算视图大小(measure) - 判断是否重新需要安置视图的位置(layout) - 判断是否需要重绘(draw) 其整个流程图如下: 图片来自:Android 开源项目源码解析 公共技术点中的 View 绘制流程 在Android中View的整个生命周期,调用invalidate和requestLayout会触发一系列的方法,

Android View 绘制过程

Android的View绘制是从根节点(Activity是DecorView)开始,他是一个自上而下的过程.View的绘制经历三个过程:Measure.Layout.Draw.基本流程如下图: performTraversals函数,具体的可以参考一下源代码: 1 private void performTraversals() { 2 final View host = mView; 3 ... 4 host.measure(childWidthMeasureSpec, childHeight

Android View 绘制流程(Draw) 完全解析

前言 前几篇文章,笔者分别讲述了DecorView,measure,layout流程等,接下来将详细分析三大工作流程的最后一个流程--绘制流程.测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么由绘制流程完成.以下源码均取自Android API 21. 从performDraw说起 前面几篇文章提到,三大工作流程始于ViewRootImpl#performTraversals,在这个方法内部会分别调用performMeasure

Android View绘制机制

------------------------------------------------------------------------------ GitHub:lightSky    微博:    light_sky, 即时分享最新技术,欢迎关注 ------------------------------------------------------------------------------ 前言 该篇文章来自一个开源项目android-open-project-analy

Android View绘制流程

框架分析 在之前的下拉刷新中,小结过触屏消息先到WindowManagerService(Wms)然后顺次传递给ViewRoot(派生自Handler),经decor view到Activity再传递给指定的View,这次整理View的绘制流程,通过源码可知,这个过程应该没有涉及到IPC(或者我没有发现),需要绘制时在UI线程中通过ViewRoot发送一个异步请求消息,然后ViewRoot自己接收并不处理这个消息. 在正式进入View绘制之前,首先需要明确一下Android UI的架构组成,偷图

简单研究Android View绘制二 LayoutParams

2015-07-28 17:23:20 本篇是关于LayoutParams相关 ViewGroup.LayoutParams文档解释如下: LayoutParams are used by views to tell their parents how they want to be laid out. See ViewGroup Layout Attributes for a list of all child view attributes that this class supports.