selector和自定义显示状态

本文详细解析了Android中Selector的工作原理及其实现过程。介绍了如何通过XML文件定义不同状态下的Drawable资源,并阐述了View与Drawable间的状态同步机制。此外,还深入分析了StateListDrawable类的具体实现。

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);  
            }  
        }  
    }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Only鱼籽酱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值