自定义View

澳门新葡亰网站注册 1

1.View的绘制流程分几步,从哪开始?哪个过程结束以后能看到view?

4.1 初始ViewRoot和DecorView
View的绘制流程是从ViewRoot的performTraversal的开始的。经过measure,layout,draw三个过程。
performTraversal会依次调用performMeasure,performLayout,performDraw来完成顶级View的measure,layout和draw过程。performMeasure方法中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素了,这样就完成了一次measure过程,layout和draw的过程类似。
(1)measure过程决定了View的宽高,几乎在所有情况下,这个宽高都等于View最终的宽高。getMeasuredHeight(),getMeasuredWidth()来得到测量宽高。
(2)layout过程决定了View四个顶点的位置,和View实际的宽高。通过getHeight()和getWidth()来得到实际的宽高。
(3)draw过程决定view的显示。
DecorView是一个FrameLayout,包含了一个竖直方向的LinearLayout,上面是标题栏,下面是内容。
4.1 理解MeasureSpec
(1)MeasureSpec和LayoutParams的对应关系。
在View测量的时候,系统会将LayoutParams在父容器的约束下转换成MeasureSpec,在根据MeasureSpec来决定View测量后的宽高。
MeasureSpec不是唯一由LayoutParams决定的,而是和父容器一起决定的。对于DecorView,它的MeasureSpec是由窗口的大小和自身的LayoutParams确定的。
(2)普通view的MeasureSpec的创建规则
当View采用固定宽高时,不管父容器采用什么MeasureSpec,View的MeasureSpec都是精确模式,大小等于LayoutParams中的大小。
当View采用match_parent时,如果父容器是精确模式,那么view也是精确模式,大小是父容器剩余的大小。如果父容器是最大模式,那么view也是最大模式,大小不超过父容器的剩余空间。
当View采用wrap_content时,不管父容器是什么模式,View都是最大模式,大小是不超过父容器剩余的大小。
4.3 View的工作流程
(1)measure过程
view的测量过程由measure方法(final)完成,在measure方法中又会调用onMeasure方法,onMeasure会调用setMeasuredDimension方法,setMeasuredDimension会设置View宽高的测量值。当View的SpecMode是AT_MOST和EXACTLY时,getDefaultSize返回的是measureSpec中的specSize的大小,而当View的SpecMode是UNSPECIFIED的时候
返回的是size,是getSuggestedMinimumWidth的返回值,如果View没有设置背景,那么返回的是android:midWidth这个属性,如果设置了背景,返回的是android:minWidth和背景最小宽度中两者的的最大值。

答:从ViewRoot的performTraversals开始,经过measure,layout,draw
三个流程。draw流程结束以后就可以在屏幕上看到view了。

澳门新葡亰网站注册 1

2.view的测量宽高和实际宽高有区别吗?

Paste_Image.png

答:基本上百分之99的情况下都是可以认为没有区别的。有两种情况,有区别。第一种
就是有的时候会因为某些原因 view会多次测量,那第一次测量的宽高
肯定和最后实际的宽高 是不一定相等的,但是在这种情况下

(2)ViewGroup的measure过程
viewGroup除了要完成自己的measure以外,还会遍历去调用所有子元素的measure方法,各个子元素在递归完成此过程。ViewGroup是一个抽象类,因此它没有重写onMeasure方法。在onMeasure方法中拿到的测量宽高是不准确的,在onLayout中获得测量宽高或者最终的宽高。view的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate、onStart、onResume时某个view已经测量完毕了。如果view还没有测量完毕,那么获得的宽高就都是0。下面是四种解决该问题的方法:
一:onWindowFocusChanged():View已经初始化完毕,宽高已经准备好了。
二:view.post(runnable):通过post可以将一个runnable投射到消息队列的尾部,然后等待Looper调用此runnable的时候,View就已经初始化好了。
三:ViewTreeObserver:使用ViewTreeObserver的众多回调方法可以完成这个功能,比如使用onGlobalLayoutListener接口,当view树的状态发生改变或者view树内部的view的可见性发生改变时,onGlobalLayout方法将被回调。伴随着view树的状态改变,这个方法也会被多次调用。
在view的默认实现中,view的测量宽高和最终宽高是相等的,只不过测量宽高形成于measure过程,而最终宽高形成于layout过程。
(3)layout过程
ViewGroup的位置被确定以后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中OnLayout方法又会被调用。layout方法的流程:首先会通过setFrame方法来设定View的四个顶点,View一旦确定,View在父容器中的位置也被确定了,接着会调用onLayout方法,这个方法的用途是确定子元素的位置。onLayout的具体实现和具体的布局有关。
(4)draw过程
1.绘制背景:background.draw(canvas);
2.绘制自己:onDraw();
3.绘制children:dispatchDraw;
4.绘制装饰:onDrawScrollBars。
4.4 自定义view
(1)继承view重写onDraw方法需要自己支持wrap_content,并且padding也要自己处理。继承特定的View例如TextView不需要考虑。
继承自ViewGroup要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响。
(2)尽量不要在View中使用Handler,因为view内部本身已经提供了post系列的方法,完全可以替代Handler的作用。
澳门新葡亰网站注册,(3)view中如果有线程或者动画,需要在onDetachedFromWindow方法中及时停止。
(4)处理好view的滑动冲突情况。
如何处理wrap_content,原因?
如果在View在布局中使用wrap_content,那么它的specMode是AT_MOST,在这种模式下,它的宽高等于specSize,而specSize等于parentSize。parentSize是父容器目前剩余空间的大小。和match_parent一致。

最后一次测量的宽高和实际宽高是一致的。此外,实际宽高是在layout流程里确定的,我们可以在layout流程里
将实际宽高写死
写成硬编码,这样测量的宽高和实际宽高就肯定不一样了,虽然这么做没有意义
而且也不好。

3.view的measureSpec 由谁决定?顶级view呢?

答:由view自己的layoutparams和父容器
 一起决定自己的measureSpec。一旦确定了spec,onMeasure中就可以确定view的宽高了。

顶级view就稍微特殊一点,对于decorView的测量在ViewRootImpl的源码里。

//desire的这2个参数就代表屏幕的宽高,
  childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
  childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);



  //decorView的measureSpec就是在这里确定的,其实比普通view的measurespec要简单的多
  //代码就不分析了 一目了然的东西
  private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
}

4.对于普通view来说,他的measure过程中,与父view有关吗?如果有关,这个父view也就是viewgroup扮演了什么角色?

答:看源码:

//对于普通view的measure来说 是由这个view的 父view ,也就是viewgroup来触发的。
//也就是下面这个measureChildWithMargins方法

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
         //第一步 先取得子view的 layoutParams 参数值   
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        //然后开始计算子view的spec的值,注意这里看到 计算的时候除了要用子view的 layoutparams参数以外
        //还用到了父view 也就是viewgroup自己的spec的值
        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);
}






//这个算view的spec的方法 看上去一大串 但是真的逻辑非常简单 就是根据父亲viewgroup
//的meaurespec 同时还有view自己的params来确定 view自己的measureSpec。
//注意这里的参数是padding,这个值的含义是 父容器已占用的控件的大小 所以view的Specsize
//的值 你们可以看到 是要减去这个padding的值的。总大小-已经用的 =可用的。 很好理解。

//然后就是下面的switch逻辑 要自己梳理清楚。其实也不难,主要是下面几条原则
//如果view采用固定宽高,也就是写死的数值那种。那就不管父亲的spec的值了,view的spec 就肯定是exactly 并且大小遵循layout参数里设置的大小。

//如果view的宽高是match_parent ,那么就要看父容器viewgroup的 spec的值了,如果父view的spec是exactly模式,
//那view也肯定是exactly,并且大小就是父容器剩下的空间。如果父容器是at_most模式,那view也是at_most 并且不会超过剩余空间大小

//如果view的宽高是wrap_content, 那就不管父容器的spec了,view的spec一定是at_most 并且不会超过父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) {
        // 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;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

5.view的meaure和onMeasure有什么关系?

答:看源码:

//view的measure是final 方法 我们子类无法修改的。
 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

//不过可以看到的是在measure方法里调用了onMeasure方法
//所以就能知道 我们在自定义view的时候一定是重写这个方法!
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

6.简要分析view的measure流程?

答:先回顾问题4,viewgroup 算出子view的spec以后
会调用子view的measure方法,而子view的measure方法
我们问题5也看过了实际上是调用的onMeasure方法

所以我们只要分析好onMeasure方法即可,注意onMeasure方法的参数
正是他的父view算出来的那2个spec的值(这里view的measure方法会把这个spec里的specSize值做略微的修改
这个部分 不做分析 因为measure方法修改specSize的部分很简单)。

//可以看出来这个就是setMeasuredDimension方法的调用 这个方法看名字就知道就是确定view的测量宽高的
//所以我们分析的重点就是看这个getDefaultSize 方法 是怎么确定view的测量宽高的
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }


//这个方法特别简单 基本可以认为就是近似的返回spec中的specSize,除非你的specMode是UNSPECIFIED
//UNSPECIFIED 这个一般都是系统内部测量才用的到,这种时候返回size 也就是getSuggestedMinimumWidth的返回值
 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的背景相关 这里不多做分析了
protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

7.自定义view中 如果onMeasure方法 没有对wrap_content 做处理
会发生什么?为什么?怎么解决?

答:如果没有对wrap_content做处理
,那即使你在xml里设置为wrap_content.其效果也和match_parent相同。看问题4的分析。我们可以知道view自己的layout为wrap,那mode就是at_most(不管父亲view是什么specmode).

这种模式下宽高就是等于specSize(getDefaultSize函数分析可知),而这里的specSize显然就是parentSize的大小。也就是父容器剩余的大小。那不就和我们直接设置成match_parent是一样的效果了么?

解决方式就是在onMeasure里 针对wrap 来做特殊处理
比如指定一个默认的宽高,当发现是wrap_content 就设置这个默认宽高即可。

8.ViewGroup有onMeasure方法吗?为什么?

答:没有,这个方法是交给子类自己实现的。不同的viewgroup子类
肯定布局都不一样,那onMeasure索性就全部交给他们自己实现好了。

9.为什么在activity的生命周期里无法获得测量宽高?有什么方法可以解决这个问题吗?