Android布局加载优化之懒汉模式-ViewStub

Android布局加载优化之懒汉模式-ViewStub

谈到布局优化,通常都会想到标签include和merge。简单先说下这两个标签的好处:include可以减少布局文件内容,比如,在我们需要在多个布局中都添加标题栏时,可以创建一个单独的xml文件,添加标题内容到该xml中,然后在需要用到的目标布局里面用include标签添加已创建好的标题栏;merge可以减少多余的包含控件。两者一起使用,可以减少布局的层级结构,从而减少绘制的工作量。注意:include标签只支持android:layout_with和android:layout_height属性,其它属性是不支持的。O了~开始这章的重点,

很多时候,在初始化程序时都是一次性把布局加载进来,使得绘制工作量变多,导致程序初始化性能降低。比如:网络请求错误时的布局、一些评论显示的布局(未有评论前)等等,这些布局我们并不经常用到,能不能按需加载?可以,那就是ViewStub。

1、为什么用ViewStub能提升程序初始化性能

ViewSub继承于View,本身是一个视图。但其有一个特点,就是轻量级的,宽和高都为0,并且它不参与绘制和任何的布局,为什么这么说,一起看下ViewStub源码。

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context);

        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ViewStub, defStyleAttr, defStyleRes);
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
        a.recycle();

        setVisibility(GONE);  //在初始构造方法中就已经设置了GONE
        setWillNotDraw(true);
    }
... ...//省略部分代码

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0); //测量中设置宽和高为0
    }

    @Override
    public void draw(Canvas canvas) {
    //无任何绘制
    }

根据上面的代码,我们可以知道,声明一个ViewStub时,只是起到了一个占位的作用,并没有实际的绘制和布局。

2、怎么去使用ViewStub,做到按需加载,一起看下下面一段简单描述。

首先,主布局声明一个ViewStub:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.rickybin.viewstubtest.MainActivity">

   <ViewStub
       android:id="@+id/textStub"
       android:inflatedId="@+id/text_inflate_id" //设置目标布局ID
       android:layout="@layout/text_layout"   //获取目标布局
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />

</RelativeLayout>

text_layout的内容:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/textId"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="ViewStub的目标TextView" />

activity的代码:

public class MainActivity extends AppCompatActivity {

    private ViewStub textStub;
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        textStub = (ViewStub) findViewById(R.id.textStub);
        if (mTextView != null){
            mTextView = (TextView) textStub.inflate(); //加载相应的TextView
        }
        mTextView.setText("获取textview");
    }
}

在activity代码initView()中判断mTextView是否为null,调用inflate()加载,这时就会把“@layout/text_layout”实例化出来。ViewStub还有一种方法加载布局,就是用setVisibility去加载,其实这个方法中最终还是调用了inflate()加载布局。再看看ViewStub源码中setVisibility的定义

    public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {  //是否已加载目标布局
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {   //加载目标布局
            super.setVisibility(visibility);//先由父类setVisibility再inflate
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }

既然都是用inflate()加载,那我们一起看看在inflate里具体做了什么:

public View inflate() {
        final ViewParent viewParent = getParent(); //获取ViewStub所在的父视图

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final View view = inflateViewNoAdd(parent);
                replaceSelfWithView(view, parent);  //替换ViewStub自身

                //把View赋值给mInflatedViewRef
                mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
            } else {
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }

//获取LayoutInflater指定的布局,赋值给变量View
private View inflateViewNoAdd(ViewGroup parent) {
        final LayoutInflater factory;
        if (mInflater != null) {
            factory = mInflater;
        } else {
            factory = LayoutInflater.from(mContext);
        }
        final View view = factory.inflate(mLayoutResource, parent, false);

        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }
        return view;
    }

replaceSelfWithView()方法

private void replaceSelfWithView(View view, ViewGroup parent) {
        final int index = parent.indexOfChild(this);
        //移除父布局中的ViewStub
        parent.removeViewInLayout(this); 

        //这里是用ViewStub的android:with和android:height设置目标布局
        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        //添加目标布局到父布局中
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }

从上面我们可以分析得到结果,ViewStub去加载目标布局时,其实是先把LayoutInflater指定的layout实例化后赋值给变量View,然后ViewStub自身会从父视图中移除,再把变量View添加进父视图中。也就是说ViewStub只能inflate一次,之后ViewStub对象就会置空,不再是整个布局结构中的一部分了。要注意一点,如果ViewStub设置了inflatedId,将会赋值给变量View的id。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值