ViewStub源码分析

辛辛苦苦写了一篇博客,发现简书markdown插入代码居然没有行序,我都懵逼了。看来这个编辑器对程序猿不太友好啊,哈哈。
如果想要更好的阅读体验可以去我的CSDN博客https://blog.csdn.net/u012814441/article/details/80524501

1、ViewStub的使用

我们先来回忆一下平时的怎么使用ViewStub的。首先定义一个供ViewStub引用的布局文件,取名layout_view_stub.xml。代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:text="hello"
        android:layout_height="wrap_content" />
    <TextView
        android:layout_width="wrap_content"
        android:text="world"
        android:layout_height="wrap_content" />
</RelativeLayout>

接着定义一个布局文件,取名为activity_main.xml。代码如下所示

<?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:id="@+id/one"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="clickThree"
        android:text="显示" />
    <Button
        android:id="@+id/two"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="clickThree"
        android:text="隐藏" />
    <Button
        android:id="@+id/three"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="clickThree"
        android:text="加载" />
    <ViewStub
        android:id="@+id/vs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/layout_view_stub"/>
</LinearLayout>

在28行引用layout_view_stub布局资源。再接着写写逻辑代码

final ViewStub viewStub = findViewById(R.id.vs);
Button button = findViewById(R.id.two);
button.setOnClickListener(new View.OnClickListener() {
    @Override
  public void onClick(View v) {
        viewStub.setVisibility(View.GONE);
  }
});

Button button1 = findViewById(R.id.one);
button1.setOnClickListener(new View.OnClickListener() {
    @Override
  public void onClick(View v) {
        viewStub.setVisibility(View.VISIBLE);
  }
});

Button button2 = findViewById(R.id.three);
button2.setOnClickListener(new View.OnClickListener() {
    @Override
  public void onClick(View v) {
        viewStub.inflate();
  }
});

效果如图


<font color="#7f7f7f" style="box-sizing: border-box; outline: none !important;">git动画</font>

2、ViewStub的疑问

  • 通过上面的演示我们已经知道一些基本用法,接下来针对平时使用提出几个问题。
    1.为什么ViewStub的能够优化布局?
    2.为什么ViewStub引用的布局的根节点不能为merge?
    3.为什么ViewStub的inflate方法连续调用两次就抛出异常?
    4.inflate方法和setVisibility方法有什么区别?

3、ViewStub源码分析

  • 首先我们需要知道什么是ViewStub?根据官方对ViewStub的定义。

它是一种不可见、零大小,可以在APP运行时动态加载布局资源的视图。

  • ViewStub是一个继承自View的控件,我们来看看它的继承结构


    这里写图片描述

    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);
        setWillNotDraw(true);
    }

先是通过3~8行完成相关属性初始化。第9行setVisibility(GONE)将ViewStub设置为不可见状态。第10行设置为不绘制视图。完成了初始化之后按照View的绘制流程,接着就会回调onMeasure方法。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
    }

在onMeasure方法中,将自身的宽度和高度设置为0。并且重写了draw方法,不对此视图进行渲染绘制。通过这两个方法我们可以解决第一个问题。"为什么ViewStub的能够优化布局?"
  当我们在布局中使用ViewStub时,此时它是0大小,不进行绘制的控件。其初始化代价小到可以忽略(真正的布局只有在调用infalte或setVisibility才会被加载),这样能够加快整个布局的初始化。因此可以说是优化了布局性能。
  在完成了整个ViewStub的初始化之后,我们需要主动调用inflate方法来加载布局。

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");
        }
    }

首先看2、3行,拿到了该视图的父节点(LinearLayout)之后,判断其是否为空且是否为ViewGroup。如果为空且不是ViewGroup就会抛出IllegalStateException异常。
  第4~14行,先判断mLayoutResource是否有引用布局资源,如果没有去到14行抛出IllegalArgumentException异常。如果有的话就调用inflateViewNoAdd方法,获取加载布局资源view。接着调用replaceSelfWithView方法,将ViewStub自身替换为view。再初始化一个弱引用来存放view对象(方便用户调用setVisibility方法复用对象)。最后9~10行如果你事先注册了setOnInflateListener方法,此时会回调onInflate方法告诉你加载布局资源成功。
  至此整个inflate方法执行完毕。但是我们还有3个疑问没有解决。先解决第2个问题“为什么ViewStub引用的layout布局的根节点不能为merge?”,先看inflateViewNoAdd方法

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;
    }

重点看第8行,在inflate加载的时候检查到mLayoutResource布局根结点含有merge就会抛出InflateException异常。这实际牵涉到LayoutInflate源码分析,这里就不再解释太深入了。我们只要知道ViewStub引用的布局根结点不能使用merge标签就OK了。
  接着解决第3个问题“为什么ViewStub的inflate方法连续调用两次就抛出异常?”。先看看replaceSelfWithView源码

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);
        }
    }

在第2行,首先查找该视图(也就是ViewStub)在父节点(LinearLayout)的位置(下标)。接着从父节点中删除该视图。在6~8行将view的视图添加到父节点(LinearLayout)中。
  当我们第一次调用inflate方法,总结起来就是下面这些步骤
1、先是通过ViewStub获取它的父节点
2、寻找ViewStub在父节点中的位置,并且使用index记录下来。
3、把ViewStub从父节点中删除
4、将view(加载的布局)放进父节点的index位置。
  当我们第二次调用inflate方法,这是由于ViewStub已经从父节点中删除,此时在第1步骤,获取父节点是一个空指针,因此会直接抛出一个IllegalStateException异常。
  最后一个问题“inflate方法和setVisibility方法有什么区别?”,要回答这个问题就分析它们的源码,由于inflate的源码已经分析过,那就看看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);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }

在2行看到一个很熟悉的变量mInflatedViewRef,这个就是刚才说过的弱引用对象,它里面存放了实例化的view对象。如果它为空就会执行第12行的infalte方法。不为空走3~8行代码,首先从弱引用对象中拿出view的实例,接着判断是否为空,如果为空则抛出IllegalStateException异常。不为空就按照传进来的visibility变量对view设置显示隐藏。
  总结:inflate只是用于布局资源,而setVisibility用于设置布局资源的可见性(显示或隐藏)内部帮你自动加载了布局资源。
要注意的是,当你调用了setVisibility(内部调用了inflate)之后就不能再调用inflate方法。

4、结束

第一次写源码分析的博客,如果有不对的地方欢迎指正,谢谢(>_<)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容