selector原理簡述過程:
1.drawable = new StateListDrawable();//android預設使用selector産生的Drawable對象類型是StateListDrawable
2.drawable.inflate(r, parser, attrs);//解析xml檔案把各種狀态儲存進入drawable對象
3.setPressed(boolean pressed);//當View被點中調用,使用者點選動作之後就是view産生狀态的地方,改變一個現有狀态參數為點中
4.onCreateDrawableState(0);//View生成狀态集傳遞給drawable對象,參數不為0表示有自定義狀态
5.onStateChange(stateSet);//drawable通過這個方法解析出一個index可以找到之前xml檔案中存入對應的圖檔
6.invalidateSelf()//drawable通過這個方法重新繪制,其中包含一個Drawable.Callback接口
7.invalidateDrawable()//Drawable.Callback接口會調用這個方法進行重繪,在這裡這個方法被view重寫,調用了view的draw方法重繪
結論:
1.我們的Drawable類對象必須将View設定為回調對象,實作invalidateDrawable(Drawable drawable),讓view負責繪制
2.Drawable對象可以解析xml檔案并且儲存各種狀态和對應的圖檔
3.不能直接給Drawable指定狀态,要通過onCreateDrawableState(0)傳回的int[],Drawable對象從中可以解析出一個index來找到對應圖檔,也就是說Drawable的狀态是和View相關聯,狀态值是View傳遞給它,而View的狀态值是由一系列不同的操作,例如點選,選中産生。
4.由上所述,自定義狀态和顯示對應圖檔的步驟:
a.自定義xml檔案selector,自定義狀态對應圖檔,傳遞給drawable對象
b.在View中設定一個表明狀态的參數mState
c.在View中類似onclick的操作響應方法中改變mState的狀态
d.在View的onCreateDrawableState方法中,根據mState的狀态判斷是不是要添加額外的狀态進入狀态數組傳回給Drawable對象
e.Drawable對象會在onStateChange方法中根據傳入的狀态和selector中的參數和對應的圖檔根據一些規則指定一個正确的圖檔進行顯示
源碼分析:
1.當按鈕或其他元件狀态改變時,怎麼實作的相應狀态圖檔或顔色的顯示呢。首先Drawable對象會通過xml載入selector的資訊,這樣drawable對象就有了不同的狀态和圖檔,由于Drawable是抽象類,為了響應不同的狀态需要實作Drawable類
以下代碼是當用selector作為Drawable傳入view中, android預設定義了StateListDrawable類來響應和儲存狀态
[java] view plaincopyprint?
public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
Drawable drawable;
final String name = parser.getName();
if (name.equals("selector")) {
drawable = new StateListDrawable();//看下這裡
} else if (name.equals("level-list")) {
drawable = new LevelListDrawable();
} else if (name.equals("layer-list")) {
drawable = new LayerDrawable();
} else if (name.equals("transition")) {
drawable = new TransitionDrawable();
} else if (name.equals("color")) {
drawable = new ColorDrawable();
} else if (name.equals("shape")) {
drawable = new GradientDrawable();
} else if (name.equals("scale")) {
drawable = new ScaleDrawable();
} else if (name.equals("clip")) {
drawable = new ClipDrawable();
} else if (name.equals("rotate")) {
drawable = new RotateDrawable();
} else if (name.equals("animated-rotate")) {
drawable = new AnimatedRotateDrawable();
} else if (name.equals("animation-list")) {
drawable = new AnimationDrawable();
} else if (name.equals("inset")) {
drawable = new InsetDrawable();
} else if (name.equals("bitmap")) {
drawable = new BitmapDrawable();
if (r != null) {
((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
}
} else if (name.equals("nine-patch")) {
drawable = new NinePatchDrawable();
if (r != null) {
((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
}
} else {
throw new XmlPullParserException(parser.getPositionDescription() +
": invalid drawable tag " + name);
}
drawable.inflate(r, parser, attrs);
return drawable;
}
2.View會在setPress後産生按下狀态
[java] view plaincopyprint?
public void setPressed(boolean pressed) {
if (pressed) {
mPrivateFlags |= PRESSED;//PRESSED标志位置1
} else {
mPrivateFlags &= ~PRESSED;//PRESSED标志位置0
}
refreshDrawableState();//重新整理Drawable狀态
dispatchSetPressed(pressed);//分發setPress事件,Viewgroup重寫了該方法,實作向子View的分發。
}
3.要實作圖檔的改變其實是改變View中的Drawable對象的狀态,當view的狀态改變,需要通知Drawable對象
[java] view plaincopyprint?
public void refreshDrawableState() {
mPrivateFlags |= DRAWABLE_STATE_DIRTY;//DRAWABLE_STATE_DIRTY标志位是幹什麼的呢,往下看就知道了
drawableStateChanged();//改變drawableState
ViewParent parent = mParent;
if (parent != null) {
parent.childDrawableStateChanged(this);//如果有父VIew的話,通知父View。
}
}
[java] view plaincopyprint?
protected void drawableStateChanged() {
Drawable d = mBGDrawable;//我們設定的selector對應的Drawable,也就是StateListDrawable
if (d != null && d.isStateful()) {//StateListDrawable重寫了該方法,傳回true。
d.setState(getDrawableState());
}
}
3.每個View通過onCreateDrawableState(0)來傳回他目前的狀态給Drawable對象
[java] view plaincopyprint?
public final int[] getDrawableState() {
if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
return mDrawableState;
} else {
mDrawableState = onCreateDrawableState(0);
mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}
//這是onCreateDrawableState(0)的源碼,主要作用是确定windows的狀态,view是否能被顯示,是不是焦點或按下的狀态,然後把所有的狀态産生一個int[]值傳回,參數extraSpace是表明額外的狀态,是以可以自定義狀态,例如CompoundButton
protected int[] onCreateDrawableState(int extraSpace) {
if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
mParent instanceof View) {
//傳回父類的狀态
return ((View) mParent).onCreateDrawableState(extraSpace);
}
int[] drawableState;
int privateFlags = mPrivateFlags;
//是否PRESSED狀态
int viewStateIndex = (((privateFlags & PRESSED) != 0) ? 1 : 0);
//是否ENABLED狀态
viewStateIndex = (viewStateIndex << 1)
+ (((mViewFlags & ENABLED_MASK) == ENABLED) ? 1 : 0);
//是否焦點狀态
viewStateIndex = (viewStateIndex << 1) + (isFocused() ? 1 : 0);
//是否選擇狀态
viewStateIndex = (viewStateIndex << 1)
+ (((privateFlags & SELECTED) != 0) ? 1 : 0);
//view所在window是否獲得焦點
final boolean hasWindowFocus = hasWindowFocus();
viewStateIndex = (viewStateIndex << 1) + (hasWindowFocus ? 1 : 0);
//通過以上的判斷産生一個索引viewStateIndex,在VIEW_STATE_SETS中傳回一個狀态數組(是以VIEW_STATE_SETS是int[][]類型)
drawableState = VIEW_STATE_SETS[viewStateIndex];
//noinspection ConstantIfStatement
if (false) {
Log.i("View", "drawableStateIndex=" + viewStateIndex);
Log.i("View", toString()
+ " pressed=" + ((privateFlags & PRESSED) != 0)
+ " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
+ " fo=" + hasFocus()
+ " sl=" + ((privateFlags & SELECTED) != 0)
+ " wf=" + hasWindowFocus
+ ": " + Arrays.toString(drawableState));
}
if (extraSpace == 0) {
return drawableState;
}
//如果有額外的自定義狀态就把它添加到狀态數組中
final int[] fullState;
if (drawableState != null) {
fullState = new int[drawableState.length + extraSpace];
System.arraycopy(drawableState, 0, fullState, 0, drawableState.length);
} else {
fullState = new int[extraSpace];
}
return fullState;
}
//這個是CompoundButton對象重寫的onCreateDrawableState方法,把額外的自定義CHECKED_STATE_SET狀态添加進去
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
4.接下來就是Drawable對象需要通過setState接收狀态,同時通過重寫onStateChange方法來找到正确的圖檔并繪制
[java] view plaincopyprint?
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);//StateListDrawable重寫了onStateChange。
}
return false;
}
[java] view plaincopyprint?
@Override
//看StateListDrawable的onStateChange是怎麼實作的,選擇了一個正确的圖檔
protected boolean onStateChange(int[] stateSet) {
int idx = mStateListState.indexOfStateSet(stateSet);//根據狀态集比對一個索引值
if (idx < 0) {
idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
}
if (selectDrawable(idx)) {//根據索引值能找到相應狀态集對應的Drawable對象。
return true;
}
return super.onStateChange(stateSet);
}
[java] view plaincopyprint?
public boolean selectDrawable(int idx)
{
if (idx == mCurIndex) {
return false;
}
if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
Drawable d = mDrawableContainerState.mDrawables[idx];
if (mCurrDrawable != null) {
mCurrDrawable.setVisible(false, false);
}
mCurrDrawable = d;//把目前狀态集對應的Drawable對象指派給mCurrDrawable
mCurIndex = idx;
if (d != null) {
d.setVisible(isVisible(), true);
d.setAlpha(mAlpha);
d.setDither(mDrawableContainerState.mDither);
d.setColorFilter(mColorFilter);
d.setState(getState());
d.setLevel(getLevel());
d.setBounds(getBounds());
}
} else {
if (mCurrDrawable != null) {
mCurrDrawable.setVisible(false, false);
}
mCurrDrawable = null;
mCurIndex = -1;
}
invalidateSelf();//最終調用這個方法。下面看看這個方法。
return true;
}
[java] view plaincopyprint?
public void invalidateSelf()
{
if (mCallback != null) {
mCallback.invalidateDrawable(this);
}
}
5.View實作了Drawable用于繪制的回調接口
[java] view plaincopyprint?
public void setBackgroundDrawable(Drawable d) {
boolean requestLayout = false;
mBackgroundResource = 0;
if (mBGDrawable != null) {
mBGDrawable.setCallback(null);
unscheduleDrawable(mBGDrawable);
}
if (d != null) {
Rect padding = sThreadLocal.get();
if (padding == null) {
padding = new Rect();
sThreadLocal.set(padding);
}
if (d.getPadding(padding)) {
setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
// Compare the minimum sizes of the old Drawable and the new. If there isn't an old or
// if it has a different minimum size, we should layout again
if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() ||
mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) {
requestLayout = true;
}
d.setCallback(this);//這裡設定了CallBack,View實作了Drawable.Callback接口。
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setVisible(getVisibility() == VISIBLE, false);
mBGDrawable = d;
if ((mPrivateFlags & SKIP_DRAW) != 0) {
mPrivateFlags &= ~SKIP_DRAW;
mPrivateFlags |= ONLY_DRAWS_BACKGROUND;
requestLayout = true;
}
} else {
/* Remove the background */
mBGDrawable = null;
if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) {
mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND;
mPrivateFlags |= SKIP_DRAW;
}
requestLayout = true;
}
computeOpaqueFlags();
if (requestLayout) {
requestLayout();
}
mBackgroundSizeChanged = true;
invalidate();
}
[java] view plaincopyprint?
//View中invalidateDrawable的實作
public void invalidateDrawable(Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);//最終會申請失效,導緻重新繪制。
}
}
[java] view plaincopyprint?
//View調用draw繪制
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
final Drawable background = mBGDrawable;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
background.draw(canvas);//最終會調用background的draw()
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}
[java] view plaincopyprint?
//StateListDrawable的父類DrawableContainer的draw()
@Override
public void draw(Canvas canvas) {
if (mCurrDrawable != null) {
mCurrDrawable.draw(canvas);
}
}
[java] view plaincopyprint?
//mCurrDrawable是一個BitmapDrawable類型的對象。看一下BitmapDrawable的draw()
@Override
public void draw(Canvas canvas) {
Bitmap bitmap = mBitmap;
if (bitmap != null) {
final BitmapState state = mBitmapState;
if (mRebuildShader) {
Shader.TileMode tmx = state.mTileModeX;
Shader.TileMode tmy = state.mTileModeY;
if (tmx == null && tmy == null) {
state.mPaint.setShader(null);
} else {
Shader s = new BitmapShader(bitmap,
tmx == null ? Shader.TileMode.CLAMP : tmx,
tmy == null ? Shader.TileMode.CLAMP : tmy);
state.mPaint.setShader(s);
}
mRebuildShader = false;
copyBounds(mDstRect);
}
Shader shader = state.mPaint.getShader();
if (shader == null) {
if (mApplyGravity) {
Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight,
getBounds(), mDstRect);
mApplyGravity = false;
}
canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint);//最終通過canvas繪制這張圖檔。
} else {
if (mApplyGravity) {
mDstRect.set(getBounds());
mApplyGravity = false;
}
canvas.drawRect(mDstRect, state.mPaint);
}
}
}