ViewStub你真的了解吗

目录介绍

  • 01.什么是ViewStub
  • 02.ViewStub构造方法
  • 03.inflate()方法解析
  • 04.WeakReference使用
  • 05.ViewStub为什么无大小
  • 06.ViewStub为什么不绘制
  • 07.能够屡次inflate()吗
  • 08.ViewStub不支持merge
  • 09.ViewStub使用场景
  • 10.ViewStub总结分析

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深刻知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,固然也在工做之余收集了大量的面试题,长期更新维护而且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 连接地址:https://github.com/yangchong2...
  • 若是以为好,能够star一下,谢谢!固然也欢迎提出建议,万事起于忽微,量变引发质变!

01.什么是ViewStub

  • ViewStub 是一个看不见的,没有大小,不占布局位置的 View,能够用来懒加载布局。
  • 当 ViewStub 变得可见或 inflate() 的时候,布局就会被加载(替换 ViewStub)。所以,ViewStub 一直存在于视图层次结构中直到调用了 setVisibility(int)inflate()
  • 在 ViewStub 加载完成后就会被移除,它所占用的空间就会被新的布局替换。

02.ViewStub构造方法

  • 先来看看构造方法:java

    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方法。android

    // 复写了 setVisibility(int) 方法
    @Override
    @android.view.RemotableViewMethod
    public void setVisibility(int visibility) {
        // private WeakReference<View> 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();
            }
        }
    }

03.inflate()方法解析

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

    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>(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使用特色github

    • ViewStub只能被Inflate一次,inflate以后ViewStub对象就会被置为空。即某个被ViewStub指定的布局被Inflate后,就不可以再经过ViewStub来控制它了。
    • ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,固然也能够把View写在某个布局文件中。

04.WeakReference使用

  • 使用了弱引用管理对象的建立,代码以下所示面试

    • 在这里使用了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");
            }
        } 
    }

05.ViewStub为什么无大小

  • 首先先看一段源码,以下所示:canvas

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0);
    }
    
    @Override
    public void draw(Canvas canvas) {
    }
    
    @Override
    protected void dispatchDraw(Canvas canvas) {
    }
  • 有没有以为很不同凡响markdown

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

06.ViewStub为什么不绘制

  • 具体看一下setWillNotDraw(true)方法,代码以下:网络

    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }
  • View中,对于WILL_NOT_DRAW是这样定义的:app

    /**
     * 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.
*/
static final int WILL_NOT_DRAW = 0x00000080;
```
  • 设置WILL_NOT_DRAW以后,onDraw()不会被调用,经过略过绘制的过程,优化了性能。在ViewGroup中,初始化时设置了WILL_NOT_DRAW,代码以下:async

    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相关的代码。

07.能够屡次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");
        }
    }
  • 其实也能够用一张图来理解它,以下所示,摘自网络

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

08.ViewStub不支持merge

  • 不能引入包含merge标签的布局到ViewStub中。不然会报错:android.view.InflateException: Binary XML file line #1: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true

09.ViewStub使用场景

  • 通常的app中大多有这么一个功能,当加载的数据为空时显示一个数据为空的视图、在数据加载失败时显示加载失败对应的UI,当没有网络的时候加载没有网络的UI,并支持点击重试会比白屏的用户体验更好一些。俗称,页面状态切换管理……通常来讲,加载中、加载失败、空数据等状态的UI风格,在App内的全部页面中须要保持一致,也就是须要作到全局统一,也支持局部定制。
  • ViewStub的优点在于在上面的场景中,并不必定须要把全部的内容都展现出来,能够隐藏一些View视图,待用户须要展现的时候再加载到当前的Layout中,这个时候就能够用到ViewStub这个控件了,这样能够减小资源的消耗,使最初的加载速度变快。
  • 那么就有了以前开发使用的状态管理器开源库,就是采用了ViewStub这个控件,让View状态的切换和Activity完全分离开。用builder模式来自由的添加须要的状态View,能够设置有数据,数据为空,加载数据错误,网络错误,加载中等多种状态,而且支持自定义状态的布局。能够说彻底不影响性能……

10.ViewStub总结分析

  • 分析源码的原理,无论认识到哪一步,最终的目标仍是在运用上,即把看源码得到的知识用到实际开发中,那么关于ViewStub的使用技巧,具体能够看个人状态管理器案例,连接地址:https://github.com/yangchong2...
  • 欢迎你的star,这也是开源和写博客的源源动力,哈哈

ViewStub状态管理库:https://github.com/yangchong2...

开源博客大汇总:https://github.com/yangchong2...

相关文章
相关标签/搜索