Android 多主题之 EditText

多主题的需求在很多 Android 应用中都有存在,实现方式也是多种多样。比如,以插件化方式实现的有 QQ,Weico 等;以修改原生控件颜色的方式实现的有 哔哩哔哩,酷安 等。
本文所写的就是以修改原生控件颜色的方式应用主题的,EditText 的修改。

然而,这个不是这么好做的。。QAQ
首先查阅 EditText 的 API 文档,发现还是提供了两个方法的

使用 EditText.setBackgroundTintList(colorStateList) 可以设置 EditText 下划线的颜色

使用 EditText.setHighlightColor(color) 可以设置 EditText 选中文字后的高亮颜色

恩,改完后确实生效了

但是。。输入光标和光标控制滑块还是原来的颜色,反而不伦不类的了。。

于是查看 EditText 的源码,发现是在它的父类 —— TextView 中的 mEditor 这个对象中控制的,但是这个变量是 private 的,也没有提供公开的获得方法,无奈只好用反射了

1
2
3
4
5
6
7
private static Field mEditor;
private static void getEditorFieldFromReflect() {
if (mEditor == null) {
mEditor = ReflectUtils.getDeclaredField(TextView.class, "mEditor");
}
}

为了让反射达到最大的性能,声明了一个静态属性 mEditor,并在它为 null 的时候才进行反射(下同)

  • ReflectUtils 是一个自定义的反射工具类

再获取输入光标的 Field 对象

1
2
3
4
5
6
7
private static Field mCursorDrawableRes;
private static void getCursorFieldFromReflect() {
if (mCursorDrawableRes == null) {
mCursorDrawableRes = ReflectUtils.getDeclaredField(TextView.class, "mCursorDrawableRes");
}
}

这样就获取了输入光标的原始 id,也就是 mCursorDrawableRes
由于原始的 CursorDrawable,是存在于 Android Framework 中的 framework-res.apk 中的,所以这里用一种曲线救国的方式,先获取到他的 id,进而获取到 Drawable,然后通过 Android 5.0 新增的 Tint 方式渲染 Drawable 的颜色,再设置进去

实现代码如下

1
2
3
4
5
6
7
8
9
10
11
private static void setCursorColor(EditText editText, int color, Object editor) throws Exception {
int cursorId = mCursorDrawableRes.getInt(editText);
Drawable drawable = editText.getContext().getDrawable(cursorId);
if (drawable != null) {
drawable.setTint(color);
}
ReflectUtils.setObjectField(mEditor.getType(), "mCursorDrawable",
editor, new Drawable[]{drawable, drawable});
}

这样输入光标的颜色就修改完成了

之后就是光标控制滑块的颜色了
查看源码,发现这个对应的是三个 Drawable

只是这些的 id 是在 EditText 里的。于是,故技重施,和 mCursorDrawableRes 一样

1
2
3
4
5
6
7
8
9
10
private static void getSelectFieldFromReflect() {
if (mSelectHandleLeft == null || mSelectHandleRight == null || mSelectHandleCenter == null) {
Class<?> EditorClass = mEditor.getType();
mSelectHandleLeft = ReflectUtils.getDeclaredField(EditorClass, "mSelectHandleLeft");
mSelectHandleRight = ReflectUtils.getDeclaredField(EditorClass, "mSelectHandleRight");
mSelectHandleCenter = ReflectUtils.getDeclaredField(EditorClass, "mSelectHandleCenter");
}
}

1
2
3
4
5
6
7
8
9
private static void setSelectHandleColor(EditText editText, int color, Object editor) throws Exception {
Drawable leftDrawable = (Drawable) mSelectHandleLeft.get(editor);
Drawable rightDrawable = (Drawable) mSelectHandleRight.get(editor);
Drawable centerDrawable = (Drawable) mSelectHandleCenter.get(editor);
updateSelectHandleColor(leftDrawable, "mTextSelectHandleLeftRes", editText, color);
updateSelectHandleColor(rightDrawable, "mTextSelectHandleRightRes", editText, color);
updateSelectHandleColor(centerDrawable, "mTextSelectHandleRes", editText, color);
}

最后做个 setColor 的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void setColor(EditText editText, int color) {
setUnderlineColor(editText, color);
setHighlightColor(editText, color);
try {
getEditorFieldFromReflect();
getCursorFieldFromReflect();
getSelectFieldFromReflect();
Object editor = mEditor.get(editText);
setCursorColor(editText, color, editor);
setSelectHandleColor(editText, color, editor);
} catch (Exception e) {
e.printStackTrace();
}
}

需要时直接 EditColorHelper.setColor(EditText, Color) 就可以了

GitHub 地址:iAcn/EditColorHelper
也欢迎 Star,沟通交流,一起提高 Development Level