Android给一组控件设置StateListDrawable

今天在弄一个小东西的时候用到StateListDrawable,结果遇到一点问题。现在简单总结一下。
问题是这样的,在前面写SegmentView的时候,给里面的各个Item添加背景,想着中间的几个Item(中间的Item都没有圆角)的背景都是一样的,那就用一个StateListDrawable对象就够了吧。结果就出问题了,如果中间有2个或更多的Item,当点击它们的时候背景就乱了,和想象的结果完全不一样。下面我把代码贴出来:

package com.belows.test;

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private StateListDrawable mListDrawable;
    private ColorStateList mColorList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        int selected = android.R.attr.state_selected;
        mListDrawable = new StateListDrawable();
        ColorDrawable selectedDrawable = new ColorDrawable(Color.BLUE);
        ColorDrawable normalDrawable = new ColorDrawable(Color.YELLOW);
        mListDrawable.addState(new int[]{selected}, selectedDrawable);
        mListDrawable.addState(new int[]{-selected}, normalDrawable);

        mColorList = new ColorStateList(new int[][]{{selected},{-selected}},new int[]{Color.WHITE,Color.GRAY});

        setContentView(genRootView());
    }

    private LinearLayout genRootView() {
        final LinearLayout root = new LinearLayout(this);
        root.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dip2px(this, 50)));

        root.addView(genTabView("Tab1"));
        root.addView(genTabView("Tab2"));
        root.addView(genTabView("Tab3"));
        root.addView(genTabView("Tab4"));

        for (int i=0,size=root.getChildCount(); i<size; ++i) {
            root.getChildAt(i).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    selectItem(root,v);
                }
            });
        }
        return root;
    }

    private void selectItem(LinearLayout root, View selectedView) {
        for (int i=0,size=root.getChildCount(); i<size; ++i) {
            root.getChildAt(i).setSelected(false);
        }
        selectedView.setSelected(true);
    }

    private TextView genTabView(String tab) {
        TextView textView = new TextView(this);
        textView.setText(tab);
        textView.setBackgroundDrawable(mListDrawable);
        textView.setTextColor(mColorList);

        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
        lp.weight = 1;
        textView.setLayoutParams(lp);


        return textView;
    }

    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }
}

大家可以看到点击各个Tab后看到的现象非常奇怪,各个Tab背景的转换非常混乱,甚至都没有什么规律可言,不过Text的颜色(textColor)却是按照设想的方式在正常工作,这是怎么一回事呢?
还是到源码里去看看吧,首先看看setSelected(boolean selected)这个函数

public void setSelected(boolean selected) {
        //noinspection DoubleNegation
        if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) {
            mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0);
            if (!selected) resetPressedState();
            invalidate(true);
            refreshDrawableState();
            dispatchSetSelected(selected);
            if (selected) {
                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
            } else {
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
            }
        }
    }

函数一开始就判断设置的select状态是否和View当前的select状态一样,如果是一样的话就不做任何操作了
if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected)
如果不一样,就先设置选中的状态,然后刷新View,再然后调用refreshDrawableState();这个函数,而这个函数又会调用drawableStateChanged(),看一下这个函数做了什么

protected void drawableStateChanged() {
        final int[] state = getDrawableState();

        final Drawable bg = mBackground;
        if (bg != null && bg.isStateful()) {
            bg.setState(state);
        }

        final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (fg != null && fg.isStateful()) {
            fg.setState(state);
        }

        if (mScrollCache != null) {
            final Drawable scrollBar = mScrollCache.scrollBar;
            if (scrollBar != null && scrollBar.isStateful()) {
                scrollBar.setState(state);
            }
        }

        if (mStateListAnimator != null) {
            mStateListAnimator.setState(state);
        }
    }

可以从中看到当我们设置View的selected状态时,View会将这个状态传递给background,foreground等几个Drawable。
在这个例子中,由于设置View的background时用的都是同一个Drawable,当其中的一个Tab设置selected为true时,其backgroundDrawable的state也被设置为true,这时其他的backgroundDrawable的state也跟着改变了,所以就乱了。
从效果图可以看到,当一开始点击第一个Tab时,第四个Tab的背景改变了,而第二、三个没有改变,这又是怎么会事呢?首先我们看一下setBackgroundDrawable这个函数,

@Deprecated
    public void setBackgroundDrawable(Drawable background) {
        ...

            background.setCallback(this);
            ...
        invalidate(true);
    }

我们看到在这个函数里,我们的Drawable的callback是调用这个函数的View,所以当四个View都调用了setBackgroundDrawable之后,这个Drawable的callback就是第四个View了。然后我们再来看一下View的setSelected函数,通过一系列的跳转(setSelected->refreshDrawableState->drawableStateChanged->(StateListDrawable)setState->onStateChange->selectDrawable->invalidateSelf)到了StateListDrawable的invalidateSelf函数,我们来看一下这个函数:

public void invalidateSelf() {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.invalidateDrawable(this);
        }
    }

这里的callback就是我们刚才调用setBackgroundDrawable时设置的callback,也就是第四个View,而View的invalidateDrawable函数又做了什么呢?

public void invalidateDrawable(@NonNull Drawable drawable) {
        if (verifyDrawable(drawable)) {
            final Rect dirty = drawable.getDirtyBounds();
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;

            invalidate(dirty.left + scrollX, dirty.top + scrollY,
                    dirty.right + scrollX, dirty.bottom + scrollY);
            rebuildOutline();
        }
    }

其中getDirtyBounds其实就是Drawable的整个区域(这个我们可以查看Drawable的源码看到,StateListDrawable没有复写这个方法),所以当我们调用setSelect方法的时候其实是被选中的那个View自己在刷新,但是由于Drawable的callback是第四个View,所以第四个View的背景也跟着刷新了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值