View
This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for /widgets/, which are used to create interactive UI components (buttons, text fields, etc.).
Overview
首先是View這個類的繼承層次:
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource
可以看到是一個普通類兄旬,實(shí)現(xiàn)了3個接口。
View.java文件代碼共有27000多行,乍一看很嚇人殃恒,但其中注釋也占了很大部分氛堕,仔細(xì)看來主要分成以下方面
構(gòu)造函數(shù)
繪制相關(guān)
各種getter/setter
事件處理相關(guān)
這篇首先看看構(gòu)造函數(shù)以及繪制相關(guān)的源碼
構(gòu)造函數(shù)
View這個類的構(gòu)造函數(shù)一共有四個,方法簽名分別是:
public View(Context context)
public View(Context context, @Nullable AttributeSet attrs)
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)
其中第2第3個方法的實(shí)現(xiàn)都只是調(diào)用了第4個方法舔庶,而第4個方法又會首先調(diào)用第1個方法猪狈,因此先看下第1個方法箱沦,即單參數(shù)的構(gòu)造方法實(shí)現(xiàn):
public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
// Set some flags defaults
mPrivateFlags2 =
(LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
(TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
(PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
(TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
(PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
(IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
mRenderNode = RenderNode.create(getClass().getName(), this);
if (!sCompatibilityDone && context != null) {
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
// Older apps may need this compatibility hack for measurement.
sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
// Older apps expect onMeasure() to always be called on a layout pass, regardless
// of whether a layout was requested on that View.
sIgnoreMeasureCache = targetSdkVersion < Build.VERSION_CODES.KITKAT;
Canvas.sCompatibilityRestore = targetSdkVersion < Build.VERSION_CODES.M;
Canvas.sCompatibilitySetBitmap = targetSdkVersion < Build.VERSION_CODES.O;
Canvas.setCompatibilityVersion(targetSdkVersion);
// In M and newer, our widgets can pass a “hint” value in the size
// for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
// know what the expected parent size is going to be, so e.g. list items can size
// themselves at 1/3 the size of their container. It breaks older apps though,
// specifically apps that use some popular open source libraries.
sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M;
// Old versions of the platform would give different results from
// LinearLayout measurement passes using EXACTLY and non-EXACTLY
// modes, so we always need to run an additional EXACTLY pass.
sAlwaysRemeasureExactly = targetSdkVersion <= Build.VERSION_CODES.M;
// Prior to N, layout params could change without requiring a
// subsequent call to setLayoutParams() and they would usually
// work. Partial layout breaks this assumption.
sLayoutParamsAlwaysChanged = targetSdkVersion <= Build.VERSION_CODES.M;
// Prior to N, TextureView would silently ignore calls to setBackground/setForeground.
// On N+, we throw, but that breaks compatibility with apps that use these methods.
sTextureViewIgnoresDrawableSetters = targetSdkVersion <= Build.VERSION_CODES.M;
// Prior to N, we would drop margins in LayoutParam conversions. The fix triggers bugs
// in apps so we target check it to avoid breaking existing apps.
sPreserveMarginParamsInLayoutParamConversion =
targetSdkVersion >= Build.VERSION_CODES.N;
sCascadedDragDrop = targetSdkVersion < Build.VERSION_CODES.N;
sHasFocusableExcludeAutoFocusable = targetSdkVersion < Build.VERSION_CODES.O;
sAutoFocusableOffUIThreadWontNotifyParents = targetSdkVersion < Build.VERSION_CODES.O;
sUseDefaultFocusHighlight = context.getResources().getBoolean(
com.android.internal.R.bool.config_useDefaultFocusHighlight);
sThrowOnInvalidFloatProperties = targetSdkVersion >= Build.VERSION_CODES.P;
sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P;
sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P;
sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
sCompatibilityDone = true;
}
}
概括來說,主要做了以下幾件事:
對View可訪問的關(guān)鍵變量進(jìn)行賦值:mContext和mResources等
根據(jù)系統(tǒng)版本對其成員變量進(jìn)行賦值雇庙,這里的變量大多數(shù)都是boolean型
然后是第4個4參數(shù)的構(gòu)造函數(shù)實(shí)現(xiàn):
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
if (mDebugViewAttributes) {
saveAttributeData(attrs, a);
}
Drawable background = null;
int leftPadding = -1;
int topPadding = -1;
int rightPadding = -1;
int bottomPadding = -1;
int startPadding = UNDEFINED_PADDING;
int endPadding = UNDEFINED_PADDING;
int padding = -1;
int paddingHorizontal = -1;
int paddingVertical = -1;
int viewFlagValues = 0;
int viewFlagMasks = 0;
boolean setScrollContainer = false;
int x = 0;
int y = 0;
float tx = 0;
float ty = 0;
float tz = 0;
float elevation = 0;
float rotation = 0;
float rotationX = 0;
float rotationY = 0;
float sx = 1f;
float sy = 1f;
boolean transformSet = false;
int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
int overScrollMode = mOverScrollMode;
boolean initializeScrollbars = false;
boolean initializeScrollIndicators = false;
boolean startPaddingDefined = false;
boolean endPaddingDefined = false;
boolean leftPaddingDefined = false;
boolean rightPaddingDefined = false;
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
// Set default values.
viewFlagValues |= FOCUSABLE_AUTO;
viewFlagMasks |= FOCUSABLE_AUTO;
final int N = a.getIndexCount();
for (int I = 0; I < N; I++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
case com.android.internal.R.styleable.View_padding:
padding = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = padding;
mUserPaddingRightInitial = padding;
leftPaddingDefined = true;
rightPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingHorizontal:
paddingHorizontal = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = paddingHorizontal;
mUserPaddingRightInitial = paddingHorizontal;
leftPaddingDefined = true;
rightPaddingDefined = true;
break;
// 此處省略約400行
}
}
setOverScrollMode(overScrollMode);
// Cache start/end user padding as we cannot fully resolve padding here (we don’t have yet
// the resolved layout direction). Those cached values will be used later during padding
// resolution.
mUserPaddingStart = startPadding;
mUserPaddingEnd = endPadding;
if (background != null) {
setBackground(background);
}
// setBackground above will record that padding is currently provided by the background.
// If we have padding specified via xml, record that here instead and use it.
mLeftPaddingDefined = leftPaddingDefined;
mRightPaddingDefined = rightPaddingDefined;
if (padding >= 0) {
leftPadding = padding;
topPadding = padding;
rightPadding = padding;
bottomPadding = padding;
mUserPaddingLeftInitial = padding;
mUserPaddingRightInitial = padding;
} else {
if (paddingHorizontal >= 0) {
leftPadding = paddingHorizontal;
rightPadding = paddingHorizontal;
mUserPaddingLeftInitial = paddingHorizontal;
mUserPaddingRightInitial = paddingHorizontal;
}
if (paddingVertical >= 0) {
topPadding = paddingVertical;
bottomPadding = paddingVertical;
}
}
if (isRtlCompatibilityMode()) {
if (!mLeftPaddingDefined && startPaddingDefined) {
leftPadding = startPadding;
}
mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial;
if (!mRightPaddingDefined && endPaddingDefined) {
rightPadding = endPadding;
}
mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial;
} else {
final boolean hasRelativePadding = startPaddingDefined || endPaddingDefined;
if (mLeftPaddingDefined && !hasRelativePadding) {
mUserPaddingLeftInitial = leftPadding;
}
if (mRightPaddingDefined && !hasRelativePadding) {
mUserPaddingRightInitial = rightPadding;
}
}
internalSetPadding(
mUserPaddingLeftInitial,
topPadding >= 0 ? topPadding : mPaddingTop,
mUserPaddingRightInitial,
bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
if (viewFlagMasks != 0) {
setFlags(viewFlagValues, viewFlagMasks);
}
if (initializeScrollbars) {
initializeScrollbarsInternal(a);
}
if (initializeScrollIndicators) {
initializeScrollIndicatorsInternal();
}
a.recycle();
// Needs to be called after mViewFlags is set
if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
recomputePadding();
}
if (x != 0 || y != 0) {
scrollTo(x, y);
}
if (transformSet) {
setTranslationX(tx);
setTranslationY(ty);
setTranslationZ(tz);
setElevation(elevation);
setRotation(rotation);
setRotationX(rotationX);
setRotationY(rotationY);
setScaleX(sx);
setScaleY(sy);
}
if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
setScrollContainer(true);
}
computeOpaqueFlags();
}
概括來說谓形,主要做了兩件事情:
從xml中將屬性分析出來,然后賦值到對應(yīng)的變量中疆前,函數(shù)的70%的代碼都在做這件事情
對Padding寒跳、transform等屬性進(jìn)行賦值
繪制
一個View的繪制,需要經(jīng)歷3個階段:
需要知道這個View所占的區(qū)域有多大
需要知道這個View所處的位置是哪里
需要知道這個View外觀長什么樣
以上3個階段分別對應(yīng)measure(),layout(),draw()函數(shù)
measure()
首先來看measure函數(shù)的源碼:
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
}
閱讀一個函數(shù)竹椒,從讀懂函數(shù)簽名開始:從measure()的函數(shù)簽名中可以得到以下信息:
final函數(shù)童太,函數(shù)被關(guān)鍵字final標(biāo)記,意味著不可被子類重寫胸完,但仔細(xì)看函數(shù)體內(nèi)书释,大量的代碼都是與設(shè)置boolean標(biāo)志位相關(guān)的,并沒有進(jìn)行measure赊窥,真正measure的地方在上述38行的onMeasure()方法中征冷,這也是自定義View時進(jìn)行測量操作的地方
入?yún)idthMeasureSpec和heightMeasureSpec,一個View需要確定自己有多大誓琼,不能為所欲為,需要知道兩點(diǎn)信息:/父容器對其大小的限制/和/父容器允許的大小肴捉,/入?yún)⒌膚idthMeasureSpec和heightMeasureSpec分別從寬和高兩個維度用1個變量給出了上述2點(diǎn)信息腹侣,下面具體看下是如何做到的
MeasureSpec
在View的onMeasure()方法中,會調(diào)用getDefaultSize(int size, int measureSpec)方法來獲得這個View默認(rèn)的size齿穗,看下這個方法的源碼即可了解MeasureSpec的原理
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;
}
上述代碼中關(guān)鍵的兩行傲隶,第3行MeasureSpec.getMode()得到的就是父容器對其大小的限制,而MeasureSpec.getSize()得到的是父容器允許/建議的大小
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
getMode()和getSize()方法的實(shí)現(xiàn)非常簡潔窃页,這里的MODE_MASK取值為0X3<<30跺株,即32位,高2位為11脖卖,余下為0乒省,因?yàn)樯厦鎯蓚€方法的含義分別是:getMode為取measureSpec的高2位,getSize為取measureSpec的低30位畦木,通過這個方式袖扛,1個32位的變量即可存儲模式和尺寸兩個信息
在getDefaultSize()函數(shù)中也能看到,得到的specMode有3種取值:
UNSPECIFIED:父容器對該View的大小沒有限制
AL_MOST:最多不能超過給出的specSize
EXACTLTY:取值要為給出的specSize
meMeasure的最終目標(biāo),是在onMeasure()方法中蛆封,將測量得到的寬高唇礁,賦值給View的寬高,用于后續(xù)使用
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
layout()
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i I 0; i I numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
final boolean wasLayoutValid = isLayoutValid();
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if (!wasLayoutValid && isFocused()) {
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
if (canTakeFocus()) {
// We have a robust focus, so parents should no longer be wanting focus.
clearParentsWantFocus();
} else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
// This is a weird case. Most-likely the user, rather than ViewRootImpl, called
// layout. In this case, there's’no guarantee that parent layouts will be evaluated
// and thus the safest action is to clear focus here.
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
clearParentsWantFocus();
} else if (!hasParentWantsFocus()) {
// original requestFocus was likely on this view directly, so just clear focus
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
}
// otherwise, we let parents handle re-assigning focus during their layout passes.
} else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
View focused = findFocus();
if (focused != null) {
// Try to restore focus as close as possible to our starting focus.
if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
// Give up and clear focus once we’v’ reached the top-most parent which wants
// focus.
focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
}
}
}
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
layout()函數(shù)惨篱,作用是確定View的坐標(biāo)盏筐,通過left/top/right/bottom四個值來確定,有2點(diǎn)值得記錄的:
進(jìn)行l(wèi)ayout()時砸讳,會檢查是否已經(jīng)進(jìn)行了onMeasure()琢融,確保layout()在onMeasure(0之后
和measure()與onMeasure()類似,真正進(jìn)行l(wèi)ayout操作的绣夺,在onLayout()方法里吏奸,也是自定義View確定布局的地方,在View.java里陶耍,onLayout()函數(shù)是一個空實(shí)現(xiàn)
draw()
確定了View的大小和位置之后奋蔚,就可以進(jìn)行實(shí)際的繪制操作了
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we’re done…
return;
}
……
}
draw()過程,源碼的注釋比較清晰烈钞,主要有7個步驟:
繪制背景
如果有需要泊碑,保存圖層信息,用于淡出準(zhǔn)備
繪制內(nèi)容毯欣,這一步最關(guān)鍵馒过,也和measure()/layout()相似,這里會調(diào)用onDraw()來繪制真正的內(nèi)容
繪制子view
如果有需要酗钞,繪制類似陰影效果
繪制View的裝飾腹忽,如滾動條
繪制焦點(diǎn)效果