引言
在平常的开发中,我们总会有各种各样的按钮,圆角的、直角的、正常状态的、按下状态的、禁用状态的。一直的做法就是在 drawable 中写一个 selector,然后用 item 加 shap 来实现。这种做法实现起来也是非常简单,但是存在一个问题:当我们 shap 文件有上千个的时候,我们应该如何维护?
分析
先上一张图吧:
仔细分析下来,图中的几个按钮都是差不多的,他们之间有着许多的相通点,像这种情况,我们真的需要为每一个 TextView 写一个单独的 selector 吗?按照我个人的理解,其实可以将这些不同的属性抽取出来做成自定义 View。
确定属性
既然分析出了异同,那么我们就可以先确定好需要哪些属性,所谓磨刀不误砍柴工,编码之前有个完整的思路,能少走许多弯路。
属性名 | 说明 |
---|---|
TextRadius | 圆角 |
背景颜色相关 | |
NormalBackgroundColor | 正常情况下的背景颜色 |
PressBackgroundColor | 按下情况下的背景颜色 |
DisableBackgroundColor | 禁用状态下的背景颜色 |
SelectedBackgroundColor | 选中状态下背景颜色 |
文字颜色相关 | |
NormalTextColor | 正常情况下的文字颜色 |
PressTextdColor | 按下情况下的文字颜色 |
DisableTextColor | 禁用状态下的文字颜色 |
SelectedTextColor | 选中状态下文字颜色 |
开始编码
创建 PressTextView
开始编码了,创建 PressTextView 继承至 TextView,我的做法是将 TextView 当做 Button 来使用,好像是 TextView 有更好的拓展性。
创建 attr
class 创建完成,在 value 目录下创建 press_text_view_attr 文件,将前面确定好的属性转换为 attr,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PressTextView">
<!--圆角-->
<attr name="TextRadius" format="dimension" />
<!--正常情况下的背景颜色-->
<attr name="NormalBackgroundColor" format="color" />
<!--按下情况下的背景颜色-->
<attr name="PressBackgroundColor" format="color" />
<!--禁用状态下的背景颜色-->
<attr name="DisableBackgroundColor" format="color" />
<!--选中状态下背景颜色-->
<attr name="SelectedBackgroundColor" format="color" />
<!--正常情况下的文字颜色-->
<attr name="NormalTextColor" format="color" />
<!--按下情况下的文字颜色-->
<attr name="PressTextColor" format="color" />
<!--禁用状态下的文字颜色-->
<attr name="DisableTextColor" format="color" />
<!--选中状态下文字颜色-->
<attr name="SelectedTextColor" format="color" />
</declare-styleable>
</resources>
初始化相关属性
这里定义的属性稍微有点多,而且将来相关属性可能会越来越多,所以选择创建一个 config 类来存储相关属性,减少 PressTextView 的代码量。
PressTextViewConfig 内容:
/**不使用public修饰是不想其它模块进行访问*/
/*public*/ class PressTextViewConfig {
/**
* 圆角
*/
public float TextRadius;
/**
* 正常情况下的背景颜色
*/
public int NormalBackgroundColor;
/**
* 按下情况下的背景颜色
*/
public int PressBackgroundColor;
/**
* 禁用状态下的背景颜色
*/
public int DisableBackgroundColor;
/**
* 选中状态下背景颜色
*/
public int SelectedBackgroundColor;
/**
* 正常情况下的文字颜色
*/
public int NormalTextColor;
/**
* 按下情况下的文字颜色
*/
public int PressTextColor;
/**
* 禁用状态下的文字颜色
*/
public int DisableTextColor;
/**
* 选中状态下文字颜色
*/
public int SelectedTextColor;
}
然后再 PressTextView 中定义一个 PressTextViewConfig 的属性,这样就可以方便的赋值和使用:
/**
* 初始化相关属性
*
* @param context
* @param attrs
*/
private void init(Context context, AttributeSet attrs) {
mPressTextViewConfig = new PressTextViewConfig();
// 打开样式资源
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PressTextView);
//圆角
mPressTextViewConfig.TextRadius = typedArray.getDimension(R.styleable.PressTextView_TextRadius, 5);
// 正常背景颜色
mPressTextViewConfig.NormalBackgroundColor = typedArray
.getColor(R.styleable.PressTextView_NormalBackgroundColor,
0xff3F51B5);
// 按下背景颜色
mPressTextViewConfig.PressBackgroundColor = typedArray
.getColor(R.styleable.PressTextView_PressBackgroundColor,
0xff303F9F);
// 禁言背景颜色
mPressTextViewConfig.DisableBackgroundColor = typedArray
.getColor(R.styleable.PressTextView_DisableBackgroundColor,
0xffeeeeee);
// 选中背景颜色
mPressTextViewConfig.SelectedBackgroundColor = typedArray
.getColor(R.styleable.PressTextView_SelectedBackgroundColor,
0xffFF4081);
// 正常字体颜色
mPressTextViewConfig.NormalTextColor = typedArray.getColor(R.styleable.PressTextView_NormalTextColor,
Color.WHITE);
// 按下字体颜色
mPressTextViewConfig.PressTextColor = typedArray.getColor(R.styleable.PressTextView_PressTextColor,
0xffeeeeee);
// 禁用字体颜色
mPressTextViewConfig.DisableTextColor = typedArray.getColor(R.styleable.PressTextView_DisableTextColor,
0xff999999);
// 选中字体颜色
mPressTextViewConfig.SelectedTextColor = typedArray.getColor(R.styleable.PressTextView_SelectedTextColor,
0xffcdcdcd);
//释放资源
typedArray.recycle();
mPaint = new Paint();
mPaint.setAntiAlias(true);
setClickable(true);
setFocusable(true);
}
绘制
我们继承的是 TextView,所以其他逻辑可以直接忽略,包括宽高啊、测量之类的,只需要重写 onDraw 方法,在 onDraw 中进行绘制。而绘制有以下要点:
- 因为背景使用圆角,所以使用 drawRoundRect 来进行绘制,而绘制的时机是在 super.onDraw(canvas)之前进行绘制。
- 需要判断不同的状态来使用不同的背景颜色和字体颜色
- 设置字体颜色需要在 super.onDraw(canvas)之后
- 需要重写 onTouchEvent 方法,方法内不做其它事情,就为了调用 postInvalidate 方法触发绘制。
核心代码也比较简单,这里就直接全部贴上了:
@Override
protected void onDraw(Canvas canvas) {
if (mBackgroundRectf == null) {
mBackgroundRectf = new RectF(0, 0, getWidth(), getHeight());
}
// 画笔颜色
mPaint.setColor(switchBackground());
canvas.drawRoundRect(mBackgroundRectf, mPressTextViewConfig.TextRadius,
mPressTextViewConfig.TextRadius,
mPaint);
super.onDraw(canvas);
//设置字体颜色
setTextColor(switchTextColor());
}
/**
* 字体颜色
*/
private int switchTextColor() {
if (!isEnabled()) {
return mPressTextViewConfig.DisableTextColor;
}
// 按下状态
if (isPressed()) {
return mPressTextViewConfig.PressTextColor;
}
// 选中状态
if (isSelected()) {
return mPressTextViewConfig.SelectedTextColor;
}
return mPressTextViewConfig.NormalTextColor;
}
/**
* 判断背景颜色
*
* @return
*/
private int switchBackground() {
// 禁用状态
if (!isEnabled()) {
return mPressTextViewConfig.DisableBackgroundColor;
}
// 按下状态
if (isPressed()) {
return mPressTextViewConfig.PressBackgroundColor;
}
// 选中状态
if (isSelected()) {
return mPressTextViewConfig.SelectedBackgroundColor;
}
return mPressTextViewConfig.NormalBackgroundColor;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 触摸的时候重绘
postInvalidate();
return super.onTouchEvent(event);
}
使用
编写完成,现在在 xml 中进行引用。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<com.jc.lib.view.PressTextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="正常可点击(有按下背景和字体颜色)"
android:textSize="18sp"
app:NormalBackgroundColor="#03a4f9"
app:NormalTextColor="#fff"
app:PressBackgroundColor="#3F51B5"
app:PressTextColor="#eee"
app:TextRadius="8dp" />
<com.jc.lib.view.PressTextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="禁用状态"
android:textSize="18sp"
android:enabled="false"
app:DisableBackgroundColor="#eeeeee"
app:DisableTextColor="#999999"
app:TextRadius="20dp" />
<com.jc.lib.view.PressTextView
android:id="@+id/mSelectTv"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="选中状态"
android:textSize="18sp"
app:SelectedBackgroundColor="@color/colorPrimaryDark"
app:SelectedTextColor="#fff"
app:TextRadius="4dp" />
</LinearLayout>
效果
现在就是验证编码成果的时候了。
总结
其实这只是一个简单的 TextView 封装,并不难,有时候只是一个思路问题,由此延伸,我们的其他布局也是不是可以这样做呢?或者再加上边框、实线边框、虚线边框、上边框。这些就不一一去实现了,在这里主要是为了提供这样一个思路,我们更需要做的是跳出当前思想的局限性,用散发性思维去思考事物。
最后
未完待续、敬请期待!
免为其难的关注一下公众号吧!!
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于