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/yangchong211/YCBlogs
  • 假定觉得好,能够star一下,谢谢!当然也欢迎提出主张,万事起于忽微,突变引起突变!

01.什么是ViewStub

  • ViewStub 是一个看不见的,没有巨细,不占布局方位的 View,可拿来懒加载布局
  • 当 ViewStub 变得可见或 inflate() 的时分,布局就会被加载(替换 ViewStub)。因而,ViewStub 一向存在于视图层次结构中直到调用了 setVisibility(int)inflate()
  • 在 ViewStub 加载结束后就会被移除,它所占用的空间就会被新的布局替换。

02.ViewStub结构办法

  • 先来看看结构办法:

    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.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() 是要害的加载结束,代码如下所示:

    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运用特征

    • 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为何无巨细

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(0, 0);
    }
    @Override
    public void draw(Canvas canvas) {
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
    }
  • 有没有觉得很异乎寻常

    • 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是这样界说的:

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

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

    • ViewStub你真的了解吗
  • 也便是说,一旦调用inflate上面的办法后ViewStub就会变成null了,因而运用该方针特别必需求分外留神空指针问题。

08.ViewStub不支撑merge

  • 不能引进包括merge标签的布局到ViewStub中。不然会报错:android.view.InflateException: Binary XML file line #1: 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/yangchong211/YCStateLayout
  • 欢迎你的star,这也是开源和写博客的源源动力,哈哈

ViewStub状况处理库:https://github.com/yangchong211/YCStateLayout

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