天天看點

selector和自定義顯示狀态

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);  
            }  
        }  
    }