布局优化之ViewStub源码分析

1.前言

    对于Android的单线程模型来说,把控好主线程中的操作是至关重要的。布局复杂了,很容易引起卡顿,或者某些情况下界面展示慢的情况,我们都知道ViewStub能实现懒加载,减少布局渲染时间,那么它究竟用了什么方法来实现的,这里做一个学习记录,加深印象。

2.如何优化布局

    ViewStub是不可见,宽高为0的View,当调用inflate()或者setVisibility(int visibility)后进行加载。这样就可以在需要的时候加载,不用在布局绘制的时候就一次性就全部加载渲染出来,可以缓解主线程的压力。

3.如何使用

//1.在布局中将原本需要懒加载的layout用下面这种方式替换,layout属性指定的是将要加载的布局,inflatedId是layout指定的布局被加载替换ViewStub后指定的id

<ViewStub android:id="@+id/stub"
          android:inflatedId="@+id/subTree"
          android:layout="@layout/mySubTree"
          android:layout_width="120dip"
          android:layout_height="40dip" />

//2.在需要加载的时机调用

ViewStub stub = findViewById(R.id.stub);
View inflated = stub.inflate();

如上面的代码,调用stub.inflate()后layout指向的布局将被加载到ViewStub所在的位置,也就是ViewStub的父布局中。

4.源码分析

public final class ViewStub extends View {
    private int mInflatedId;
    private int mLayoutResource;
    private WeakReference<View> mInflatedViewRef;
    private LayoutInflater mInflater;
    private OnInflateListener mInflateListener;
	...
    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context);
        //(1)
        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);
        setWillNotDraw(true);
    }
    ...
    //(2)
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0);
    }
    //(3)
    public View inflate() {
        final ViewParent viewParent = getParent();
        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final View view = inflateViewNoAdd(parent);
                replaceSelfWithView(view, parent);
                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");
        }
    }
     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;
    }
     private void replaceSelfWithView(View view, ViewGroup parent) {
        final int index = parent.indexOfChild(this);
        parent.removeViewInLayout(this);
        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }
    //(4)
    public void setOnInflateListener(OnInflateListener inflateListener) {
        mInflateListener = inflateListener;
    }
}

  • (1)从frameworks\base\core\res\res\values\attrs.xml文件中可以看出,这里是在解析布局中的inflatedId和layout属性值。
    <declare-styleable name="ViewStub">
        <attr name="id" />
        <attr name="layout" format="reference" />
        <attr name="inflatedId" format="reference" />
    </declare-styleable>
  • (2)测量时宽高都设置为0了,所以不可见宽高为0
  • (3)inflate是ViewStub实现延迟加载的关键,调用时会先获取父布局,然后用LayoutInflator加载布局,并没有什么特殊操作,加载完了会用布局中配置的inflateId作为指定layout的id。replaceSelfWithView()方法也是简单粗暴,直接从父布局中将ViewStub移除掉,然后直接addView()方式加上来,因为addView()中会再调用requestLayout(),这样就能addView()后布局被更新。
  • (4)如果要监听ViewStub被加载完成的回调,可以setOnInflateListener():
        viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                
            }
        });

5.需要注意的问题

  • 1.如果定义了ViewStub的inflateId属性,那么在inflate()后layout属性指定的layout的id会被动态的替换成inflateId的值;
  • 2.ViewStub的layout布局中不允许使用merge标签,具体原因待后面分析
  • 3.NPE问题:如果ViewStub被inflate过一次,会从xml解析的View树中移除掉,第二次调用findViewById()查找到是null
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TechMix

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值