碎碎唸
不知道什么时候,自己项目中的TextView
会自己变成AppCompatTextView
。因为项目时间赶。不是特别的重视这个问题。再后来遇到了一个需求,那就是需要全局更改项目中的数字字体。这个时候我又重新开始思考AppCompatTextView
的问题。
因为在之前的项目中更换过字体。只需自己继承自TextView
,然后在构造方法中设置一下字体。就可以完成自定义字体了。
public class FontTextView extends AppCompatTextView {
public FontTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setTypeface(TypeFaceHelper.getInstance().getFineTypeFace());
}
}
因为之前的项目是在项目早期,就已经决定使用自定义字体。所以我们当初规定所有的文字控件都是以FontTextView
但是,现在我们的项目进行着迭代了很久,如果继续使用这样方式工作量会很大,而且容易出错。所以我们需要一种比较简单的方式,来完成这件事。在开工之前,我希望能够先处理以下两个问题。
- 为什么
TextView
会被替换成AppCompatTextView
呢? - 能不能将
AppCompatTextView
替换成我们的FontTextView
呢?
查找
我开始尝试的去寻找在什么时候,我们的TextView
被替换了。我们可以在AppCompatTextView
的构造方法中下个断点。这样当AppCompatTextView
被创建的话,我们就可以知道什么时候它被创建了。 下面是它的函数调用栈。
从上面的函数调用栈中,在我们xml
解析完成之后,然后会调用createView
这个方法来创建view
。我们重点查看一下createView
这个方法
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
这里会根据xml
解析出来的标签,从而创建对应的控件。具体查看一下TextView
在是什么时候被创建.
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
这样我们就已经知道了,我们的TextView
在xml
解析完成之后会被替换成AppCompatTextView
了。那么现在如果能够将AppCompatTextView
替换成我们的FontTextView
就能完成自定义字体了。
替换
通过上面的函数栈,可以发现我onCreateView
是AppCompatDelegateImpl
里面的一个方法。
那么我们能不能重写这个方法,然后将返回值改成我们特定的FontTextView
呢?
答案是一定的,但是这里有一个需要注意的地方。因为AppCompatDelegateImpl
是包访问权限,正常情况下你是没有访问权限的。但是你可以把你的包名改成和它一致,这样你就具有访问权限了。
//包名一致
package androidx.appcompat.app;
public class FontTextDelegate extends AppCompatDelegateImpl {
public FontTextDelegate(Context context, Window window, AppCompatCallback callback) {
super(context, window, callback);
}
@Override
public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
if (name.equals("TextView")) {
return new FontTextView(context, attrs);
}
return super.createView(parent, name, context, attrs);
}
}
我们已经重写了AppCompatDelegateImpl
的方法了,现在只需要查看AppCompatDelegateImpl
在什么时候被初始换。然后替换成我们重写后的子类即可完成目标了。还是一样的方式,在AppCompatDelegateImpl
构造方法中设一个断点。
通过函数调用栈可以看到,AppCompatDelegateImpl
是在AppCompatActivity
中的getDelegate
初始换的,那么我们只需要重写我们项目中的Activity
的getDelegate
方法。就可以替换成AppCompatDelegateImpl
了。
以上!
网友评论