内容简介:所以我们本文主要学习:估计很多人都会使用AS的Tools — Layout Inspector功能来查看自己写的界面结构及控件的相应元素。比如我们写了很简单的例子:
很久没写文章了,所以打算水一篇文章,毕竟这方面知识的文章有很多很多。
所以我们本文主要学习:
- LayoutInflater相关知识
- setFactory相关知识
- 实际项目中的用处
估计很多人都会使用AS的Tools — Layout Inspector功能来查看自己写的界面结构及控件的相应元素。
比如我们写了很简单的例子:
public class TestActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); } } 复制代码
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="textview" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher" /> </LinearLayout> 复制代码
然后用AS查看:
大家有没有看到有没有什么特别的地方:
我们在布局中写的是 Button
, TextView
, ImageView
,但是在AS的Layout Inspector功能查看下,变成了 AppCompatButton
, AppCompatTextView
, AppComaptImageView
,那到底是我们的按钮真的已经在编译的时候自动变成了 AppCompatXXX
系列,还是只是单纯的在这个 工具 里面看的时候我们的控件只是显示给我们看到的名字是 AppCompatXXX
系列而已。
我们把我们的Activity的父类做下修改,改为:
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创建过程
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.获取代理类具体方法源码:" @NonNull public 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方法
所以我们这边要记住其实就是调用AppCompatDelegateImpl的createView方法
所以我们这边要记住其实就是调用AppCompatDelegateImpl的createView方法
重要的事情说三遍,因为后面会用到这块
我们继续来分析源码,我们跟踪到 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
方法:
@NonNull protected AppCompatButton createButton(Context context, AttributeSet attrs) { return new AppCompatButton(context, attrs); } 复制代码
所以我们看到了,最终我们的 Button
替换成了 AppCompatButton
。
2.2 自己实现自定义Factory2
我们现在来具体看下 Factory2
的 onCreateView
方法,我们自己来实现一个自定义的 Factory2
类,而不是用系统自己设置的:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() { //'这个方法是Factory接口里面的,因为Factory2是继承Factory的' @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } //'这个方法是Factory2里面定义的方法' @Override public 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 = Button E: layout_width , -2 E: layout_height , -2 E: text , button E: parent:android.widget.LinearLayout{4e37f38 V.E...},name = TextView E: layout_width , -2 E: layout_height , -2 E: text , textview E: parent:android.widget.LinearLayout{4e37f38 V.E...},name = ImageView E: layout_width , -2 E: layout_height , -2 E: src , @2131361792 复制代码
正好的确是我们layout中设置的控件的值。我们知道了在这个 onCreateView
方法中,我们可以拿到当前View的内容,我们学着系统替换AppCompatXXX控件的方式更换我们demo中的控件,加上这段代码:
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() { @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } @Override public 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的方法,只不过在它构建前,更改参数而已,这样最终还是会跑系统的代码。
我们前面代码提过 最终设置的 Factory2
之后调用的 onCreateView
方法,其实就是调用 AppCompatDelegateImpl
的 createView
方法 (就是前面讲的,重要的事情说三遍那个地方,忘记的可以回头再看下)
所以我们可以修改相应的控件的参数,最后再把修改过的内容重新还给 AppCompatDelegateImpl
的 createView
方法去生成View即可,这样系统原本帮我们做的兼容性也都还在。
所以我们这里要修改代码为:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() { //'这个方法是Factory接口里面的,因为Factory2是继承Factory的' @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } //'这个方法是Factory2里面定义的方法' @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { if(name.equals("TextView") || name.equals("ImageView")){ name = "Button"; } //'我们只是更换了参数,但最终实例化View的逻辑还是交给了AppCompatDelegateImpl' AppCompatDelegate delegate = getDelegate(); View view = delegate.createView(parent, name, context, attrs); return view; } }); super.onCreate(savedInstanceState); setContentView(R.layout.activity_test1); } 复制代码
我们最终可以看到:
按钮也的确都变成了AppCompatButton。
总结:设置Factory2更像是在系统填充View之前,先跑了一下onCreateView方法,然后我们可以在这个方法里面,在View被填充前,对它进行修改。
3. 实际项目中的用处
其实以前在一些文章中也看到过,说什么突然你想全局要替换 Button
到 TextView
,这样更方便什么的,但是单纯这种直接整个控件替换我个人更喜欢去xml文件里面改,因为一般一个app是团队一起开发,然后你这么处理,后期别人维护时候,看了xml,反而很诧异,后期维护我个人感觉不方便。
所以我这个列举了几个常用的功能:
3.1. 全局替换字体等属性
因为字体等是TextView的一个属性,为了加一个属性,我们就没必要去全部的布局中进行更改,只需要上我们的onCreateView中,发现是TextView,就去设置我们对应的字体。
public static Typeface typeface; @Override protected void onCreate(Bundle savedInstanceState) { if (typeface == null){ typeface = Typeface.createFromAsset(getAssets(), "xxxx.ttf"); } LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() { @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { AppCompatDelegate delegate = getDelegate(); View view = delegate.createView(parent, name, context, attrs); if ( view!= null && (view instanceof TextView)){ ((TextView) view).setTypeface(typeface); } return view; } }); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } 复制代码
3.2 动态换肤功能
这块动态换肤功能,网上的文章也很多,但是基本的原理都一样,也是用了我们本文的知识,和上面的更换字体类似,我们可以对做了标记的View进行识别,然后在onCreateView遍历到它的时候,更改它的一些属性,比如背景色等,然后再交给系统去生成View。
具体可以参考下: Android动态换肤原理解析及实践
3.3 无需编写shape、selector,直接在xml设置值
估计前端时间大家在掘金都看到过这篇文章:
里面讲到我们如果要设置控件的角度等属性值,不需要再去写特定的shape或者selector文件,直接在xml中写入:
初步一看是不是感觉很神奇?what amazing !!
其实核心也是使用了我们今天讲到的知识点,自定义Factory类,只需要在onCreateView方法里面,判断attrs的参数名字,比如发现名字是我们制定的stroke_color属性,就去通过代码手动帮他去设置这个值,我们来查看下它的部分代码,我们直接看onCreateView方法即可:
@Override public View onCreateView(String name, Context context, AttributeSet attrs) { ...... ...... if (typedArray.getBoolean(R.styleable.background_ripple_enable, false) && typedArray.hasValue(R.styleable.background_ripple_color)) { int color = typedArray.getColor(R.styleable.background_ripple_color, 0); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Drawable contentDrawable = (stateListDrawable == null ? drawable : stateListDrawable); RippleDrawable rippleDrawable = new RippleDrawable(ColorStateList.valueOf(color), contentDrawable, contentDrawable); view.setClickable(true); view.setBackground(rippleDrawable); } else { StateListDrawable tmpDrawable = new StateListDrawable(); GradientDrawable unPressDrawable = DrawableFactory.getDrawable(typedArray); unPressDrawable.setColor(color); tmpDrawable.addState(new int[]{-android.R.attr.state_pressed}, drawable); tmpDrawable.addState(new int[]{android.R.attr.state_pressed}, unPressDrawable); view.setClickable(true); view.setBackground(tmpDrawable); } } return view; ...... ...... } 复制代码
是不是这么看,大家基本就懂了原理,这样你再去看它的库,或者要加上什么自己特定的属性,都有能力自己去进行修改了。
以上所述就是小编给大家介绍的《Android技能树 — LayoutInflater Factory小结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Android技能树 — 网络小结(7)之 Retrofit源码详细解析
- Android技能树 — 网络小结之 OkHttp超超超超超超超详细解析
- hive实战技能
- 前端时光机(经典技能)
- 云计算技能图谱
- 多线程二 基本技能
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Wireshark网络分析实战
[以色列 Yoram Orzach / 古宏霞、孙余强 / 人民邮电出版社 / 2015-1 / 79.00元
本书采用步骤式为读者讲解了一些使用Wireshark来解决网络实际问题的技巧。 本书共分为14章,其内容涵盖了Wireshark的基础知识,抓包过滤器的用法,显示过滤器的用法,基本/高级信息统计工具的用法,Expert Info工具的用法,Wiresahrk在Ethernet、LAN及无线LAN中的用法,ARP和IP故障分析,TCP/UDP故障分析,HTTP和DNS故障分析,企业网应用程序行......一起来看看 《Wireshark网络分析实战》 这本书的介绍吧!