什么都别管,先看效果图
封装的demo请自行下载
https://github.com/linqinen708/MyCheckedTextView
Android开发的小伙伴,在遇到TextView 需要设置背景色时,应该会联想到shape
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="@color/green_61A71F"/>
</shape>
或则根据某种状态,比如 按压、选择和点击等条件,而改变背景色时,肯定第一时间就想到了selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/shape_bg_check" android:state_checked="true"/>
<item android:drawable="@drawable/shape_bg_uncheck" android:state_checked="false"/>
</selector>
然后再在布局中设置background就行了。
但是如果每次需要设置,都要去创建drawable下的文件,非常的繁琐,而且耦合度高,能否有其他的方式来实现呢?
上网搜索后,发现了两个个神器 GradientDrawable 和 StateListDrawable
细心的小伙伴会发现,当我们需要动态设置TextView的背景,也就是setBackground时,传入的参数实际上是一个Drawable对象,这也是为什么我们的shape和selector文件需要放在drawable文件夹下的原因。而GradientDrawable 和 StateListDrawable就是继承自Drawable的
1.GradientDrawable
如果我们需要动态设置TextView的背景颜色、形状和圆角等属性时,就可以使用GradientDrawable
GradientDrawable gradientDrawable = new GradientDrawable();
gradientDrawable.setColor(Color.RED);//背景颜色,对应标签shape 中的solid属性
gradientDrawable.setShape(GradientDrawable.RECTANGLE);//形状
gradientDrawable.setStroke(1,Color.GREEN);//边框颜色,对应标签shape 中的stroke属性
gradientDrawable.setCornerRadius(5);//四个圆角的角度,对应标签shape 中的radius属性
//如果需要设置某一个圆角的角度,使用setCornerRadii方法
//1、2两个参数表示左上角,3、4表示右上角,5、6表示右下角,7、8表示左下角
//gradientDrawable.setCornerRadii(new float[]{mRadiusTopLeft, mRadiusTopLeft, mRadiusTopRight, mRadiusTopRight, mRadiusBottomRight, mRadiusBottomRight, mRadiusBottomLeft, mRadiusBottomLeft});
//gradientDrawable.setOrientation();//只有GradientDrawable.LINEAR_GRADIENT,才有Orientation属性,默认GradientDrawable.Orientation.TOP_BOTTOM
//gradientDrawable.setColors(new int[]{Color.RED, Color.GREEN});//设置setColors之后,setColor无效
//gradientDrawable.setGradientType(mGradientType);//默认GradientDrawable.LINEAR_GRADIENT
//gradientDrawable.setGradientRadius(10);//gradient 角度
textView.setBackground(gradientDrawable);
2.StateListDrawable用法也很简单
StateListDrawable backgroundDrawable = new StateListDrawable();
backgroundDrawable.addState(new int[]{android.R.attr.state_pressed}, mPressedDrawable);
backgroundDrawable.addState(new int[]{android.R.attr.state_checked}, mCheckedDrawable);
backgroundDrawable.addState(new int[]{android.R.attr.state_enabled}, mNormalDrawable);
backgroundDrawable.addState(new int[]{-android.R.attr.state_enabled}, mUnableDrawable);
textView.setBackground(backgroundDrawable);
其中在属性,比如android.R.attr.state_enabled 前面加上 “-”,也就是 -android.R.attr.state_enabled,就表示false,其他属性也有很多,我就不一一列出了。
方法 addState(int[] stateSet, Drawable drawable) 当中有2个参数,第一个是传入一个int类型的数组,里面是你需要改变的属性,因为是数组,所以你可以同时选择多个,第二个参数就是一个Drawable 对象,比如context.getResources().getDrawable(R.drawable.bg),也可以是上面提到的GradientDrawable。
设置完之后,你设置属性,比如setEnabled,就可以触发状态的改变,从而动态改变颜色
textView.setEnabled(!textView.isEnabled());
那如果想要动态设置TextView的颜色,并且也能根据状态动态改变呢?
3.ColorStateList
int[] colors = new int[]{
getResources().getColor(R.color.yellow_F9F1DC),
getResources().getColor(R.color.blue_0a57f3),
getResources().getColor(R.color.red_E66051),
getResources().getColor(R.color.gray_f1f2f4),
};
int[][] states = new int[4][];
states[0] = new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled};
states[1] = new int[]{android.R.attr.state_checked, android.R.attr.state_enabled};
states[2] = new int[]{android.R.attr.state_enabled};
states[3] = new int[]{-android.R.attr.state_enabled};
ColorStateList colorList = new ColorStateList(states, colors);
textView1.setTextColor(colorList);
4.Checkable
细心的小伙伴会发现,在我上面的代码中,出现了TextView本身不具备的属性R.attr.state_checked。
没错,这个属性是我自己手动添加上去的。因为平常经常会碰到需要动态选择的状态,虽然有系统自带的CheckedTextView,但是用起来还是不方便,所以就自己额外对TextView进行了封装。
用起来也很方便,首先自己定义常量CHECKED_STATE_SET ,然后重写onCreateDrawableState方法,在里面对原有的状态属性额外添加一个属性,即extraSpace + 1,之后调用mergeDrawableStates方法,将自己的属性和原本的属性结合,返回drawableState即可。
然后让自己的TextView添加Checkable 接口,重写setChecked方法,其中要调用refreshDrawableState这个方法,表示刷新TextView的状态
经过日志发现,TextView在被点击触发的状态顺序依次是:
点击 > onCreateDrawableState > drawableStateChanged(state_pressed) > setChecked > onCreateDrawableState > drawableStateChanged(state_pressed 、state_checked) > onCreateDrawableState > drawableStateChanged(state_checked)
也就是说,会先触发press效果,然后触发setChecked 改变状态,此时TextView有press和check两种状态,刷新界面后,保留check状态,显示在界面上
public class MyTextView extends AppCompatTextView implements Checkable {
private static final int[] CHECKED_STATE_SET = new int[]{android.R.attr.state_checked};
private boolean mChecked;
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
//LogT.i("被选中");
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
@Override
public void setChecked(boolean checked) {
LogT.i("mChecked:" + mChecked + ", checked:" + checked);
if (this.mChecked != checked) {
this.mChecked = checked;
refreshDrawableState();
}
}
@Override
public boolean isChecked() {
return mChecked;
}
@Override
public void toggle() {
setChecked(!mChecked);
}
}
同理,setEnabled(false) 的状态也是这样改变的
按照以上步骤基本可以完成常见的TextView的动态设置背景和文字颜色,不过这样用起来还是有点麻烦,所以我们要多做一步,把这些功能封装起来
封装后,就可以在xml布局中直接使用这些属性,不再需要在drawable文件夹下创建shape和selector了
真香 O(∩_∩)O哈哈~
<com.linqinen708.mycustomview.mycheckedtextview.MyCheckedTextView
android:id="@+id/mtv_001"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_marginStart="15dp"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="确定"
app:mtv_checked_solid_color="@color/blue_8daef3"
app:mtv_checked_stroke_color="@color/blue_8daef3"
app:mtv_pressed_solid_color="@color/green_5FA51F"
app:mtv_pressed_stroke_color="@color/green_5FA51F"
app:mtv_unable_solid_color="@color/gray_D5D5D5"
app:mtv_unable_stroke_color="@color/gray_D5D5D5"
app:mtv_solid_color="@color/yellow_F9F1DC"
app:mtv_stroke_color="@color/red_E66051"
app:mtv_stroke_width="2dp"
app:mtv_checked_text_color="@color/white_ffffff"
app:mtv_pressed_text_color="@color/yellow_F9F1DC"
app:mtv_unable_text_color="@color/white_ffffff"
/>
坑1:所有系统自带的属性,默认是true,在前面添加“-”,则表示false,比如android.R.attr.state_enabled 前面加上 “-”,也就是 -android.R.attr.state_enabled,就表示false
坑2:当有多个状态需要判断时,注意顺序!!!注意顺序!!!注意顺序!!!重要的事情说三遍!!!
比如你先判断state_enabled,再判断state_pressed,不好意思,state_pressed就没有效果了,所以state_enabled要放在最后来判断,优先判断state_pressed、state_checked等状态。
别问我为什么知道,我TM一直以为我代码哪里写错了。。。o(╥﹏╥)o
//正确案例
states[0] = new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled};
states[1] = new int[]{android.R.attr.state_checked, android.R.attr.state_enabled};
states[2] = new int[]{android.R.attr.state_enabled};
states[3] = new int[]{-android.R.attr.state_enabled};
//错误案例
states[0] = new int[]{android.R.attr.state_enabled};
states[1] = new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled};
states[2] = new int[]{android.R.attr.state_checked, android.R.attr.state_enabled};
states[3] = new int[]{-android.R.attr.state_enabled};
//正确案例
StateListDrawable backgroundDrawable = new StateListDrawable();
backgroundDrawable.addState(new int[]{android.R.attr.state_pressed}, mPressedDrawable);
backgroundDrawable.addState(new int[]{android.R.attr.state_checked}, mCheckedDrawable);
backgroundDrawable.addState(new int[]{android.R.attr.state_enabled}, mNormalDrawable);
backgroundDrawable.addState(new int[]{-android.R.attr.state_enabled}, mUnableDrawable);
//错误案例
StateListDrawable backgroundDrawable = new StateListDrawable();
backgroundDrawable.addState(new int[]{android.R.attr.state_enabled}, mNormalDrawable);
backgroundDrawable.addState(new int[]{android.R.attr.state_pressed}, mPressedDrawable);
backgroundDrawable.addState(new int[]{android.R.attr.state_checked}, mCheckedDrawable);
backgroundDrawable.addState(new int[]{-android.R.attr.state_enabled}, mUnableDrawable);
参考资料:
https://blog.csdn.net/qq_31796651/article/details/75010332
https://blog.csdn.net/cui130/article/details/89181691
https://www.jianshu.com/p/64a825915da9