StateListDrawable严格意义上来讲,它是一个Drawable的容器,它继承自DrawableContainer类,它是怎么工作的呢?
StateListDrawable内部定义了一个DrawableContainerState,这个类里面有一个二维数组int[][] mStateSets用于存储StateListDrawable的各个状态。
StateListDrawable的父类DrawableContainer中定义了一个静态内部类DrawableContainerState,这个类里面定义了一个
Drawable[] mDrawables
,用于存储所有状态的下的Drawable,mStateSets的第一维与mDrawables的一一对应的,mStateSets的第二维存储的是状态数组
View类中,根据当前的state去获取mStateSets中的index,根据这个index得到mDrawables中的对应的drawable,然后绘制到View中。
拿View的setSelected()设置View的选择状态打比方:
public void setSelected(boolean selected) {
//noinspection DoubleNegation
if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) {
mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0);
……
refreshDrawableState();
……
}
}
mPrivateFlags是View用于存储当前状态的变量,它的每个bit代表view相应的状态,setSelected()方法中将改变mPrivateFlags的PFLAG_SELECTED bit位的值,
然后调用refreshDrawableState()->drawableStateChanged()方法对StateListDrawable的状态进行切换:
public void refreshDrawableState() {
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
drawableStateChanged();
……
}
protected void drawableStateChanged() {
//获取当前的state
final int[] state = getDrawableState();
boolean changed = false;
//更新drawable的state
final Drawable bg = mBackground;
if (bg != null && bg.isStateful()) {
changed |= bg.setState(state);
}
……
//更新View
if (changed) {
invalidate();
}
}
这里只要关注getDrawableState()和setState(state)两个方法,
1.getDrawableState()主要做的是根据mPrivateFlags得到当前状态的数组:
public final int[] getDrawableState() {
if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
return mDrawableState;
} else {
//获取当前的状态
mDrawableState = onCreateDrawableState(0);
mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}
调用onCreateDrawableState(),根据mPrivateFlags的值获取当前的状态:
protected int[] onCreateDrawableState(int extraSpace) {
……
int[] drawableState;
int privateFlags = mPrivateFlags;
//view选择状态标志位
if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
……
drawableState = StateSet.get(viewStateIndex);
return drawableState;
}
2.setState(state):
主要看setState()方法,对于StateListDrawable类,setState()为:
public boolean setState(@NonNull final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
StateListDrawable类重写了onStateChange()方法:
@Override
protected boolean onStateChange(int[] stateSet) {
final boolean changed = super.onStateChange(stateSet);
int idx = mStateListState.indexOfStateSet(stateSet);
if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states "
+ Arrays.toString(stateSet) + " found " + idx);
if (idx < 0) {
idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
}
return selectDrawable(idx) || changed;
}
onStateChange()方法中,根据传进来的stateSet,获取到该stateSet在mStateListState中(二维数组mStateSets)的索引,根据这个索引获取当前的drawable:
public boolean selectDrawable(int index) {
if (index == mCurIndex) {
return false;
}
……
if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
final Drawable d = mDrawableContainerState.getChild(index);
mCurrDrawable = d;
mCurIndex = index;
……
}
……
}
这时就得到selected对用的drawble: mCurrDrawable
接着drawableStateChanged()方法中调用 invalidate()方法更新view:
public void draw(Canvas canvas) {
……
if (!dirtyOpaque) {
drawBackground(canvas);
}
……
}
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
……
//画背景
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
上面的background.draw(canvas)即调用了DrawableContainer类中的draw方法,把mCurrDrawable draw到view中:
@Override
public void draw(Canvas canvas) {
if (mCurrDrawable != null) {
mCurrDrawable.draw(canvas);
}
……
}
自定义View中切换StateListDrawable
在自定义view中设置了StateListDrawable或者ColorStateList,在View状态改变的时候,如enabled,selected,pressed等,需要同时切换StateListDrawable和ColorStateList的状态:
需要重写verifyDrawable()和drawableStateChanged()方法,
当view状态改变是,一般会调用方法invalidate(),这时候会调用verifyDrawable()来确认那些drawable要改变状态:
protected boolean verifyDrawable(Drawable who)
{
return who == mSeekBarBgDrawable || super.verifyDrawable(who);
}
drawableStateChanged()根据当前view的状态来切换StateListDrawable和ColorStateList的状态:
@Override
protected void drawableStateChanged()
{
if(mSeekBarBgDrawable == null)
{
super.drawableStateChanged();
return;
}
//改变StateListDrawable的状态
if (mSeekBarBgDrawable != null && mSeekBarBgDrawable.isStateful())
{
mSeekBarBgDrawable.setState(getDrawableState());
}
//改变ColorStateList的状态
if(mAreaColor != null && mAreaColor.isStateful())
{
mPaint.setColor(mAreaColor.getColorForState(getDrawableState(),getResources().getColor(R.color.round_cl1)));
}
super.drawableStateChanged();
}
其中mSeekBarBgDrawable为在xml设置的StateListDrawable:
app:backgrounddrawable="@drawable/seekbar_bg_normal"
seekbar_bg_normal.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true">
<shape android:shape="rectangle" >
<solid android:color="@color/round_cl" />
<corners android:radius="5px"/>
</shape>
</item>
<item android:state_enabled="false">
<shape android:shape="rectangle" >
<solid android:color="@color/round_disable" />
<corners android:radius="5px"/>
</shape>
</item>
</selector>
mSeekBarBgDrawable = typedArray.getDrawable(R.styleable.LaserSensiView_backgrounddrawable);
其中mAreaColor 为在xml设置的ColorStateList:
private ColorStateList mAreaColor;
mAreaColor = typedArray.getColorStateList(R.styleable.LaserSensiView_areaColor);
app:areaColor="@color/seek_fg_color"
seek_fg_color.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/blue_btn_bg_color" />
<item android:state_enabled="true" android:color="@color/round_cl1" />
</selector>