博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ViewStub你肯定听过,但是这些细节了解吗?
阅读量:2456 次
发布时间:2019-05-10

本文共 8593 字,大约阅读时间需要 28 分钟。

1什么是ViewStub

1. ViewStub 是一个看不见的,没有大小,不占布局位置的 View,可以用来懒加载布局。

2. 当 ViewStub 变得可见或 inflate() 的时候,布局就会被加载(替换 ViewStub)。因此,ViewStub 一直存在于视图层次结构中直到调用了 setVisibility(int) 或 inflate()。

3. 在 ViewStub 加载完成后就会被移除,它所占用的空间就会被新的布局替换。

在这分享一份整理了2个月的Android进阶面试解析笔记文档,包括了知识点笔记和高频面试问题解析及部分知识点视频讲解给大家!为了不影响阅读,在这以图片展示部分内容于目录截图,有需要的朋友麻烦点赞后点击下面在线链接获取免费领取方式吧!

2ViewStub构造方法

先来看看构造方法:

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {    super(context);    final TypedArray a = context.obtainStyledAttributes(attrs,            R.styleable.ViewStub, defStyleAttr, defStyleRes);    // 要被加载的布局 Id    mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);    // 要被加载的布局    mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);    // ViewStub 的 Id    mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);    a.recycle();    // 初始状态为 GONE    setVisibility(GONE);    // 设置为不会绘制    setWillNotDraw(true);}

接下来就看看关键的方法,然后看看初始化状态setVisibility方法。

// 复写了 setVisibility(int) 方法@Override@android.view.RemotableViewMethodpublic void setVisibility(int visibility) {    // private WeakReference
mInflatedViewRef; // mInflatedViewRef 是对布局的弱引用 if (mInflatedViewRef != null) { // 如果不为 null,就拿到懒加载的 View View view = mInflatedViewRef.get(); if (view != null) { // 然后就直接对 View 进行 setVisibility 操作 view.setVisibility(visibility); } else { // 如果为 null,就抛出异常 throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { super.setVisibility(visibility); // 之前说过,setVisibility(int) 也可以进行加载布局 if (visibility == VISIBLE || visibility == INVISIBLE) { // 因为在这里调用了 inflate() inflate(); } }}

3inflate()方法解析

核心来了,平时用的时候,会经常调用到该方法。inflate() 是关键的加载实现,代码如下所示:

public View inflate() {    // 获取父视图    final ViewParent viewParent = getParent();    if (viewParent != null && viewParent instanceof ViewGroup) {        // 如果没有指定布局,就会抛出异常        if (mLayoutResource != 0) {            // viewParent 需为 ViewGroup            final ViewGroup parent = (ViewGroup) viewParent;            final LayoutInflater factory;            if (mInflater != null) {                factory = mInflater;            } else {                // 如果没有指定 LayoutInflater                factory = LayoutInflater.from(mContext);            }            // 获取布局            final View view = factory.inflate(mLayoutResource, parent,                    false);            // 为 view 设置 Id            if (mInflatedId != NO_ID) {                view.setId(mInflatedId);            }            // 计算出 ViewStub 在 parent 中的位置            final int index = parent.indexOfChild(this);            // 把 ViewStub 从 parent 中移除            parent.removeViewInLayout(this);            // 接下来就是把 view 加到 parent 的 index 位置中            final ViewGroup.LayoutParams layoutParams = getLayoutParams();            if (layoutParams != null) {                // 如果 ViewStub 的 layoutParams 不为空                // 就设置给 view                parent.addView(view, index, layoutParams);            } else {                parent.addView(view, index);            }            // mInflatedViewRef 就是在这里对 view 进行了弱引用            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"); }}

Inflate使用特点

1. ViewStub只能被Inflate一次,inflate之后ViewStub对象就会被置为空。即某个被ViewStub指定的布局被Inflate后,就不能够再通过ViewStub来控制它了。

2. ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。

4WeakReference使用

使用了弱引用管理对象的创建,代码如下所示

在这里使用了get方法

@Override@android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")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 {    }}

在这里创建了弱引用对象

public View inflate() {    final ViewParent viewParent = getParent();    if (viewParent != null && viewParent instanceof ViewGroup) {        if (mLayoutResource != 0) {            mInflatedViewRef = new WeakReference<>(view);            return view;        } else {            throw new IllegalArgumentException("ViewStub must have a valid layoutResource");        }    } }

5ViewStub为何无大小

首先先看一段源码,如下所示:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    setMeasuredDimension(0, 0);}@Overridepublic void draw(Canvas canvas) {}@Overrideprotected void dispatchDraw(Canvas canvas) {}

有没有觉得很与众不同

draw和dispatchDraw虽然重写了,但是看代码却都是什么也不做!并且onMeasure还什么也不做,直接setMeasuredDimension(0,0);来把view区域设置位0,原来一个ViewStub虽然是一个view,却是一个没有任何显示内容,也不显示任何内容的特殊view,并且对layout在加载时候不可见的。

6ViewStub为何不绘制

具体看一下setWillNotDraw(true)方法,代码如下:

public void setWillNotDraw(boolean willNotDraw) {    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}

View中,对于WILL_NOT_DRAW是这样定义的:

/** * This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be * called and further optimizations will be performed. It is okay to have * this flag set and a background. Use with DRAW_MASK when calling setFlags. * {@hide} */static final int WILL_NOT_DRAW = 0x00000080;

设置WILL_NOT_DRAW之后,onDraw()不会被调用,通过略过绘制的过程,优化了性能。在ViewGroup中,初始化时设置了WILL_NOT_DRAW,代码如下:

public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {    super(context, attrs, defStyleAttr, defStyleRes);    initViewGroup();    initFromAttributes(context, attrs, defStyleAttr, defStyleRes);}private void initViewGroup() {    // ViewGroup doesn't draw by default    if (!debugDraw()) {        setFlags(WILL_NOT_DRAW, DRAW_MASK);    }    mGroupFlags |= FLAG_CLIP_CHILDREN;    mGroupFlags |= FLAG_CLIP_TO_PADDING;    mGroupFlags |= FLAG_ANIMATION_DONE;    mGroupFlags |= FLAG_ANIMATION_CACHE;    mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;    if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {        mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;    }    setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);    mChildren = new View[ARRAY_INITIAL_CAPACITY];    mChildrenCount = 0;    mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;}

所以,在写自定义布局时,如果需要调用onDraw()进行绘制,则需要在初始化时候,调用setWillNotDraw(false)。若是想要更进一步阅读View中WILL_NOT_DRAW的相关源码,可以去看下PFLAG_SKIP_DRAW相关的代码。

7可以多次inflate()吗

ViewStub对象只可以Inflate一次,之后ViewStub对象会被置为空。同时需要注意的问题是,inflate一个ViewStub对象之后,就不能再inflate它了,否则会报错:ViewStub must have a non-null ViewGroup viewParent。

其实看一下源码就很好理解:

public View inflate() {    //获取viewStub的父容器对象    final ViewParent viewParent = getParent();    if (viewParent != null && viewParent instanceof ViewGroup) {        if (mLayoutResource != 0) {            final ViewGroup parent = (ViewGroup) viewParent;            //这里是加载布局,并且给它设置id            //布局的加载是通过LayoutInflater解析出来的            final View view = inflateViewNoAdd(parent);            //这行代码很重要,下面会将到            replaceSelfWithView(view, parent);            //使用弱引用            mInflatedViewRef = new WeakReference<>(view);            if (mInflateListener != null) {                mInflateListener.onInflate(this, view);            }            return view;        } else {            //如果已经加载出来,再次inflate就会抛出异常呢            throw new IllegalArgumentException("ViewStub must have a valid layoutResource");        }    } else {        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");    }}

其实也可以用一张图来理解它,如下所示,摘自网络

也就是说,一旦调用inflate上面的方法后ViewStub就会变成null了,因此使用该对象特别需要注意空指针问题。

8ViewStub不支持merge

不能引入包含merge标签的布局到ViewStub中。否则会报错:

android.view.InflateException: Binary XML file line #1:  can be used only with a valid ViewGroup root and attachToRoot=true

9ViewStub使用场景

一般的app中大多有这么一个功能,当加载的数据为空时显示一个数据为空的视图、在数据加载失败时显示加载失败对应的UI,当没有网络的时候加载没有网络的UI,并支持点击重试会比白屏的用户体验更好一些。

俗称,页面状态切换管理……一般来说,加载中、加载失败、空数据等状态的UI风格,在App内的所有页面中需要保持一致,也就是需要做到全局统一,也支持局部定制。

ViewStub的优势在于在上面的场景中,并不一定需要把所有的内容都展示出来,可以隐藏一些View视图,待用户需要展示的时候再加载到当前的Layout中,这个时候就可以用到ViewStub这个控件了,这样可以减少资源的消耗,使最初的加载速度变快。

那么就有了之前开发使用的状态管理器开源库,就是采用了ViewStub这个控件,让View状态的切换和Activity彻底分离开。用builder模式来自由的添加需要的状态View,可以设置有数据,数据为空,加载数据错误,网络错误,加载中等多种状态,并且支持自定义状态的布局。可以说完全不影响性能……

10ViewStub总结分析

分析源码的原理,不管认识到哪一步,最终的目标还是在运用上,即把看源码获得的知识用到实际开发中,

转载地址:http://wochb.baihongyu.com/

你可能感兴趣的文章
黄金时代 李银河_一周前五篇文章:开发人员的黄金时代
查看>>
一周前五篇文章:我们非常喜欢Raspberry Pi B +
查看>>
天气小程序 github_一周前五名:GitHub上的免费书籍和开放多云的天气
查看>>
排名前5的文章:适用于您的食物的Linux,面向Docker用户的提示等
查看>>
您不懂JavaScript,但您应该
查看>>
闪存驱动器_仅使用闪存驱动器即可测试Linux
查看>>
锡晶须啤酒厂打破了秘密食谱的趋势
查看>>
openstack 存储_软件定义的存储对OpenStack意味着什么
查看>>
taiga项目管理_Taiga,一种注重可用性的新型开源项目管理工具
查看>>
Google Analytics(分析)的前3种开源替代品
查看>>
facebook 开源_一周前五篇文章:Facebook,Samsung和HP的开源
查看>>
为Docker带来新的安全功能
查看>>
在OpenStack上试用WordPress 4.0
查看>>
本周最热门的5篇文章:WordPress 4.0,FarmBot和Docker安全性
查看>>
资产发现开源工具_开源工具可帮助孩子们发现数字创造力
查看>>
ansible开源ui_本周热门文章5:丰富的开源工具! 和Ansible的崛起
查看>>
arduino智能锁_学生使用Arduino构建智能设备和科学仪器
查看>>
开放源代码_开放式代码本质的10种方式
查看>>
开源教学系统_开源教学改变了我的生活
查看>>
在学校游说开源和Linux
查看>>