您的位置:首页 > 移动开发 > Android开发

Android给一组控件设置StateListDrawable

2016-04-05 20:35 483 查看
今天在弄一个小东西的时候用到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状态一样,如果是一样的话就不
4000
做任何操作了

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的背景也跟着刷新了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android