Android技能树 — LayoutInflater Factory小结

栏目: Android · 发布时间: 6年前

内容简介:所以我们本文主要学习:估计很多人都会使用AS的Tools — Layout Inspector功能来查看自己写的界面结构及控件的相应元素。比如我们写了很简单的例子:

很久没写文章了,所以打算水一篇文章,毕竟这方面知识的文章有很多很多。

所以我们本文主要学习:

  1. LayoutInflater相关知识
  2. setFactory相关知识
  3. 实际项目中的用处

估计很多人都会使用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查看:

Android技能树 — LayoutInflater Factory小结

大家有没有看到有没有什么特别的地方:

Android技能树 — LayoutInflater Factory小结

我们在布局中写的是 Button , TextView , ImageView ,但是在AS的Layout Inspector功能查看下,变成了 AppCompatButton , AppCompatTextView , AppComaptImageView ,那到底是我们的按钮真的已经在编译的时候自动变成了 AppCompatXXX 系列,还是只是单纯的在这个 工具 里面看的时候我们的控件只是显示给我们看到的名字是 AppCompatXXX 系列而已。

我们把我们的Activity的父类做下修改,改为:

public class TestActivity extends AppCompatActivity{
    ......
}
变为
public class TestActivity extends Activity{
    ......
}

复制代码

我们再来查看下Layout Inspector界面:

Android技能树 — LayoutInflater Factory小结

我们可以看到,控件就自动变成了我们布局里面写的控件名称了, 那就说明,我们继承的 AppCompatActivity 对我们xml里面写的控件做了替换。

而AppCompatActivity的替换主要是通过LayoutInflater setFactory

Android技能树 — LayoutInflater Factory小结

正文

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资源管理框架(三)应用程序资源的查找过程

Android技能树 — LayoutInflater Factory小结

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.获取代理类具体方法源码:"

@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以后引入的:

Android技能树 — LayoutInflater Factory小结

所以我们就直接可以简单理解为 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

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

复制代码

我们可以看到 Factory2onCreateView 方法里面的属性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;
    }

});
复制代码

我们可以看下效果:

Android技能树 — LayoutInflater Factory小结

我们知道了在onCreateView中,可以看到遍历的所有View的名字及属性参数,也可以在这里把return的值更改做替换。

但是我们知道系统替换了的AppCompatXXX控件做了很多兼容,如果我们像上面一样把TextView和ImageView直接换成了Button,那么系统也因为我们设置过了Factory2,就不会再去设置了,也就不会帮我们自动变成AppCompatButton,而是变成了三个Button。

Android技能树 — LayoutInflater Factory小结

所以我们不能单纯盲目的直接使用我们的 Factory2 ,所以我们还是用的系统最终构建View的方法,只不过在它构建前,更改参数而已,这样最终还是会跑系统的代码。

我们前面代码提过 最终设置的 Factory2 之后调用的 onCreateView 方法,其实就是调用 AppCompatDelegateImplcreateView 方法 (就是前面讲的,重要的事情说三遍那个地方,忘记的可以回头再看下)

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

复制代码

我们最终可以看到:

Android技能树 — LayoutInflater Factory小结

按钮也的确都变成了AppCompatButton。

总结:设置Factory2更像是在系统填充View之前,先跑了一下onCreateView方法,然后我们可以在这个方法里面,在View被填充前,对它进行修改。

Android技能树 — LayoutInflater Factory小结

3. 实际项目中的用处

其实以前在一些文章中也看到过,说什么突然你想全局要替换 ButtonTextView ,这样更方便什么的,但是单纯这种直接整个控件替换我个人更喜欢去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设置值

估计前端时间大家在掘金都看到过这篇文章:

无需自定义View,彻底解放shape,selector吧

里面讲到我们如果要设置控件的角度等属性值,不需要再去写特定的shape或者selector文件,直接在xml中写入:

Android技能树 — LayoutInflater Factory小结

初步一看是不是感觉很神奇?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技能树 — LayoutInflater Factory小结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Wireshark网络分析实战

Wireshark网络分析实战

[以色列 Yoram Orzach / 古宏霞、孙余强 / 人民邮电出版社 / 2015-1 / 79.00元

本书采用步骤式为读者讲解了一些使用Wireshark来解决网络实际问题的技巧。 本书共分为14章,其内容涵盖了Wireshark的基础知识,抓包过滤器的用法,显示过滤器的用法,基本/高级信息统计工具的用法,Expert Info工具的用法,Wiresahrk在Ethernet、LAN及无线LAN中的用法,ARP和IP故障分析,TCP/UDP故障分析,HTTP和DNS故障分析,企业网应用程序行......一起来看看 《Wireshark网络分析实战》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具