StateListDrawable添加默认状态导致其他状态失效

651 阅读3分钟

StateListDrawable如果默认状态添加到其他状态的前面,会倒是其他状态无法正常显示

有时由于使用<selector></selector>无法满足我们的需求时,我们可以使用动态创建StateListDrawable对象,通过添加不同的状态来实现,比如我们需要在选中状态下展示一张GIF图,在未选中状态下展示一张png图片,这时通过动态创建StateListDrawable对象来实现就比较合适。

在创建完StateListDrawable对象后,我们会通过addState方法来添加对应状态

StateListDrawable stateListDrawable = new StateListDrawable();
//添加选中状态
stateListDrawable.addState(new int[]{android.R.attr.state_checked}, selectedDrawable);
//添加未选中状态
stateListDrawable.addState(new int[]{-android.R.attr.state_checked}, unselectedDrawable);
//添加默认状态
stateListDrawable.addState(new int[0], unselectedDrawable);

如果默认状态添加在最后,是没有问题的,如果默认状态添加到了其他状态的前面,那么其他的状态就无法展示

原因在于在匹配当前状态时,会对StateListDrawable的状态数组进行遍历,判断状态数组中是否含有当前状态,但是如果状态数组长度为0时,系统会直接判定它为当前状态,具体看如下代码

1.在每次StateListDrawable关联的view的状态发生改变时,会回调onStateChange方法来更新drawable,在此方法中会调用mStateListState.indexOfStateSet(StateSet.WILD_CARD)方法来匹配进行状态匹配

// >> android.graphics.drawable.StateListDrawable

//在每次StateListDrawable关联的view的状态发生改变时,会回调onStateChange方法来更新drawable
@Override
protected boolean onStateChange(int[] stateSet) {
    final boolean changed = super.onStateChange(stateSet);
     //stateSet 代表当前view的状态    mStateListState代表StateListDrawable包含的所有状态
     //取出当前StateListDrawable应该展示的状态的id
    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;
}

StateListDrawable 以二维数组的方式进行选中,未选中等状态的保存,在匹配时我们会遍历二维数组,取出每个状态与当前view的状态进行匹配,如果匹配成功,返回当前应该展示的状态的角标

// >> android.graphics.drawable.StateListDrawable.StateListState

//获取当前StateListDrawable应该展示的状态的id
int indexOfStateSet(int[] stateSet) {
    //StateListDrawable 以二维数组的方式进行选中,未选中等状态的保存
    final int[][] stateSets = mStateSets;
    final int N = getChildCount();
    for (int i = 0; i < N; i++) {
        //遍历StateListDrawable的状态数组,判断是否匹配
        if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
            return i;
        }
    }
    return -1;
}

遍历每个状态包含的状态数组,跟View当前的状态进行匹配,但是由于默认状态时添加的数组的长度就是为0,所以不会走遍历逻辑,而是直接 return true,这也就解释了为什么默认状态如果不放在最后会导致其他状态无法显示,因为还没有遍历到其他状态

// >> android.util.StateSet
public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) {
    if (stateSet == null) {
        return (stateSpec == null || isWildCard(stateSpec));
    }
    int stateSpecSize = stateSpec.length;
    int stateSetSize = stateSet.length;
    //遍历取出匹配状态,注意,如果stateSpec数组长度为0时是不会进行此循环,
    //而添加默认状态时添加的数组的长度就是为0,所以会直接走最后一步,return true
    for (int i = 0; i < stateSpecSize; i++) {
        int stateSpecState = stateSpec[i];
        if (stateSpecState == 0) {
            // We've reached the end of the cases to match against.
            return true;
        }
        final boolean mustMatch;
        if (stateSpecState > 0) {
            mustMatch = true;
        } else {
            // We use negative values to indicate must-NOT-match states.
            mustMatch = false;
            stateSpecState = -stateSpecState;
        }
        boolean found = false;
        for (int j = 0; j < stateSetSize; j++) {
            final int state = stateSet[j];
            if (state == 0) {
                // We've reached the end of states to match.
                if (mustMatch) {
                    // We didn't find this must-match state.
                    return false;
                } else {
                    // Continue checking other must-not-match states.
                    break;
                }
            }
            if (state == stateSpecState) {
                if (mustMatch) {
                    found = true;
                    // Continue checking other other must-match states.
                    break;
                } else {
                    // Any match of a must-not-match state returns false.
                    return false;
                }
            }
        }
        if (mustMatch && !found) {
            // We've reached the end of states to match and we didn't
            // find a must-match state.
            return false;
        }
    }
    //直接返回了true
    return true;
}

所以我们在添加默认状态时,要么将其添加到末尾,要么就不添加默认状态,如果我们的view的状态确定的话,我们是可以不添加默认状态的,比如RadioButton 它只有选中和未选中两种状态,那么此时我们可以不为其添加默认状态