来源:AndroidChina
1.view 的绘制流程分几步,从哪开始?哪个过程结束以后能看到 view?
答:从 ViewRoot 的 performTraversals 开始,经过 measure,layout,draw 三个流程。draw 流程结束以后就可以在屏幕上看到 view 了。
2.view 的测量宽高和实际宽高有区别吗?
答:基本上百分之 99 的情况下都是可以认为没有区别的。有两种情况,有区别。第一种 就是有的时候会因为某些原因 view 会多次测量,那第一次测量的宽高 肯定和最后实际的宽高 是不一定相等的,但是在这种情况下
最后一次测量的宽高和实际宽高是一致的。此外,实际宽高是在 layout 流程里确定的,我们可以在 layout 流程里 将实际宽高写死 写成硬编码,这样测量的宽高和实际宽高就肯定不一样了,虽然这么做没有意义 而且也不好。
3.view 的 measureSpec 由谁决定?顶级 view 呢?
答:由 view 自己的 layoutparams 和父容器 一起决定自己的 measureSpec。一旦确定了 spec,onMeasure 中就可以确定 view 的宽高了。
顶级 view 就稍微特殊一点,对于 decorView 的测量在 ViewRootImpl 的源码里。
1 | //desire的这2个参数就代表屏幕的宽高, |
1 |
1 | childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); |
1 |
1 | childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); |
1 |
1 | performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); |
1 |
1 | //decorView的measureSpec就是在这里确定的,其实比普通view的measurespec要简单的多 |
1 |
1 | //代码就不分析了 一目了然的东西 |
1 |
1 | private |
1 | static |
1 | int |
1 | getRootMeasureSpec( |
1 | int |
1 | windowSize, |
1 | int |
1 | rootDimension) { |
1 |
1 | int |
1 | measureSpec; |
1 |
1 | switch |
1 | (rootDimension) { |
1 |
1 | case |
1 | ViewGroup.LayoutParams.MATCH_PARENT: |
1 |
1 | // Window can't resize. Force root view to be windowSize. |
1 |
1 | measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); |
1 |
1 | break |
1 | ; |
1 |
1 | case |
1 | ViewGroup.LayoutParams.WRAP_CONTENT: |
1 |
1 | // Window can resize. Set max size for root view. |
1 |
1 | measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); |
1 |
1 | break |
1 | ; |
1 |
1 | default |
1 | : |
1 |
1 | // Window wants to be an exact size. Force root view to be that size. |
1 |
1 | measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); |
1 |
1 | break |
1 | ; |
1 |
1 | } |
1 |
1 | return |
1 | measureSpec; |
1 | } |
4.对于普通 view 来说,他的 measure 过程中,与父 view 有关吗?如果有关,这个父 view 也就是 viewgroup 扮演了什么角色?
答:看源码:
1 | //对于普通view的measure来说 是由这个view的 父view ,也就是viewgroup来触发的。 |
1 | //也就是下面这个measureChildWithMargins方法 |
1 | protected |
1 | void |
1 | measureChildWithMargins(View child, |
1 |
1 | int |
1 | parentWidthMeasureSpec, |
1 | int |
1 | widthUsed, |
1 |
1 | int |
1 | parentHeightMeasureSpec, |
1 | int |
1 | heightUsed) { |
1 |
1 | //第一步 先取得子view的 layoutParams 参数值 |
1 |
1 | final |
1 | MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); |
1 |
1 | //然后开始计算子view的spec的值,注意这里看到 计算的时候除了要用子view的 layoutparams参数以外 |
1 |
1 | //还用到了父view 也就是viewgroup自己的spec的值 |
1 |
1 | final |
1 | int |
1 | childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, |
1 |
1 | mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin |
1 |
1 | + widthUsed, lp.width); |
1 |
1 | final |
1 | int |
1 | childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, |
1 |
1 | mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin |
1 |
1 | + heightUsed, lp.height); |
1 |
1 | child.measure(childWidthMeasureSpec, childHeightMeasureSpec); |
1 | } |
1 | //这个算view的spec的方法 看上去一大串 但是真的逻辑非常简单 就是根据父亲viewgroup |
1 | //的meaurespec 同时还有view自己的params来确定 view自己的measureSpec。 |
1 | //注意这里的参数是padding,这个值的含义是 父容器已占用的控件的大小 所以view的Specsize |
1 | //的值 你们可以看到 是要减去这个padding的值的。总大小-已经用的 =可用的。 很好理解。 |
1 | //然后就是下面的switch逻辑 要自己梳理清楚。其实也不难,主要是下面几条原则 |
1 | //如果view采用固定宽高,也就是写死的数值那种。那就不管父亲的spec的值了,view的spec 就肯定是exactly 并且大小遵循layout参数里设置的大小。 |
1 | //如果view的宽高是match_parent ,那么就要看父容器viewgroup的 spec的值了,如果父view的spec是exactly模式, |
1 | //那view也肯定是exactly,并且大小就是父容器剩下的空间。如果父容器是at_most模式,那view也是at_most 并且不会超过剩余空间大小 |
1 | //如果view的宽高是wrap_content, 那就不管父容器的spec了,view的spec一定是at_most 并且不会超过父view 剩余空间的大小。 |
1 | public |
1 | static |
1 | int |
1 | getChildMeasureSpec( |
1 | int |
1 | spec, |
1 | int |
1 | padding, |
1 | int |
1 | childDimension) { |
1 |
1 | int |
1 | specMode = MeasureSpec.getMode(spec); |
1 |
1 | int |
1 | specSize = MeasureSpec.getSize(spec); |
1 |
1 | int |
1 | size = Math.max( |
1 | 0 |
1 | , specSize - padding); |
1 |
1 | int |
1 | resultSize = |
1 | 0 |
1 | ; |
1 |
1 | int |
1 | resultMode = |
1 | 0 |
1 | ; |
1 |
1 | switch |
1 | (specMode) { |
1 |
1 | // Parent has imposed an exact size on us |
1 |
1 | case |
1 | MeasureSpec.EXACTLY: |
1 |
1 | if |
1 | (childDimension >= |
1 | 0 |
1 | ) { |
1 |
1 | resultSize = childDimension; |
1 |
1 | resultMode = MeasureSpec.EXACTLY; |
1 |
1 | } |
1 | else |
1 | if |
1 | (childDimension == LayoutParams.MATCH_PARENT) { |
1 |
1 | // Child wants to be our size. So be it. |
1 |
1 | resultSize = size; |
1 |
1 | resultMode = MeasureSpec.EXACTLY; |
1 |
1 | } |
1 | else |
1 | if |
1 | (childDimension == LayoutParams.WRAP_CONTENT) { |
1 |
1 | // Child wants to determine its own size. It can't be |
1 |
1 | // bigger than us. |
1 |
1 | resultSize = size; |
1 |
1 | resultMode = MeasureSpec.AT_MOST; |
1 |
1 | } |
1 |
1 | break |
1 | ; |
1 |
1 | // Parent has imposed a maximum size on us |
1 |
1 | case |
1 | MeasureSpec.AT_MOST: |
1 |
1 | if |
1 | (childDimension >= |
1 | 0 |
1 | ) { |
1 |
1 | // Child wants a specific size... so be it |
1 |
1 | resultSize = childDimension; |
1 |
1 | resultMode = MeasureSpec.EXACTLY; |
1 |
1 | } |
1 | else |
1 | if |
1 | (childDimension == LayoutParams.MATCH_PARENT) { |
1 |
1 | // Child wants to be our size, but our size is not fixed. |
1 |
1 | // Constrain child to not be bigger than us. |
1 |
1 | resultSize = size; |
1 |
1 | resultMode = MeasureSpec.AT_MOST; |
1 |
1 | } |
1 | else |
1 | if |
1 | (childDimension == LayoutParams.WRAP_CONTENT) { |
1 |
1 | // Child wants to determine its own size. It can't be |
1 |
1 | // bigger than us. |
1 |
1 | resultSize = size; |
1 |
1 | resultMode = MeasureSpec.AT_MOST; |
1 |
1 | } |
1 |
1 | break |
1 | ; |
1 |
1 | // Parent asked to see how big we want to be |
1 |
1 | case |
1 | MeasureSpec.UNSPECIFIED: |
1 |
1 | if |
1 | (childDimension >= |
1 | 0 |
1 | ) { |
1 |
1 | // Child wants a specific size... let him have it |
1 |
1 | resultSize = childDimension; |
1 |
1 | resultMode = MeasureSpec.EXACTLY; |
1 |
1 | } |
1 | else |
1 | if |
1 | (childDimension == LayoutParams.MATCH_PARENT) { |
1 |
1 | // Child wants to be our size... find out how big it should |
1 |
1 | // be |
1 |
1 | resultSize = View.sUseZeroUnspecifiedMeasureSpec ? |
1 | 0 |
1 | : size; |
1 |
1 | resultMode = MeasureSpec.UNSPECIFIED; |
1 |
1 | } |
1 | else |
1 | if |
1 | (childDimension == LayoutParams.WRAP_CONTENT) { |
1 |
1 | // Child wants to determine its own size.... find out how |
1 |
1 | // big it should be |
1 |
1 | resultSize = View.sUseZeroUnspecifiedMeasureSpec ? |
1 | 0 |
1 | : size; |
1 |
1 | resultMode = MeasureSpec.UNSPECIFIED; |
1 |
1 | } |
1 |
1 | break |
1 | ; |
1 |
1 | } |
1 |
1 | return |
1 | MeasureSpec.makeMeasureSpec(resultSize, resultMode); |
1 |
1 | } |
5.view 的 meaure 和 onMeasure 有什么关系?
答:看源码:
1 | //view的measure是final 方法 我们子类无法修改的。 |
1 |
1 | public |
1 | final |
1 | void |
1 | measure( |
1 | int |
1 | widthMeasureSpec, |
1 | int |
1 | heightMeasureSpec) { |
1 |
1 | boolean |
1 | optical = isLayoutModeOptical( |
1 | this |
1 | ); |
1 |
1 | if |
1 | (optical != isLayoutModeOptical(mParent)) { |
1 |
1 | Insets insets = getOpticalInsets(); |
1 |
1 | int |
1 | oWidth = insets.left + insets.right; |
1 |
1 | int |
1 | oHeight = insets.top + insets.bottom; |
1 |
1 | widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); |
1 |
1 | heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); |
1 |
1 | } |
1 |
1 | // Suppress sign extension for the low bytes |
1 |
1 | long |
1 | key = ( |
1 | long |
1 | ) widthMeasureSpec << |
1 | 32 |
1 | | ( |
1 | long |
1 | ) heightMeasureSpec & 0xffffffffL; |
1 |
1 | if |
1 | (mMeasureCache == |
1 | null |
1 | ) mMeasureCache = |
1 | new |
1 | LongSparseLongArray( |
1 | 2 |
1 | ); |
1 |
1 | if |
1 | ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || |
1 |
1 | widthMeasureSpec != mOldWidthMeasureSpec || |
1 |
1 | heightMeasureSpec != mOldHeightMeasureSpec) { |
1 |
1 | // first clears the measured dimension flag |
1 |
1 | mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; |
1 |
1 | resolveRtlPropertiesIfNeeded(); |
1 |
1 | int |
1 | cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? - |
1 | 1 |
1 | : |
1 |
1 | mMeasureCache.indexOfKey(key); |
1 |
1 | if |
1 | (cacheIndex < |
1 | 0 |
1 | || sIgnoreMeasureCache) { |
1 |
1 | // measure ourselves, this should set the measured dimension flag back |
1 |
1 | onMeasure(widthMeasureSpec, heightMeasureSpec); |
1 |
1 | mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; |
1 |
1 | } |
1 | else |
1 | { |
1 |
1 | long |
1 | value = mMeasureCache.valueAt(cacheIndex); |
1 |
1 | // Casting a long to int drops the high 32 bits, no mask needed |
1 |
1 | setMeasuredDimensionRaw(( |
1 | int |
1 | ) (value >> |
1 | 32 |
1 | ), ( |
1 | int |
1 | ) value); |
1 |
1 | mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; |
1 |
1 | } |
1 |
1 | // flag not set, setMeasuredDimension() was not invoked, we raise |
1 |
1 | // an exception to warn the developer |
1 |
1 | if |
1 | ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { |
1 |
1 | throw |
1 | new |
1 | IllegalStateException( |
1 | "View with id " |
1 | + getId() + |
1 | ": " |
1 |
1 | + getClass().getName() + |
1 | "#onMeasure() did not set the" |
1 |
1 | + |
1 | " measured dimension by calling" |
1 |
1 | + |
1 | " setMeasuredDimension()" |
1 | ); |
1 |
1 | } |
1 |
1 | mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; |
1 |
1 | } |
1 |
1 | mOldWidthMeasureSpec = widthMeasureSpec; |
1 |
1 | mOldHeightMeasureSpec = heightMeasureSpec; |
1 |
1 | mMeasureCache.put(key, (( |
1 | long |
1 | ) mMeasuredWidth) << |
1 | 32 |
1 | | |
1 |
1 | ( |
1 | long |
1 | ) mMeasuredHeight & 0xffffffffL); |
1 | // suppress sign extension |
1 |
1 | } |
1 | //不过可以看到的是在measure方法里调用了onMeasure方法 |
1 | //所以就能知道 我们在自定义view的时候一定是重写这个方法! |
1 |
1 | protected |
1 | void |
1 | onMeasure( |
1 | int |
1 | widthMeasureSpec, |
1 | int |
1 | heightMeasureSpec) { |
1 |
1 | setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), |
1 |
1 | getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); |
1 |
1 | } |
6.简要分析 view 的 measure 流程?
答:先回顾问题 4,viewgroup 算出子 view 的 spec 以后 会调用子 view 的 measure 方法,而子 view 的 measure 方法 我们问题 5 也看过了实际上是调用的 onMeasure 方法
所以我们只要分析好 onMeasure 方法即可,注意 onMeasure 方法的参数 正是他的父 view 算出来的那 2 个 spec 的值(这里 view 的 measure 方法会把这个 spec 里的 specSize 值做略微的修改 这个部分 不做分析 因为 measure 方法修改 specSize 的部分很简单)。
1 | //可以看出来这个就是setMeasuredDimension方法的调用 这个方法看名字就知道就是确定view的测量宽高的 |
1 | //所以我们分析的重点就是看这个getDefaultSize 方法 是怎么确定view的测量宽高的 |
1 |
1 | protected |
1 | void |
1 | onMeasure( |
1 | int |
1 | widthMeasureSpec, |
1 | int |
1 | heightMeasureSpec) { |
1 |
1 | setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), |
1 |
1 | getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); |
1 |
1 | } |
1 | //这个方法特别简单 基本可以认为就是近似的返回spec中的specSize,除非你的specMode是UNSPECIFIED |
1 | //UNSPECIFIED 这个一般都是系统内部测量才用的到,这种时候返回size 也就是getSuggestedMinimumWidth的返回值 |
1 |
1 | public |
1 | static |
1 | int |
1 | getDefaultSize( |
1 | int |
1 | size, |
1 | int |
1 | measureSpec) { |
1 |
1 | int |
1 | result = size; |
1 |
1 | int |
1 | specMode = MeasureSpec.getMode(measureSpec); |
1 |
1 | int |
1 | specSize = MeasureSpec.getSize(measureSpec); |
1 |
1 | switch |
1 | (specMode) { |
1 |
1 | case |
1 | MeasureSpec.UNSPECIFIED: |
1 |
1 | result = size; |
1 |
1 | break |
1 | ; |
1 |
1 | case |
1 | MeasureSpec.AT_MOST: |
1 |
1 | case |
1 | MeasureSpec.EXACTLY: |
1 |
1 | result = specSize; |
1 |
1 | break |
1 | ; |
1 |
1 | } |
1 |
1 | return |
1 | result; |
1 | } |
1 | //跟view的背景相关 这里不多做分析了 |
1 | protected |
1 | int |
1 | getSuggestedMinimumWidth() { |
1 |
1 | return |
1 | (mBackground == |
1 | null |
1 | ) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); |
1 |
1 | } |
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 的生命周期里无法获得测量宽高?有什么方法可以解决这个问题吗?
答:因为 measure 的过程和 activity 的生命周期 没有任何关系。你无法确定在哪个生命周期执行完毕以后 view 的 measure 过程一定走完。可以尝试如下几种方法 获取 view 的测量宽高。
1 | //重写activity的这个方法 |
1 | public |
1 | void |
1 | onWindowFocusChanged( |
1 | boolean |
1 | hasFocus) { |
1 |
1 | super |
1 | .onWindowFocusChanged(hasFocus); |
1 |
1 | if |
1 | (hasFocus) { |
1 |
1 | int |
1 | width = tv.getMeasuredWidth(); |
1 |
1 | int |
1 | height = tv.getMeasuredHeight(); |
1 |
1 | Log.v( |
1 | "burning" |
1 | , |
1 | "width==" |
1 | + width); |
1 |
1 | Log.v( |
1 | "burning" |
1 | , |
1 | "height==" |
1 | + height); |
1 |
1 | } |
1 |
1 | } |
或者重写这个方法
1 | @Override |
1 |
1 | protected |
1 | void |
1 | onStart() { |
1 |
1 | super |
1 | .onStart(); |
1 |
1 | tv.post( |
1 | new |
1 | Runnable() { |
1 |
1 | @Override |
1 |
1 | public |
1 | void |
1 | run() { |
1 |
1 | int |
1 | width = tv.getMeasuredWidth(); |
1 |
1 | int |
1 | height = tv.getMeasuredHeight(); |
1 |
1 | } |
1 |
1 | }); |
1 |
1 | } |
再或者:
1 | @Override |
1 |
1 | protected |
1 | void |
1 | onStart() { |
1 |
1 | super |
1 | .onStart(); |
1 |
1 | ViewTreeObserver observer = tv.getViewTreeObserver(); |
1 |
1 | observer.addOnGlobalLayoutListener( |
1 | new |
1 | ViewTreeObserver.OnGlobalLayoutListener() { |
1 |
1 | @Override |
1 |
1 | public |
1 | void |
1 | onGlobalLayout() { |
1 |
1 | int |
1 | width = tv.getMeasuredWidth(); |
1 |
1 | int |
1 | height = tv.getMeasuredHeight(); |
1 |
1 | tv.getViewTreeObserver().removeOnGlobalLayoutListener( |
1 | this |
1 | ); |
1 |
1 | } |
1 |
1 | }); |
1 |
1 | } |
10.layout 和 onLayout 方法有什么区别?
答:layout 是确定本身 view 的位置 而 onLayout 是确定所有子元素的位置。layout 里面 就是通过 serFrame 方法设设定本身 view 的 四个顶点的位置。这 4 个位置以确定 自己 view 的位置就固定了
然后就调用 onLayout 来确定子元素的位置。view 和 viewgroup 的 onlayout 方法都没有写。都留给我们自己给子元素布局
11.draw 方法 大概有几个步骤?
答: 一共是 4 个步骤, 绘制背景———绘制自己——–绘制 chrildren—-绘制装饰。
12.setWillNotDraw 方法有什么用?
答:这个方法在 view 里。
1 | /** |
1 |
1 | * If this view doesn't do any drawing on its own, set this flag to |
1 |
1 | * allow further optimizations. By default, this flag is not set on |
1 |
1 | * View, but could be set on some View subclasses such as ViewGroup. |
1 |
1 | * |
1 |
1 | * Typically, if you override {@link #onDraw(android.graphics.Canvas)} |
1 |
1 | * you should clear this flag. |
1 |
1 | * |
1 |
1 | * @param willNotDraw whether or not this View draw on its own |
1 |
1 | */ |
1 |
1 | public |
1 | void |
1 | setWillNotDraw( |
1 | boolean |
1 | willNotDraw) { |
1 |
1 | setFlags(willNotDraw ? WILL_NOT_DRAW : |
1 | 0 |
1 | , DRAW_MASK); |
1 |
1 | } |
用于设置标志位的 也就是说 如果你的自定义 view 不需要 draw 的话,就可以设置这个方法为 true。这样系统知道你这个 view 不需要 draw 可以优化执行速度。viewgroup 一般都默认设置这个为 true,因为 viewgroup 多数都是只负责布局
不负责 draw 的。而 view 这个标志位 默认一般都是关闭的。
13.自定义 view 有哪些需要注意的点?
答:主要是要处理 wrap_content 和 padding。否则 xml 那边设置这 2 个属性就根本没用了。还有不要在 view 中使用 handler 因为人家已经提供了 post 方法。如果是继承自 viewGroup,那在 onMeasure 和 onLayout 里面 也要考虑
padding 和 layout 的影响。也就是说 specSize 要算一下 。最后就是如果 view 的动画或者线程需要停止,可以考虑在 onDetachedFromWindow 里面来做。
针对上述的几点,给出几个简单的自定义 view 供大家理解。
给出一个圆形的 view 范例:
1 | package |
1 | com.example.administrator.motioneventtest; |
1 | import |
1 | android.content.Context; |
1 | import |
1 | android.graphics.Canvas; |
1 | import |
1 | android.graphics.Color; |
1 | import |
1 | android.graphics.Paint; |
1 | import |
1 | android.util.AttributeSet; |
1 | import |
1 | android.view.View; |
1 | /** |
1 |
1 | * Created by Administrator on 2016/2/4. |
1 |
1 | */ |
1 | public |
1 | class |
1 | CircleView |
1 | extends |
1 | View { |
1 |
1 | private |
1 | int |
1 | mColor = Color.RED; |
1 |
1 | private |
1 | Paint mPaint = |
1 | new |
1 | Paint(Paint.ANTI_ALIAS_FLAG); |
1 |
1 | private |
1 | void |
1 | init() { |
1 |
1 | mPaint.setColor(mColor); |
1 |
1 | } |
1 |
1 | @Override |
1 |
1 | protected |
1 | void |
1 | onMeasure( |
1 | int |
1 | widthMeasureSpec, |
1 | int |
1 | heightMeasureSpec) { |
1 |
1 | super |
1 | .onMeasure(widthMeasureSpec, heightMeasureSpec); |
1 |
1 | int |
1 | widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); |
1 |
1 | int |
1 | widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); |
1 |
1 | int |
1 | heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); |
1 |
1 | int |
1 | heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); |
1 |
1 | //处理为wrap_content时的情况 |
1 |
1 | if |
1 | (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { |
1 |
1 | setMeasuredDimension( |
1 | 200 |
1 | , |
1 | 200 |
1 | ); |
1 |
1 | } |
1 | else |
1 | if |
1 | (widthSpecMode == MeasureSpec.AT_MOST) { |
1 |
1 | setMeasuredDimension( |
1 | 200 |
1 | , heightSpecSize); |
1 |
1 | } |
1 | else |
1 | if |
1 | (heightSpecMode == MeasureSpec.AT_MOST) { |
1 |
1 | setMeasuredDimension(widthSpecSize, |
1 | 200 |
1 | ); |
1 |
1 | } |
1 |
1 | } |
1 |
1 | @Override |
1 |
1 | protected |
1 | void |
1 | onDraw(Canvas canvas) { |
1 |
1 | super |
1 | .onDraw(canvas); |
1 |
1 | //处理padding的情况 |
1 |
1 | final |
1 | int |
1 | paddingLeft = getPaddingLeft(); |
1 |
1 | final |
1 | int |
1 | paddingRight = getPaddingRight(); |
1 |
1 | final |
1 | int |
1 | paddingTop = getPaddingTop(); |
1 |
1 | final |
1 | int |
1 | paddingBottom = getPaddingBottom(); |
1 |
1 | int |
1 | width = getWidth() - paddingLeft - paddingRight; |
1 |
1 | int |
1 | height = getHeight() - paddingTop - paddingBottom; |
1 |
1 | int |
1 | radius = Math.min(width, height) / |
1 | 2 |
1 | ; |
1 |
1 | canvas.drawCircle(paddingLeft + width / |
1 | 2 |
1 | , paddingTop + height / |
1 | 2 |
1 | , radius, mPaint); |
1 |
1 | } |
1 |
1 | public |
1 | CircleView(Context context, AttributeSet attrs, |
1 | int |
1 | defStyleAttr) { |
1 |
1 | super |
1 | (context, attrs, defStyleAttr); |
1 |
1 | init(); |
1 |
1 | } |
1 |
1 | public |
1 | CircleView(Context context) { |
1 |
1 | super |
1 | (context); |
1 |
1 | init(); |
1 |
1 | } |
1 |
1 | public |
1 | CircleView(Context context, AttributeSet attrs) { |
1 |
1 | super |
1 | (context, attrs); |
1 |
1 | init(); |
1 |
1 | } |
1 | } |
然后下面再给出一个范例,稍微复杂一点是自定义 viewgroup 了(主要是加强对 onMeasure 和 onLayout 的理解), 需求如下:
一个水平的 viewgroup,内部的子元素 为了简单 我们假定他们的宽高都是一样的。来写一个这样的简单的 viewgroup。
1 | package |
1 | com.example.administrator.motioneventtest; |
1 | import |
1 | android.content.Context; |
1 | import |
1 | android.util.AttributeSet; |
1 | import |
1 | android.util.Log; |
1 | import |
1 | android.view.View; |
1 | import |
1 | android.view.ViewGroup; |
1 | /** |
1 |
1 | * Created by Administrator on 2016/2/4. |
1 |
1 | */ |
1 | //这里我们只处理了padding的状态 没有处理margin的状态,子view的margin 对measure和layout的影响 |
1 | //就留给读者自己完成了 |
1 | public |
1 | class |
1 | CustomHorizontalLayout |
1 | extends |
1 | ViewGroup { |
1 |
1 | //设置默认的控件最小是多少 这里不提供自定义属性了 写死在代码里 你们可以自行拓展 |
1 |
1 | final |
1 | int |
1 | minHeight = |
1 | 0 |
1 | ; |
1 |
1 | final |
1 | int |
1 | minWidth = |
1 | 0 |
1 | ; |
1 |
1 | public |
1 | CustomHorizontalLayout(Context context) { |
1 |
1 | super |
1 | (context); |
1 |
1 | } |
1 |
1 | public |
1 | CustomHorizontalLayout(Context context, AttributeSet attrs) { |
1 |
1 | super |
1 | (context, attrs); |
1 |
1 | } |
1 |
1 | public |
1 | CustomHorizontalLayout(Context context, AttributeSet attrs, |
1 | int |
1 | defStyleAttr) { |
1 |
1 | super |
1 | (context, attrs, defStyleAttr); |
1 |
1 | } |
1 |
1 | @Override |
1 |
1 | protected |
1 | void |
1 | onMeasure( |
1 | int |
1 | widthMeasureSpec, |
1 | int |
1 | heightMeasureSpec) { |
1 |
1 | super |
1 | .onMeasure(widthMeasureSpec, heightMeasureSpec); |
1 |
1 | int |
1 | measureWidth = |
1 | 0 |
1 | ; |
1 |
1 | int |
1 | measureHeight = |
1 | 0 |
1 | ; |
1 |
1 | final |
1 | int |
1 | childCount = getChildCount(); |
1 |
1 | measureChildren(widthMeasureSpec, heightMeasureSpec); |
1 |
1 | int |
1 | widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); |
1 |
1 | int |
1 | widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); |
1 |
1 | int |
1 | heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); |
1 |
1 | int |
1 | heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); |
1 |
1 | final |
1 | View childView = getChildAt( |
1 | 0 |
1 | ); |
1 |
1 | final |
1 | int |
1 | paddingLeft = getPaddingLeft(); |
1 |
1 | final |
1 | int |
1 | paddingRight = getPaddingRight(); |
1 |
1 | final |
1 | int |
1 | paddingTop = getPaddingTop(); |
1 |
1 | final |
1 | int |
1 | paddingBottom = getPaddingBottom(); |
1 |
1 | //没有子控件 时 我们的宽高要作特殊处理 |
1 |
1 | if |
1 | (childCount == |
1 | 0 |
1 | ) { |
1 |
1 | //当没有子控件时,如果长宽有一个为wrap 那么就让这个控件以最小的形式展现 |
1 |
1 | //这里我们最小设置为0 |
1 |
1 | if |
1 | (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) { |
1 |
1 | setMeasuredDimension(minWidth, minHeight); |
1 |
1 | } |
1 | else |
1 | { |
1 |
1 | //否则根据我们的layout属性来 |
1 |
1 | setMeasuredDimension(getLayoutParams().width, getLayoutParams().height); |
1 |
1 | } |
1 |
1 | } |
1 | else |
1 | if |
1 | (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { |
1 |
1 | measureWidth = childView.getMeasuredWidth() * childCount; |
1 |
1 | measureHeight = childView.getMeasuredHeight(); |
1 |
1 | setMeasuredDimension(paddingLeft + measureWidth + paddingRight, paddingTop + measureHeight + paddingBottom); |
1 |
1 | } |
1 | else |
1 | if |
1 | (heightSpecMode == MeasureSpec.AT_MOST) { |
1 |
1 | measureHeight = childView.getMeasuredHeight(); |
1 |
1 | setMeasuredDimension(paddingLeft + paddingRight + widthSpecSize, paddingTop + paddingBottom + measureHeight); |
1 |
1 | } |
1 | else |
1 | if |
1 | (widthSpecMode == MeasureSpec.AT_MOST) { |
1 |
1 | measureWidth = childView.getMeasuredWidth() * childCount; |
1 |
1 | setMeasuredDimension(paddingLeft + paddingRight + measureWidth, paddingTop + paddingBottom + heightSpecSize); |
1 |
1 | } |
1 |
1 | } |
1 |
1 | @Override |
1 |
1 | protected |
1 | void |
1 | onLayout( |
1 | boolean |
1 | changed, |
1 | int |
1 | l, |
1 | int |
1 | t, |
1 | int |
1 | r, |
1 | int |
1 | b) { |
1 |
1 | final |
1 | int |
1 | paddingLeft = getPaddingLeft(); |
1 |
1 | final |
1 | int |
1 | paddingRight = getPaddingRight(); |
1 |
1 | final |
1 | int |
1 | paddingTop = getPaddingTop(); |
1 |
1 | final |
1 | int |
1 | paddingBottom = getPaddingBottom(); |
1 |
1 | //左边初始位置为0 |
1 |
1 | int |
1 | childLeft = |
1 | 0 |
1 | + paddingLeft; |
1 |
1 | final |
1 | int |
1 | childCount = getChildCount(); |
1 |
1 | for |
1 | ( |
1 | int |
1 | i = |
1 | 0 |
1 | ; i < childCount; i++) { |
1 |
1 | final |
1 | View childView = getChildAt(i); |
1 |
1 | if |
1 | (childView.getVisibility() != View.GONE) { |
1 |
1 | final |
1 | int |
1 | childWidth = childView.getMeasuredWidth(); |
1 |
1 | childView.layout(childLeft, |
1 | 0 |
1 | + paddingTop, childLeft + childWidth, paddingTop + childView.getMeasuredHeight()); |
1 |
1 | childLeft += childWidth; |
1 |
1 | } |
1 |
1 | } |
1 |
1 | } |
1 | } |