美文网首页
Android中ViewStub原理解析

Android中ViewStub原理解析

作者: dashingqi | 来源:发表于2020-03-09 00:50 被阅读0次

    本文主要从如下几点来学习ViewStub

    • ViewStub是啥
    • ViewStub的属性解析
    • ViewStub的代码实操
    • ViewStub的原理解析
    • ViewStub实际中一般常用的情景
    • ViewStub的两个小问题

    ViewStub是啥

    在介绍ViewStub是啥之前,我们了解下为什么要用ViewStub

    在我们日常开发中,有些布局或者控件一开始并不需要显示,是根据业务场景来控制显示状态的,我们通常的做法就是在xml文件设置不可见,然后通过setVisibility()方法来更新它的可见性,但是这样做会对程序的性能有一定的影响,我们知道加载布局的时候有两个瓶颈 一个是将xml文件加载到内存中是IO操作,通过反射或者到View的对象反射操作是耗时操作,这两者都是耗时的操作。

    基于上面的业务情况,出现了ViewStub的标签,它是按需加载View,能够很容易实现布局的懒加载来提升程序的性能。

    • ViewStub 继承于 View

    • 看下源码声明

      /*
       * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
       * layout resources at runtime.
       * When a ViewStub is made visible, or when {@link #inflate()}  is invoked, the layout resource 
       * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
       * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
       * {@link #inflate()} is invoked.
       */
      
      
      //ViewStub是一个不可见的,宽高为0的View,可用于在程序运行的时候延迟  加载布局资源的(用于实现布局资源的“懒加载”)
      
      //当使ViewStub可见或者调用inflate方法,可以使布局资源被加载!
      
      //ViewStub存在于视图的层级中直到setVisibility()方法或者inflate()方法被执行后,ViewStub相关的资源就会被加载并在控件层级结构中代替ViewStub,同时ViewStub会从控件中移除。
      
      

    ViewStub的属性解析

    android:id
    • ViewStub在布局文件中ID,用于在代码中访问
    • View共有的
    android:layout
    • 在显示ViewStub时真正加载并且显示的布局文件
    • ViewStub特有的
    android:inflatedId
    • 真正布局加载后的布局Id
    • ViewStub特有的

    ViewStub的代码实操

    Xml资源文件
    • 代码如下

      <ViewStub android:id="@+id/stub"       
        android:inflatedId="@+id/subTree"                                        
        android:layout="@layout/mySubTree              
        android:layout_width="120dp"
        android:layout_height="40dp"/>
      
    Java代码
    • 代码如下

      //1,通过id找到ViewStub,得到ViewStub对象
      ViewStub myViewStub = (ViewStub)findViewById(R.id.stub);
      if(myViewStub!=null){
        //2,通过inflate方法加载真正的布局View
        View myInflatedView = myViewStub.inflate();
        //3,找到相应的控件
       TextView myTextView =  myInflatedView.findViewById(R.id.my_text_view);
      }
      

    ViewStub的原理解析

    ViewStub的构造方法
    • 代码如下

      public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
              super(context);
      
              final TypedArray a = context.obtainStyledAttributes(attrs,
                      R.styleable.ViewStub, defStyleAttr, defStyleRes);
              saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
                      defStyleRes);
      
              //获取在xml文件中定义的inflatedId属性
              mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
              //获取到xml文件中定义的layout属性
              mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
              //获取xml文件中定义的id属性
              mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
              a.recycle();
      
              //设置ViewStub直接不显示
              //也可以看出来,你在xml文件中如何控制它的显示属性,都是不显示的
              setVisibility(GONE);
              // 设置ViewStub不尽兴绘制
              setWillNotDraw(true);
          }
      
    ViewStub的onMeasure和onDraw方法
    • 代码如下

      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
              //设置宽和高都为0 也就是控件的大小为0
              setMeasuredDimension(0, 0);
          } 
      
      @Override
          public void draw(Canvas canvas) {
              //不进行任何绘制
          }
      
          @Override
          protected void dispatchDraw(Canvas canvas) {
          }
      
    ViewStub的inflate方法
    • 代码如下

      public View inflate() {
              //获取ViewStub在布局文件中的父布局
              final ViewParent viewParent = getParent();
      
              if (viewParent != null && viewParent instanceof ViewGroup) {
                  //mLayoutResource 就是属性 layout指定的真正要加载的布局
                  if (mLayoutResource != 0) {
                      final ViewGroup parent = (ViewGroup) viewParent;
                      //把真正要显示的View布局文件渲染成View对象并且给返回
                      final View view = inflateViewNoAdd(parent);
                      //将ViewStub从布局文件结构中移除,并且把渲染好的View添加到ViewStub所处的位置。
                      replaceSelfWithView(view, parent);
      
                      mInflatedViewRef = new WeakReference<>(view);
                      if (mInflateListener != null) {
                          //保存当前View对象的弱引用,方便其他地方使用
                          mInflateListener.onInflate(this, view);
                      }
      
                      //返回创建的View对象
                      return view;
                  } else {
                      //当我们没有为ViewStub指定layut属性时,会走这个case,抛出异常
                      throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
                  }
              } else {
                  //第一个调用ViewStub的inflate方法后,会把ViewStub从布局文件结构中移除,就是没有了ViewGroup了
                  // 当第二次调用ViewStub的inflate方法后,会走这个case,抛出异常。
                  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);
              }
              // 把真正要显示的布局文件渲染成View对象
              final View view = factory.inflate(mLayoutResource, parent, false);
      
              //  mInflatedId 对应 android:inflatedId 如果指定了就为渲染好的View给设置进去
              if (mInflatedId != NO_ID) {
                  view.setId(mInflatedId);
              }
              return view;
          }
      
      private void replaceSelfWithView(View view, ViewGroup parent) {
              // 获取ViewStub在父布局中所处在的位置
              final int index = parent.indexOfChild(this);
              // 将ViewStub从父布局中移除
              parent.removeViewInLayout(this);
              // 获取ViewStub的布局参数
              final ViewGroup.LayoutParams layoutParams = getLayoutParams();
              // 当设置了布局参数(例如 android:width="50dp",height="50dp")
              if (layoutParams != null) {
                  // 将渲染好的View连同ViewStub的布局参数添加到ViewStub所处的位置
                  parent.addView(view, index, layoutParams);
              } else {
                  //将渲染好的View添加到ViewStub所处的位置
                  parent.addView(view, index);
              }
          }
      
    • 在我看来inflate方法主要的就是inflateViewNoAdd和replaceSelfWithView方法

    • inflateViewNoAdd:获取到布局渲染器将真正需要展示的布局文件渲染成View并且给返回。

    • replaceSelfWithView:将ViewStub从布局文件结构中移除,同时把渲染好的View添加到ViewStub之前所处的位置

    • 之后把渲染好的View的弱引用给存储起来。方便在setVisibility()方法中使用。

    ViewStub的setVisibility()方法
    • 代码如下

      public void setVisibility(int visibility) {
              // 纵观全局,mInflatedViewRef只有在inflate方法中初始化了,
              // 当真正的布局文件被加载之后
              if (mInflatedViewRef != null) {
                  // 获取到当前的View
                  View view = mInflatedViewRef.get();
                  if (view != null) {
                      //操纵当前View的可见行
                      view.setVisibility(visibility);
                  } else {
                      throw new IllegalStateException("setVisibility called on un-referenced view");
                  }
              } else {
                  //没有调用inflate的话,会设置可见性
                  super.setVisibility(visibility);
                  //当 当前设置可见性为 VISIBLE或者INVISIBLE的时候,会调用inflate方法。
                  if (visibility == VISIBLE || visibility == INVISIBLE) {
                      inflate();
                  }
              }
          }
      

    ViewStub实际中一般常用情景

    • 比如我们在无数据或者网络错粗的时候,需要单独显示一个布局,那么这个布局就可以用ViewStub。

    ViewStub的几个问题

    两次调用ViewStub的inflate方法会怎么样?
    • 我们知道第一次调用inflate方法的时候,会将ViewStub从布局文件结构中移除。
    • 当第二次调用的时候,ViewStub已经没有父控件了,当做错误检查的时候,会抛出异常。
    在xml文件中为ViewStub设置了 android:visibility="visible" 属性,ViewStub中真正要显示的View会显示嘛?
    • 不会,我们在ViewStub的构造方法中,有看到它默认调用了setVisibility(GONE)
    • 所以你在xml文件中如何操作可见行,都是没法显示的。

    相关文章

      网友评论

          本文标题:Android中ViewStub原理解析

          本文链接:https://www.haomeiwen.com/subject/mecedhtx.html