Android给一组控件设置StateListDrawable
2016-04-05 20:35
483 查看
今天在弄一个小东西的时候用到StateListDrawable,结果遇到一点问题。现在简单总结一下。
问题是这样的,在前面写SegmentView的时候,给里面的各个Item添加背景,想着中间的几个Item(中间的Item都没有圆角)的背景都是一样的,那就用一个StateListDrawable对象就够了吧。结果就出问题了,如果中间有2个或更多的Item,当点击它们的时候背景就乱了,和想象的结果完全不一样。下面我把代码贴出来:
大家可以看到点击各个Tab后看到的现象非常奇怪,各个Tab背景的转换非常混乱,甚至都没有什么规律可言,不过Text的颜色(textColor)却是按照设想的方式在正常工作,这是怎么一回事呢?
还是到源码里去看看吧,首先看看setSelected(boolean selected)这个函数
函数一开始就判断设置的select状态是否和View当前的select状态一样,如果是一样的话就不
4000
做任何操作了
if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected)
如果不一样,就先设置选中的状态,然后刷新View,再然后调用refreshDrawableState();这个函数,而这个函数又会调用drawableStateChanged(),看一下这个函数做了什么
可以从中看到当我们设置View的selected状态时,View会将这个状态传递给background,foreground等几个Drawable。
在这个例子中,由于设置View的background时用的都是同一个Drawable,当其中的一个Tab设置selected为true时,其backgroundDrawable的state也被设置为true,这时其他的backgroundDrawable的state也跟着改变了,所以就乱了。
从效果图可以看到,当一开始点击第一个Tab时,第四个Tab的背景改变了,而第二、三个没有改变,这又是怎么会事呢?首先我们看一下setBackgroundDrawable这个函数,
我们看到在这个函数里,我们的Drawable的callback是调用这个函数的View,所以当四个View都调用了setBackgroundDrawable之后,这个Drawable的callback就是第四个View了。然后我们再来看一下View的setSelected函数,通过一系列的跳转(setSelected->refreshDrawableState->drawableStateChanged->(StateListDrawable)setState->onStateChange->selectDrawable->invalidateSelf)到了StateListDrawable的invalidateSelf函数,我们来看一下这个函数:
这里的callback就是我们刚才调用setBackgroundDrawable时设置的callback,也就是第四个View,而View的invalidateDrawable函数又做了什么呢?
其中getDirtyBounds其实就是Drawable的整个区域(这个我们可以查看Drawable的源码看到,StateListDrawable没有复写这个方法),所以当我们调用setSelect方法的时候其实是被选中的那个View自己在刷新,但是由于Drawable的callback是第四个View,所以第四个View的背景也跟着刷新了。
问题是这样的,在前面写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的背景也跟着刷新了。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories