换肤、全局字体替换、无需编写 shape,Android 程序员月薪 20k 的涨薪秘籍
public class TestActivity extends AppCompatActivity{......}变为 public class TestActivity extends Activity{......}
我们再来查看下 Layout Inspector 界面:
我们可以看到,控件就自动变成了我们布局里面写的控件名称了, 那就说明,我们继承的AppCompatActivity
对我们 xml 里面写的控件做了替换。
而 AppCompatActivity 的替换主要是通过 LayoutInflater setFactory
1.LayoutInflater 相关知识
其实大部分人使用LayoutInflater
的话,更多的是使用了inflate
方法,用来对 Layout 文件变成 View:
View view = LayoutInflater.from(this).inflate(R.layout.activity_test,null);
甚至于我们平常在 Activity 里面经常写的setContentView(R.layout.xxx);
方法的内部也是通过inflate
方法实现的。
有没有想过为什么调用了这个方法后,我们就可以拿到了相关的 View 对象了呢?
其实很简单,就是我们传入的是一个 xml 文件,里面通过 xml 格式写了我们的布局,而这个方法会帮我们去解析 XML 的格式,然后帮我们实例化具体的 View 对象即可,我们具体一步步来看源码:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();if (DEBUG) {Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
Integer.toHexString(resource) + ")");}
//"可以看到主要分为 2 步"//"第一步:通过 res.getLayout 方法拿到 XmlResourceParser 对象"final XmlResourceParser parser = res.getLayout(resource);
try {
//"第二步:通过 inflate 方法最终把 XmlResourceParser 转为 View 实例对象"return inflate(parser, root, attachToRoot);
} finally {parser.close();}}
本来我想大片的源码拷贝上来,然后一步步写上内容,但是后来发现一个讲解资源获取过程的不错的系列文章,所以我就直接借鉴大佬的,直接贴上链接了:
(关于本文的内容相关的,可以着重看下第一篇和第三篇,inflate 的源码在第三篇)
Android资源管理框架(Asset Manager)(一)简介
[Android 资源管理框架(二)AssetManager 创建过程](
)
[Android 资源管理框架(三)应用程序资源的查找过程](
)
2. Factory 相关知识
2.1 源码中默认设置的 Factory2 相关代码
我们在前言中的例子中可以看到我们的Activity
继承了AppCompatActivity
,我们来查看AppCompatActivity
的onCreate
方法:
protected void onCreate(@Nullable Bundle savedInstanceState) {//"1.获取代理类对象"AppCompatDelegate delegate = this.getDelegate();
//"2.调用代理类的 installViewFactory 方法"delegate.installViewFactory();
............super.onCreate(savedInstanceState);}
我们可以看到和Activity
的onCreate
方法最大的不同就是AppCompatActivity
把onCreate
种的操作都放在了代理类AppCompatDelegate
中的onCreate
方法中处理了,而AppCompatDelegate
是抽象类,具体的实现类是AppCompatDelegateImpl
,
//"1.获取代理类具体方法源码:"
@NonNullpublic AppCompatDelegate getDelegate() {if (this.mDelegate == null) {this.mDelegate = AppCompatDelegate.create(this, this);}
return this.mDelegate;}
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);}
我们再来看代理类的installViewFactory
方法具体实现:
public void installViewFactory() {
//'获取了 LayoutInflater 对象'LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
if (layoutInflater.getFactory() == null) {//'如果 layoutInflater 的 factory2 为 null,对 LayoutInflater 对象设置 factory'LayoutInflaterCompat.setFactory2(layoutInflater, this);} else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");}}
AppCompatDelegateImpl
自己实现了Fatory2
接口,所以就直接setFactory2(xx,this)
即可,我们来看下 Factory2 到底是啥:
public interface Factory2 extends Factory {public View onCreateView(View parent, String name, Context context, AttributeSet attrs);}
可能很多人在以前看过相关文章,都是Factory
接口及方法是setFactory
,对于Factory2
是一脸懵逼,我们可以看到上面的Factory2
代码,Factory2
其实就是继承了Factory
接口,其实setFactory
方法已经被弃用了,而且你调用setFactory
方法,内部其实还是调用了setFactory2
方法,setFactory2
是在 SDK>=11 以后引入的:
所以我们就直接可以简单理解为Factory2
类和setFactory2
方法是用来替代Factory
类和setFactory
方法
所以也就执行了AppCompatDelegateImpl
里面的onCreateView
方法:
//'调用方法 1'public View onCreateView(String name, Context context, AttributeSet attrs) {return this.onCreateView((View)null, name, context, attrs);}
//'调用方法 2'public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {this.createView(parent, name, context, attrs);}
//'调用方法 3'public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
//先实例化 mAppCpatViewInflater 对象代码............
//'直接看这里,最后调用了 mAppCompatViewInflater.createView 方法返回相应的 View'return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());}
所以通过上面我们可以看到,最终设置的Factory2
之后调用的onCreateView
方法,其实就是调用 AppCompatDelegateImpl 的 createView 方法
(最终调用了 AppCompatViewInflater 类中的 createView 方法)
所以我们这边要记住其实就是调用 AppCompatDelegateImpl 的 createView 方法</br>所以我们这边要记住其实就是调用 AppCompatDelegateImpl 的 createView 方法</br>所以我们这边要记住其实就是调用 AppCompatDelegateImpl 的 createView 方法</br>重要的事情说三遍,因为后面会用到这块
我们继续来分析源码,我们跟踪到AppCompatViewInflater
类中的createView
方法(这里以Button
为例,其他的代码暂时去除):
final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
............
View view = null;byte var12 = -1;switch(name.hashCode()) {
............
case 2001146706:if (name.equals("Button")) {var12 = 2;}}
switch(var12) {
............
case 2:view = this.createButton(context, attrs);this.verifyNotNull((View)view, name);break;
............
return (View)view;}
我们来看createButton
方法:
@NonNullprotected AppCompatButton createButton(Context context, AttributeSet attrs) {return new AppCompatButton(context, attrs);}
所以我们看到了,最终我们的Button
替换成了AppCompatButton
。
2.2 自己实现自定义 Factory2
我们现在来具体看下Factory2
的onCreateView
方法,我们自己来实现一个自定义的Factory2
类,而不是用系统自己设置的:
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {//'这个方法是 Factory 接口里面的,因为 Factory2 是继承 Factory 的'@Overridepublic View onCreateView(String name, Context context, AttributeSet attrs) {return null;}
//'这个方法是 Factory2 里面定义的方法'@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs) {Log.e(TAG, "parent:" + parent + ",name = " + name);int n = attrs.getAttributeCount();for (int i = 0; i < n; i++) {Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));}return null;}
});
super.onCreate(savedInstanceState);setContentView(R.layout.activity_test1);}
我们可以看到Factory2
的onCreateView
方法里面的属性 parent 指的是父 View 对象,name 是当前这个 View 的 xml 里面的名字,attrs 包含了 View 的属性名字及属性值。
打印后我们可以看到打印出来了我们的 demo 中的 Layout 布局中写的三个控件了。
..................
E: parent:android.widget.LinearLayout{4e37f38 V.E...},name = ButtonE: layout_width , -2E: layout_height , -2E: text , buttonE: parent:android.widget.LinearLayout{4e37f38 V.E...},name = TextViewE: layout_width , -2E: layout_height , -2E: text , textviewE: parent:android.widget.LinearLayout{4e37f38 V.E...},name = ImageViewE: layout_width , -2E: layout_height , -2E: src , @2131361792
正好的确是我们 layout 中设置的控件的值。我们知道了在这个onCreateView
方法中,我们可以拿到当前 View 的内容,我们学着系统替换 AppCompatXXX 控件的方式更换我们 demo 中的控件,加上这段代码:
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {@Overridepublic View onCreateView(String name, Context context, AttributeSet attrs) {return null;}
@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//'我们在这里对传递过来的 View 做了替换'//'把 TextView 和 ImageView 都换成了 Button'if(name.equals("TextView") || name.equals("ImageView")){Button button = new Button(context, attrs);return button;}
return null;}
});
我们可以看下效果:
我们知道了在 onCreateView 中,可以看到遍历的所有 View 的名字及属性参数,也可以在这里把 return 的值更改做替换。
但是我们知道系统替换了的 AppCompatXXX 控件做了很多兼容,如果我们像上面一样把 TextView 和 ImageView 直接换成了 Button,那么系统也因为我们设置过了 Factory2,就不会再去设置了,也就不会帮我们自动变成 AppCompatButton,而是变成了三个 Button。
所以我们不能单纯盲目的直接使用我们的Factory2
,所以我们还是用的系统最终构建 View 的方法,只不过在它构建前,更改参数而已,这样最终还是会跑系统的代码。
我们前面代码提过<font color = "red">最终设置的Factory2
之后调用的onCreateView
方法,其实就是调用AppCompatDelegateImpl
的createView
方法</font>(就是前面讲的,重要的事情说三遍那个地方,忘记的可以回头再看下)
所以我们可以修改相应的控件的参数,最后再把修改过的内容重新还给AppCompatDelegateImpl
的createView
方法去生成 View 即可,这样系统原本帮我们做的兼容性也都还在。
所以我们这里要修改代码为:
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {//'这个方法是 Factory 接口里面的,因为 Factory2 是继承 Factory 的'@Overridepublic View onCreateView(String name, Context context, AttributeSet attrs) {return null;}
//'这个方法是 Factory2 里面定义的方法'@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
评论