Android自定义控件 —— 三大流程
来源:互联网 发布:visualbasic2010 mac版 编辑:程序博客网 时间:2024/05/09 17:48
摘要
自定义控件在工作中时常有所接触,但始终是只得其形,不得其神。最近抽空系统学习了下这方面的知识,准备用“Android自定义控件”作为一个博客系列来记录我的学习心得。系列文中不乏引用到Android SDK源码,以API 25为准。最后,如有雷同,没错,就是我去抄的。
Android自定义控件系列目录
Android自定义控件 —— 事件分发
Android自定义控件 —— 三大流程
View的三大流程分为:测量(measure)、布局(layout)、绘制(draw),下面就一一进行探索吧。
measure
布局绘画涉及两个流程:测量流程和布局流程,测量流程通过measure(int, int)
实现,是View树自顶向下的遍历,每个View在循环过程中将尺寸细节往下传递,当测量过程完成之后,所有的View都存储了自己的尺寸。第二个过程则是通过方法layout(int, int, int, int)
来实现的,也是自顶向下的。在这个过程中,每个父容器ViewGroup负责通过计算好的尺寸放置它的子View。
前面讲过,onMeasure(int, int)
是用来测量当前控件大小的,给onLayout(boolean, int, int, int, int)
提供数值参考,需要特别注意的是,测量完成以后通过setMeasuredDimension(int,int)
设置给系统。
MeasureSpec
这是一个View的内部类,介绍之前,先来看看源码
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** @hide */ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) @Retention(RetentionPolicy.SOURCE) public @interface MeasureSpecMode {} public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } return makeMeasureSpec(size, mode); } @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); int size = getSize(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return makeMeasureSpec(size, UNSPECIFIED); } size += delta; if (size < 0) { Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size + ") spec: " + toString(measureSpec) + " delta: " + delta); size = 0; } return makeMeasureSpec(size, mode); } public static String toString(int measureSpec) { int mode = getMode(measureSpec); int size = getSize(measureSpec); StringBuilder sb = new StringBuilder("MeasureSpec: "); if (mode == UNSPECIFIED) sb.append("UNSPECIFIED "); else if (mode == EXACTLY) sb.append("EXACTLY "); else if (mode == AT_MOST) sb.append("AT_MOST "); else sb.append(mode).append(" "); sb.append(size); return sb.toString(); } } ...}
作用
MeasureSpec是用来规范测量数值的。什么意思呢?MeasureSpec规定了,一个规范的测量数值(int型,32位)应该由mode和size两部分组成,即应该由一个32位的二进制数“mode(前两位) + size(后30位,即原始测量数值)”的形式组成。
mode分类
mode是用两位二进制表示的数,其分类如下
UPSPECIFIED:父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小
EXACTLY:父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略子元素本身的大小
AT_MOST:子元素至多达到指定大小的值
它们对应的二进制值分别是
UNSPECIFIED = 00
EXACTLY = 01
AT_MOST = 10
应用
在XML布局时,通过android:layout_width
和android:layout_height
来设置控件的宽高值,宽高值分为三种类型:wrap_content、match_parent、具体值,三种值类型所对应的mode为
wrap_content ——> MeasureSpec.AT_MOST
match_parent ——> MeasureSpec.EXACTLY
具体值 ——> MeasureSpec.EXACTLY
举个例子,测量的宽度值为50,那么其所对应的MeasureSpec规范数值就应该为:01 000000000000000000000000110010,其中,前两位(01)代表mode位,50对应的二进制数为110010,不足30位补位即可。
MeasureSpec和LayoutParams
上面了解了MeasureSpec这个类,那么该类是怎么创建来的呢?根据什么创建来的呢?现在就来深入了解一下。
对于普通View,其对应的MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams来共同决定的。
普通View的measure过程由ViewGroup传递而来,那么来看看源码了解下MeasureSpec是怎么被创建出来的
package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... 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); } ...}
上述方法,首先获取到了子元素的LayoutParams,然后根据当前子元素的LayoutParams和父容器的MeasureSpec来获取子元素的 MeasureSpec,最后调用了子元素的measure(int, int)
。
来看看getChildMeasureSpec(int, int, int)
的具体实现
package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... 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); } ...}
正如先前所说,上述方法主要是根据父容器的MeasureSpec同时结合子元素的LayoutParams来确定子元素的MeasureSpec。
由于UNSPECIFIED这个mode主要用于系统内部measure,一般来说,我们无须关注,所以排除掉UNSPECIFIED来总结一下就是
当子View采用固定宽高的时候,不管父容器的MeasureSpec是什么,子View的mode都是EXACTLY,并且其大小遵循LayoutParams中的大小
当子View的宽高设为match_parent的时候,如果父容器的mode为EXACTLY,那么子View的mode也是EXACTLY,并且大小是父容器的剩余空间。如果父容器的mode为AT_MOST,那么子View的mode也是AT_MOST,并且大小不会超过父容器的剩余空间
当子View的宽高设为wrap_content的时候,不管父容器的mode是EXACTLY还是AT_MOST,子View的mode都是AT_MOST,并且大小不能够超过父容器的剩余空间
View的measure流程
View的测量流程是由measure(int, int)
来完成的
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... 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); final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; // Optimize layout by avoiding an extra EXACTLY pass when the view is // already measured as the correct size. In API 23 and below, this // extra pass is required to make LinearLayout re-distribute weight. final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec; final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); if (forceLayout || needsLayout) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = forceLayout ? -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 } ...}
发现该方法是一个final型方法,所以不能被重写。不过在其中回调了onMeasure(int, int)
,因此我们只需在View中重写onMeasure(int, int)
方法来完成View的测量即可。那么View默认的onMeasure(int, int)
实现是怎样的呢?
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } ...}
可以看到,该方法的实现很简单,直接调用了setMeasuredDimension(int, int)
来设置测量的尺寸。也就是说,关键就在于getDefaultSize(int, int)
方法里
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... 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(int, int)
方法返回的就是MeasureSpec的specSize(源码里的变量,即实际测量值)部分,而这个MeasureSpec是ViewGroup传递过来的。
到这里也就理解了,为什么当我们在布局中设置子View的宽高值为wrap_content的时候,如果不重写其onMeasure(int, int)
方法,则默认大小就是父容器的可用大小了。
当我们在布局中设置子View的宽高值为wrap_content时,那么测量模式(即mode)就是AT_MOST,在该模式下,它的宽高值等于specSize。而specSize由ViewGroup传递过来时就是parentSize(自定义的变量名称),也就是父控件的可用大小。
当我们在布局中设置子View的宽高值为match_parent时,那么不用多说,宽高值当然也是parentSize。这时候,我们只需对AT_MOST测量模式进行处理
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width = 0; int height = 0; if(widthMode == MeasureSpec.AT_MOST){ width = ... } if(heightMode == MeasureSpec.AT_MOST){ height = ... } setMeasuredDimension(widthMode != MeasureSpec.AT_MOST ? widthSize : width, heightMode != MeasureSpec.AT_MOST? heightSize : height);}
上述代码,判断当测量模式是AT_MOST时,自己计算View的宽高。其他情况,直接使用specSize。
至于UNSPECIFIED这种情况,则是使用的是getDefaultSize(int, int)
的第一个参数的值,也就是getSuggestedMinimumWidth()
和getSuggestedMinimumHeight()
所返回的值,一般用于系统内部的测量过程,这两个方法的源码如下
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } ...}
从源码看出,这是判断View有没有背景,没有背景的话,那么值就是View最小的宽度或高度,也就是对应XML布局中的android:minWidth
和android:minHeight
属性,如果属性没有指定的话,默认值为0;有背景的话,那么值就是View最小的宽度或高度和背景的最小宽度或高度,取两者中最大的一个值,这个值就是当测量模式是UNSPECIFIED时View的测量宽/高。
到这里就完成了整个View的measure过程,完成之后我们就可以通过getMeasuredWidth()
和getMeasuredHeight()
方法获取View正确的测量宽/高了。但是需要注意的是,在某些极端情况下,系统可能需要在多次measure过程后才能确定最终的测量宽/高,在这种情况下,直接在onMeasure(int, int)
中获取的测量宽/高可能是不准确的,保险的做法是在onLayout(boolean, int, int, int, int)
方法中去获取。
ViewGroup的measure流程
ViewGroup的measure流程和View不同,不仅需要完成自身的measure流程,还需要去遍历其所有子View,各个子元素再递归这个流程。ViewGroup提供了一个叫measureChildren(int, int)
的方法
package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } ...}
该方法遍历了所有的子View,判断如果子View没有GONE掉的时候,就继续执行measureChild(View, int, int)
方法
package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } ...}
该方法获取了子View的LayoutParams,然后通过getChildMeasureSpec(int, int, int)
创建子View的MeasureSpec,创建好后,将MeasureSpec传给子VIew进行其measure 流程。
解读源码发现,ViewGroup并没有定义其具体的测量流程(即实现onMeasure(int, int)
),这是因为ViewGroup是一个抽象类,它的测量流程需要其子类去实现不同的布局特性,没办法做统一实现,比如说像LinearLayout、RelativeLayout等。
layout
通过源码发现,layout流程的关键在于onLayout(boolean, int, int, int, int)
,实现View的layout流程,就是实现该View的布局,而如果换成是ViewGroup,那么就实现其所有子控件的布局。
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } ...}
package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... @Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b); ...}
draw
下面直接通过查看draw(Canvas)
的源码,来分析下其draw流程
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... public void draw(Canvas canvas) { ... /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed ... if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) ... // Step 2, save the canvas' layers ... int solidColor = getSolidColor(); if (solidColor == 0) { final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } if (drawBottom) { canvas.saveLayer(left, bottom - length, right, bottom, null, flags); } if (drawLeft) { canvas.saveLayer(left, top, left + length, bottom, null, flags); } if (drawRight) { canvas.saveLayer(right - length, top, right, bottom, null, flags); } } else { scrollabilityCache.setFadeColor(solidColor); } // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ... if (drawTop) { ... canvas.drawRect(left, top, right, top + length, p); } if (drawBottom) { ... canvas.drawRect(left, bottom - length, right, bottom, p); } if (drawLeft) { ... canvas.drawRect(left, top, left + length, bottom, p); } if (drawRight) { ... canvas.drawRect(right - length, top, right, bottom, p); } ... // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); } ...}
上面的源码注释写的很清晰,通过查看后我们了解到View的绘制共分为如下六步
绘制背景
如果需要,保存图层信息
绘制View的内容
如果View有子View,绘制View的子View
如果需要,绘制View的边缘(如阴影等)
绘制View的装饰(如滚动条等)
其中以上六步,第二步和第五步并不是必须的,所以我们只需重点分析其他四步即可。
绘制背景
绘制背景调用了drawBackground(Canvas)
方法
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... private void drawBackground(Canvas canvas) { // 获取背景 drawable final Drawable background = mBackground; if (background == null) { return; } // 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界 setBackgroundBounds(); ... // 获取 mScrollX 和 mScrollY值 final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { // 如果 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移 canvas.translate(scrollX, scrollY); // 调用 Drawable 的 draw 方法绘制背景 background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } ...}
绘制内容
绘制内容调用了View#onDraw(Canvas)
方法,由于View的内容各不相同,所以该方法是一个空实现,需要由子类去实现
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... protected void onDraw(Canvas canvas) { } ...}
绘制子View
绘制子View调用了View#dispatchDraw(Canvas)
方法,该方法同样是一个空实现
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... protected void dispatchDraw(Canvas canvas) { } ...}
当只有包含子View的时候,才会去重写它,ViewGroup不正好符合条件吗? 来看下ViewGroup对该方法的实现吧
package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... protected void dispatchDraw(Canvas canvas) { ... final int childrenCount = mChildrenCount; ... for (int i = 0; i < childrenCount; i++) { ... if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) { more |= drawChild(canvas, transientChild, drawingTime); } ... } } ...}
ViewGroup#dispatchDraw(Canvas)
方法的代码比较多,只分析重点,遍历了所有的子View并调用了ViewGroup#drawChild(Canvas, View, long)
方法
package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager { ... protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } ...}
该方法最终还是调用了子View的draw(Canvas)
方法。
由于ViewGroup已经为我们实现了该方法,所以我们一般都不需要重写该方法。
绘制装饰
绘制装饰调用了View#onDrawForeground(Canvas)
方法,源码如下
package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; if (foreground != null) { if (mForegroundInfo.mBoundsChanged) { mForegroundInfo.mBoundsChanged = false; final Rect selfBounds = mForegroundInfo.mSelfBounds; final Rect overlayBounds = mForegroundInfo.mOverlayBounds; if (mForegroundInfo.mInsidePadding) { selfBounds.set(0, 0, getWidth(), getHeight()); } else { selfBounds.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); } final int ld = getLayoutDirection(); Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); foreground.setBounds(overlayBounds); } foreground.draw(canvas); } } ...}
该方法默认实现是绘制了滚动指示器、滚动条、和前景。
参考链接:
1. Android View 的绘制流程
2. 自定义控件三部曲视图篇(一)——测量与布局
3. Android 从0开始自定义控件之深入理解 MeasureSpec (六)
4. Android 从0开始自定义控件之 View 的 measure 过程(七)
5. Andriod 从0开始自定义控件之 View 的 layout 过程 (八)
6. Android 从0开始自定义控件之 View 的 draw 过程 (九)
- Android自定义控件 —— 三大流程
- 三大自定义控件
- Android学习笔记三—自定义控件
- Android自定义view之measure、layout、draw三大流程
- android 自定义View研究(三) — 自定义组合控件
- Android自定义控件(三)——有弹性的ListView
- Android自定义View示例(三)—滑动控件
- Android自定义控件系列(三)—底部菜单(上)
- Android控件架构与自定义控件详解(三)——自定义ViewGroup
- android 自定义ViewAnimator文字轮播广告控件——自定义控件学习(三)
- 自定义控件(三) 源码分析measure流程
- Android自定义控件之自定义View(三)——自定义水波纹
- 【Android自定义控件】Android自定义虚线<三>
- Android学习自定义View(三)——自绘控件和组合控件
- Android自定义控件(三)继承控件
- Android 控件架构与自定义控件(三)
- android自定义控件(三) 自定义属性
- android自定义控件(三) 自定义属性
- ZOJ 2849【瞎暴力的搜索】
- jQuery中一些必须要知道的知识点总结--20个(下)
- spring3+quartz1集群配置、分布式集群配置
- LeetCode-Easy部分中标签为Array#119 : Pascal’s Triangle II
- LintCode-翻转一个链表
- Android自定义控件 —— 三大流程
- 【Json】Json数据格式初探
- intellij设置UTF-8
- A + B Problem II
- php简单的接口
- Java反射机制的简单应用
- Java自动装箱与拆箱
- 高德地图 室内地图
- 【Java】基础知识巩固(char和String)&&示例(一)