在定義ListView的Selector時候,有個drawSelectorOnTop的屬性,若是drawSelectorOnTop爲true的話,Selector的效果是畫在List Item的上面(Selector是蓋住了ListView的文字或者圖片),即Foreground前景。若是drawSelectorOnTop爲false的話,Selector的效果是畫在List Item的下面,即Background背景。因爲項目中剛好須要自定義View,須要實現此效果。 java
本文借ListView的代碼來剖析一下, android
ListView完成此部分功能在frameworks\base\core\java\android\widget\AbsListView.java文件中。 canvas
用mSelector即ListView要畫的Selector(資源文件),而mSelectorRect則是想要畫的區域。 ide
/** * Indicates whether the list selector should be drawn on top of the children or behind */ boolean mDrawSelectorOnTop = false; 決定畫前景仍是背景 /** * The drawable used to draw the selector */ Drawable mSelector; ListView用中來顯示Selector的Drawable,即ListSelector對應的XML文件 /** * The current position of the selector in the list. */ int mSelectorPosition = INVALID_POSITION; /** * Defines the selector's location and dimension at drawing time */ Rect mSelectorRect = new Rect(); 用來畫Selector的區域,即Selector畫的位置
AbsListView中構造方法中有獲取selector post
Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector); if (d != null) { setSelector(d); } //默認爲false,畫的是背景 mDrawSelectorOnTop = a.getBoolean( com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);下面看一下setSelector是如何實現的
/** * Controls whether the selection highlight drawable should be drawn on top of the item or * behind it. * * @param onTop If true, the selector will be drawn on the item it is highlighting. The default * is false. * * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop */ public void setDrawSelectorOnTop(boolean onTop) { //提供是否畫前景或者背景的接口 mDrawSelectorOnTop = onTop; } /** * Set a Drawable that should be used to highlight the currently selected item. * * @param resID A Drawable resource to use as the selection highlight. * * @attr ref android.R.styleable#AbsListView_listSelector */ public void setSelector(int resID) { setSelector(getResources().getDrawable(resID)); 設置listSelector的XML文件 } public void setSelector(Drawable sel) { if (mSelector != null) { mSelector.setCallback(null); unscheduleDrawable(mSelector); } mSelector = sel; Rect padding = new Rect(); sel.getPadding(padding); mSelectionLeftPadding = padding.left; mSelectionTopPadding = padding.top; mSelectionRightPadding = padding.right; mSelectionBottomPadding = padding.bottom; sel.setCallback(this); //須要給Selector設置Callback updateSelectorState(); } /** * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the * selection in the list. * * @return the drawable used to display the selector */ public Drawable getSelector() { return mSelector; } void updateSelectorState() { if (mSelector != null) { if (shouldShowSelector()) { mSelector.setState(getDrawableState());//更新Selector的狀態 } else { mSelector.setState(StateSet.NOTHING); } } }
這樣就將Selector設置給ListView了,而且更新了drawable的狀態。 動畫
接下來咱們再看一下Android是如何將drawable畫到ListView的Item上的。 this
在AbsListView中有個onTouchEvent的方法用來處理Touch事件,其中有一段代碼就是肯定Selector要畫的區域。 spa
if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { final Handler handler = getHandler(); if (handler != null) { handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap : mPendingCheckForLongPress); } mLayoutMode = LAYOUT_NORMAL; if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { mTouchMode = TOUCH_MODE_TAP; setSelectedPositionInt(mMotionPosition); layoutChildren(); child.setPressed(true);//設置List Item狀態爲 pressed positionSelector(mMotionPosition, child);//肯定畫Selector的區域 setPressed(true); //設置ListView 的狀態爲pressed if (mSelector != null) { Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { ((TransitionDrawable) d).resetTransition(); } } if (mTouchModeReset != null) { removeCallbacks(mTouchModeReset); } mTouchModeReset = new Runnable() { @Override public void run() { mTouchMode = TOUCH_MODE_REST; child.setPressed(false); setPressed(false); if (!mDataChanged) { performClick.run(); } } }; postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration()); } else { mTouchMode = TOUCH_MODE_REST; updateSelectorState(); } return true; } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { performClick.run(); } }
接下來看看positionSelector的實現, rest
void positionSelector(int position, View sel) { if (position != INVALID_POSITION) { mSelectorPosition = position; } //設置Selector的區域爲List Item View的邊界 final Rect selectorRect = mSelectorRect; selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); if (sel instanceof SelectionBoundsAdjuster) { ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect); } positionSelector(selectorRect.left, selectorRect.top, selectorRect.right, selectorRect.bottom); final boolean isChildViewEnabled = mIsChildViewEnabled; if (sel.isEnabled() != isChildViewEnabled) { mIsChildViewEnabled = !isChildViewEnabled; if (getSelectedItemPosition() != INVALID_POSITION) { refreshDrawableState();//根據View狀態更新drawable的狀態 } } } private void positionSelector(int l, int t, int r, int b) { mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r + mSelectionRightPadding, b + mSelectionBottomPadding); }
好了如今已經決定了將selector畫在哪裏,Selector的狀態也已經更新OK。 code
還差一步沒有作,那就是究竟是將其怎麼畫上面的呢?
答案就在AbsListView.java裏的dispatchDraw方法裏面。
@Override protected void dispatchDraw(Canvas canvas) { int saveCount = 0; final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { saveCount = canvas.save(); final int scrollX = mScrollX; final int scrollY = mScrollY; canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, scrollX + mRight - mLeft - mPaddingRight, scrollY + mBottom - mTop - mPaddingBottom); mGroupFlags &= ~CLIP_TO_PADDING_MASK; } final boolean drawSelectorOnTop = mDrawSelectorOnTop; if (!drawSelectorOnTop) { //將Selector畫爲背景 drawSelector(canvas); } super.dispatchDraw(canvas);// 用Canvas畫ListView if (drawSelectorOnTop) { //將Selector畫爲前景 drawSelector(canvas); } if (clipToPadding) { canvas.restoreToCount(saveCount); mGroupFlags |= CLIP_TO_PADDING_MASK; } } private void drawSelector(Canvas canvas) { if (!mSelectorRect.isEmpty()) { final Drawable selector = mSelector; selector.setBounds(mSelectorRect);//設置drawable畫的區域 selector.draw(canvas); //使用canvas將drawable畫上去 } }
看到這裏,想必你們都已經明白如何畫前景和背景了吧。在dispatchDraw以前調用就是畫前景,在dispatchDraw以後調用就是畫背景。
另外補充一下,本文並無介紹動畫部分,有興趣的能夠本身研究下。
總結一下,實現這個功能須要有三個步驟:
1.設置Selector,並更新狀態(初始化時候)
2.肯定Selector畫的區域,設置View的狀態,根據View狀態,更新Selector的狀態(通常是對Event的處理方法中)
3.使用Canvas在dispatchDraw中,將Selector畫上去,畫Drawable的時候須要先設置區域,再調用drawable的draw方法。
後面我再將View如何畫背景和前景補上,今天就先到這裏吧。