写点什么

换肤、全局字体替换、无需编写 shape,Android 程序员月薪 20k 的涨薪秘籍

用户头像
Android架构
关注
发布于: 34 分钟前

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,我们来查看AppCompatActivityonCreate方法:


protected void onCreate(@Nullable Bundle savedInstanceState) {//"1.获取代理类对象"AppCompatDelegate delegate = this.getDelegate();


//"2.调用代理类的 installViewFactory 方法"delegate.installViewFactory();


............super.onCreate(savedInstanceState);}


我们可以看到和ActivityonCreate方法最大的不同就是AppCompatActivityonCreate种的操作都放在了代理类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 方法


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


(最终调用了 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

我们现在来具体看下Factory2onCreateView方法,我们自己来实现一个自定义的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);}


我们可以看到Factory2onCreateView方法里面的属性 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方法,其实就是调用AppCompatDelegateImplcreateView方法</font>(就是前面讲的,重要的事情说三遍那个地方,忘记的可以回头再看下)


所以我们可以修改相应的控件的参数,最后再把修改过的内容重新还给AppCompatDelegateImplcreateView方法去生成 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) {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
换肤、全局字体替换、无需编写shape,Android程序员月薪20k的涨薪秘籍