基于Android8.0分析源码的ViewStub源码解析

源码基于安卓8.0分析结果

创新互联公司基于分布式IDC数据中心构建的平台为众多户提供成都西云数据中心 四川大带宽租用 成都机柜租用 成都服务器租用。

ViewStub是一种不可见的并且大小为0的试图,它可以延迟到运行时才填充inflate 布局资源,当Viewstub设为可见或者是inflate的时候,就会填充布局资源,这个布局和普通的试图就基本上没有任何区别,比如说,加载网络失败,或者是一个比较消耗性能的功能,需要用户去点击才可以加载!从而这样更加的节约了性能。对安卓布局很友好!

ViewStub用法

 
 
 
 
  1.     android:padding="10dp"
  2.     android:background="@color/colorPrimary"
  3.     android:layout_gravity="center"
  4.     android:inflatedId="@+id/view_stub_inflateid"
  5.     android:id="@+id/view_stub"
  6.     android:layout="@layout/view_stub_imageview"
  7.     android:layout_width="wrap_content"
  8.     android:layout_height="wrap_content" />

这篇文章安卓代码、图片、布局、网络和电量优化说如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点 -----2018.6.7修正这个说法,以前我说的是错误的,根本上的原因是ViewStub设置了 inflateid ,这才是更本身的原因

在这里记住一点,如果在 ViewStub标签中设置了android:inflatedId="@+id/view_stub_inflateid",在layout布局中的根布局在设置android:id="@+id/view_stub_layout",这个id永远找出来都是为null的,原因会在下面说明

 
 
 
 
  1.     android:padding="10dp"
  2.     android:id="@+id/view_stub_layout"
  3.     android:src="@drawable/ic_launcher_background"
  4.     android:layout_width="match_parent"
  5.     android:layout_height="match_parent">
  6.     
  7.         android:text="如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点  -----2018.6.7修正这个说法,以前我说的是错误的,根本上的原因是ViewStub设置了 inflateid ,这才是更本身的原因"
  8.         android:layout_width="wrap_content"
  9.         android:layout_height="wrap_content" />
  10.     
  11.         android:layout_marginTop="20dp"
  12.         android:id="@+id/imageview"
  13.         android:padding="10dp"
  14.         android:src="@drawable/ic_launcher_background"
  15.         android:layout_width="match_parent"
  16.         android:layout_height="match_parent"/>

在activity或者是fragment中的使用,mViewStub.getParent()==null就是说明没有被填充,需要填充,如果填充了,那么它的parent不会为null,具体的骚操作,后续我介绍View的绘制流程的时候在详细说明。

第一种使用的方法

 
 
 
 
  1.  mViewStub = findViewById(R.id.view_stub);
  2.  if (null!=mViewStub.getParent()){
  3.  View inflate = mViewStub.inflate();
  4.  ....
  5. }

第二种方式:mViewStub.setVisibility(View.VISIBLE);和inflate()方法一样。

 
 
 
 
  1.  mViewStub = findViewById(R.id.view_stub);
  2.  if (null!=mViewStub.getParent()){
  3.    mViewStub.setVisibility(View.VISIBLE);
  4.  ....
  5. }

第三种方式,my_title_parent_id是layout的根布局的id

 
 
 
 
  1. mViewStub = findViewById(R.id.view_stub);
  2.  // 成员变量commLv2为空则代表未加载 commLv2 的id为ViewStub中的根布局的id
  3.  View commLv2=findViewById(R.id.my_title_parent_id);
  4. if ( commLv2 == null ) {
  5.    // 加载评论列表布局, 并且获取评论ListView,inflate函数直接返回ListView对象
  6.      commLv2 = (View)mViewStub.inflate();
  7.    } else {
  8.       // ViewStub已经加载
  9.   }

ViewStub构造方法,注意获取了几个值mInflatedId就是android:inflatedId="@+id/find_view_stub"这个值, mLayoutResource就是layout的resId,ViewStub的 mIDid。可以看出ViewStub是View的子类.

 
 
 
 
  1. public final class ViewStub extends View {
  2.  public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  3.         super(context);
  4.         final TypedArray a = context.obtainStyledAttributes(attrs,
  5.                 R.styleable.ViewStub, defStyleAttr, defStyleRes);
  6.         // TODO: 2018/5/23  ViewStub 中设置的标签id 如果设置了 这里就一定有值 mInflatedId!=NO_Id
  7.         mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
  8.         mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
  9.         mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
  10.         a.recycle();
  11.         //不可见
  12.         setVisibility(GONE);
  13.         // 设置不绘制
  14.         setWillNotDraw(true);
  15.     }
  16. }

在构造方法中:同时注意不可见 setVisibility(GONE); ,设置不绘制setWillNotDraw(true);,同时通过下面的方法看出,ViewStub 是一个大小为0的视图。

 
 
 
 
  1. @Override
  2.   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3.       // 宽高都为0   onMeasure的时候 宽高都为0
  4.       setMeasuredDimension(0, 0);
  5.   }
  6.   //todo 为啥这个控件 是个大小为0的控件 ,那是因为他妈的这里更不就没有画
  7.   @Override
  8.   public void draw(Canvas canvas) {
  9.   }
  10.   @Override
  11.   protected void dispatchDraw(Canvas canvas) {
  12.   }

关于inflate()方法

 
 
 
 
  1. public View inflate() {
  2.       // 1、获取ViewStub的parent view,也是目标布局根元素的parent view
  3.       final ViewParent viewParent = getParent();
  4.       if (viewParent != null && viewParent instanceof ViewGroup) {
  5.           if (mLayoutResource != 0) {
  6.               final ViewGroup parent = (ViewGroup) viewParent;
  7.               /// 2、加载目标布局  牛逼的方法
  8.               final View view = inflateViewNoAdd(parent);
  9.               // 3、将ViewStub自身从parent中移除
  10.               replaceSelfWithView(view, parent);
  11.               mInflatedViewRef = new WeakReference<>(view);
  12.               if (mInflateListener != null) {
  13.                   mInflateListener.onInflate(this, view);
  14.               }
  15.               return view;
  16.           } else {
  17.               // TODO: 2018/5/23 必须设置布局的文件
  18.               throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
  19.           }
  20.       } else {
  21.           // TODO: 2018/5/23 iewParent instanceof ViewGroup 不属于的话,就好比在一个TextView创建一个ViewStub直接爆炸
  22.           throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
  23.       }
  24.   }
  • 第一点,ViewStup也只能在ViewGroup中使用,不能在View中去使用,要不然会抛出异常IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
  • 第二点,也必须设置layout属性,要不然也会抛出异常throw new IllegalArgumentException("ViewStub must have a valid layoutResource");;

关于方法inflateViewNoAdd(parent);

 
 
 
 
  1. private View inflateViewNoAdd(ViewGroup parent) {
  2.       final LayoutInflater factory;
  3.       if (mInflater != null) {
  4.           factory = mInflater;
  5.       } else {
  6.           factory = LayoutInflater.from(mContext);
  7.       }
  8.       final View view = factory.inflate(mLayoutResource, parent, false);
  9.       //和 LayoutInflater一个道理,设置了,ViewStub 引用进来的根布局的id找出来为null  非常有些意思
  10.       if (mInflatedId != NO_ID) {
  11.           view.setId(mInflatedId);
  12.       }
  13.       return view;
  14.   }
  • 第一点,底层调用的还是LayoutInflater.from(mContext).inflate(mLayoutResource, parent, false);
  • 第二点,又看到这个方法,似曾相识,对,这也是为什么ViewStub找不到根布局id的原因,因为mInflatedId != NO_ID,就会view.setId(mInflatedId);
 
 
 
 
  1.  if (mInflatedId != NO_ID) {
  2.         view.setId(mInflatedId);
  3. }

将ViewStub自身从parent中移除replaceSelfWithView(view, parent);,具体的原因,这里不做分析,因为有点小复杂,这里就大概明白就行,对于理解这个ViewStub不困难,哈哈

 
 
 
 
  1. private void replaceSelfWithView(View view, ViewGroup parent) {
  2.      final int index = parent.indexOfChild(this);
  3.      // 3、将ViewStub自身从parent中移除
  4.      parent.removeViewInLayout(this);
  5.      final ViewGroup.LayoutParams layoutParams = getLayoutParams();
  6.      if (layoutParams != null) {
  7.          // 4、将目标布局的根元素添加到parent中,有参数
  8.          parent.addView(view, index, layoutParams);
  9.      } else {
  10.         // 5、将目标布局的根元素添加到parent中
  11.          parent.addView(view, index);
  12.      }
  13.  }

这里使用到了弱引用,只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,不论当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用,为了更好地释放内存,安卓代码、图片、布局、网络和电量优化这篇文章有很好的解释,而且这个mInflatedViewRef只在这里初始化,如果说没有调用inflate的方法的话,这个对象一定为null;

 
 
 
 
  1. //更好的释放内存
  2.  private WeakReference mInflatedViewRef;
  3.  mInflatedViewRef = new WeakReference<>(view);
  4.                if (mInflateListener != null) {
  5.               mInflateListener.onInflate(this, view)
  6.  }

为啥setVisibility(View.VISIBLE)等同于inflate,原因是ViewStub进行了重写。可以看出代码的逻辑,只要没有调用过,inflate()方法,setVisibility(VISIBLE )和setVisibility(INVISIBLE)这个两个参数走的方法一样,只不过,一个看不到,实际上的位置已经确定了(INVISIBLE)。但是如果调用多次的话setVisibility()记得也得判断下null!=mViewStub.getParent()

 
 
 
 
  1. @Override
  2.     @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
  3.     public void setVisibility(int visibility) {
  4.         // TODO: 2018/5/23  弱引用的使用
  5.         //如果已经加载过则只设置Visibility属性
  6.         if (mInflatedViewRef != null) {
  7.             View view = mInflatedViewRef.get();
  8.             if (view != null) {
  9.                 view.setVisibility(visibility);
  10.             } else {
  11.                 throw new IllegalStateException("setVisibility called on un-referenced view");
  12.             }
  13.         } else {
  14.             // 如果未加载,这加载目标布局
  15.             super.setVisibility(visibility);
  16.             if (visibility == VISIBLE || visibility == INVISIBLE) {
  17.                 inflate();// 调用inflate来加载目标布局
  18.             }
  19.         }
  20.     }

贴出全部的代码,有空的话,可以研究下。

 
 
 
 
  1. @RemoteView
  2. public final class ViewStub extends View {
  3.     private int mInflatedId;
  4.     private int mLayoutResource;
  5.     // TODO: 2018/5/23 弱引用:弱引用是比软引用更弱的一种的引用的类型,
  6.     // 只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,
  7.     // 不敢当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用
  8.     //更好的释放内存
  9.     private WeakReference mInflatedViewRef;
  10.     private LayoutInflater mInflater;
  11.     private OnInflateListener mInflateListener;
  12.     public ViewStub(Context context) {
  13.         this(context, 0);
  14.     }
  15.     /**
  16.      * Creates a new ViewStub with the specified layout resource.
  17.      *
  18.      * @param context The application's environment.
  19.      * @param layoutResource The reference to a layout resource that will be inflated.
  20.      */
  21.     public ViewStub(Context context, @LayoutRes int layoutResource) {
  22.         this(context, null);
  23.         mLayoutResource = layoutResource;
  24.     }
  25.     public ViewStub(Context context, AttributeSet attrs) {
  26.         this(context, attrs, 0);
  27.     }
  28.     public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
  29.         this(context, attrs, defStyleAttr, 0);
  30.     }
  31.     public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  32.         super(context);
  33.         final TypedArray a = context.obtainStyledAttributes(attrs,
  34.                 R.styleable.ViewStub, defStyleAttr, defStyleRes);
  35.         // TODO: 2018/5/23  ViewStub 中设置的标签id 如果设置了 这里就一定有值 mInflatedId!=NO_Id
  36.         mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
  37.         mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
  38.         mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
  39.         a.recycle();
  40.         //不可见
  41.         setVisibility(GONE);
  42.         // 设置不绘制
  43.         setWillNotDraw(true);
  44.     }
  45.     /**
  46.      * Returns the id taken by the inflated view. If the inflated id is
  47.      * {@link View#NO_ID}, the inflated view keeps its original id.
  48.      *
  49.      * @return A positive integer used to identify the inflated view or
  50.      *         {@link #NO_ID} if the inflated view should keep its id.
  51.      *
  52.      * @see #setInflatedId(int)
  53.      * @attr ref android.R.styleable#ViewStub_inflatedId
  54.      */
  55.     @IdRes
  56.     public int getInflatedId() {
  57.         return mInflatedId;
  58.     }
  59.     /**
  60.      * Defines the id taken by the inflated view. If the inflated id is
  61.      * {@link View#NO_ID}, the inflated view keeps its original id.
  62.      *
  63.      * @param inflatedId A positive integer used to identify the inflated view or
  64.      *                   {@link #NO_ID} if the inflated view should keep its id.
  65.      *
  66.      * @see #getInflatedId()
  67.      * @attr ref android.R.styleable#ViewStub_inflatedId
  68.      */
  69.     @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
  70.     public void setInflatedId(@IdRes int inflatedId) {
  71.         mInflatedId = inflatedId;
  72.     }
  73.     /** @hide **/
  74.     public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
  75.         mInflatedId = inflatedId;
  76.         return null;
  77.     }
  78.     /**
  79.      * Returns the layout resource that will be used by {@link #setVisibility(int)} or
  80.      * {@link #inflate()} to replace this StubbedView
  81.      * in its parent by another view.
  82.      *
  83.      * @return The layout resource identifier used to inflate the new View.
  84.      *
  85.      * @see #setLayoutResource(int)
  86.      * @see #setVisibility(int)
  87.      * @see #inflate()
  88.      * @attr ref android.R.styleable#ViewStub_layout
  89.      */
  90.     @LayoutRes
  91.     public int getLayoutResource() {
  92.         return mLayoutResource;
  93.     }
  94.     /**
  95.      * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
  96.      * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
  97.      * used to replace this StubbedView in its parent.
  98.      *
  99.      * @param layoutResource A valid layout resource identifier (different from 0.)
  100.      *
  101.      * @see #getLayoutResource()
  102.      * @see #setVisibility(int)
  103.      * @see #inflate()
  104.      * @attr ref android.R.styleable#ViewStub_layout
  105.      */
  106.     @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
  107.     public void setLayoutResource(@LayoutRes int layoutResource) {
  108.         mLayoutResource = layoutResource;
  109.     }
  110.     /** @hide **/
  111.     public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
  112.         mLayoutResource = layoutResource;
  113.         return null;
  114.     }
  115.     /**
  116.      * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
  117.      * to use the default.
  118.      */
  119.     public void setLayoutInflater(LayoutInflater inflater) {
  120.         mInflater = inflater;
  121.     }
  122.     /**
  123.      * Get current {@link LayoutInflater} used in {@link #inflate()}.
  124.      */
  125.     public LayoutInflater getLayoutInflater() {
  126.         return mInflater;
  127.     }
  128.     @Override
  129.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  130.         // 宽高都为0   onMeasure的时候 宽高都为0
  131.         setMeasuredDimension(0, 0);
  132.     }
  133.     //todo 为啥这个控件 是个大小为0的控件 ,那是因为他妈的这里更不就没有画
  134.     @Override
  135.     public void draw(Canvas canvas) {
  136.     }
  137.     @Override
  138.     protected void dispatchDraw(Canvas canvas) {
  139.     }
  140.     /**
  141.      * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
  142.      * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
  143.      * by the inflated layout resource. After that calls to this function are passed
  144.      * through to the inflated view.
  145.      *
  146.      * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
  147.      *
  148.      * @see #inflate()
  149.      */
  150.     @Override
  151.     @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
  152.     public void setVisibility(int visibility) {
  153.         // TODO: 2018/5/23  弱引用的使用
  154.         //如果已经加载过则只设置Visibility属性
  155.         if (mInflatedViewRef != null) {
  156.             View view = mInflatedViewRef.get();
  157.             if (view != null) {
  158.                 view.setVisibility(visibility);
  159.             } else {
  160.                 throw new IllegalStateException("setVisibility called on un-referenced view");
  161.             }
  162.         } else {
  163.             // 如果未加载,这加载目标布局
  164.             super.setVisibility(visibility);
  165.             if (visibility == VISIBLE || visibility == INVISIBLE) {
  166.                 inflate();// 调用inflate来加载目标布局
  167.             }
  168.         }
  169.     }
  170.     /** @hide **/
  171.     public Runnable setVisibilityAsync(int visibility) {
  172.         if (visibility == VISIBLE || visibility == INVISIBLE) {
  173.             ViewGroup parent = (ViewGroup) getParent();
  174.             return new ViewReplaceRunnable(inflateViewNoAdd(parent));
  175.         } else {
  176.             return null;
  177.         }
  178.     }
  179.     private View inflateViewNoAdd(ViewGroup parent) {
  180.         final LayoutInflater factory;
  181.         if (mInflater != null) {
  182.             factory = mInflater;
  183.         } else {
  184.             factory = LayoutInflater.from(mContext);
  185.         }
  186.         final View view = factory.inflate(mLayoutResource, parent, false);
  187.         //和 LayoutInflater一个道理,设置了,ViewStub 引用进来的根布局的id找出来为null  非常有些意思
  188.         if (mInflatedId != NO_ID) {
  189.             view.setId(mInflatedId);
  190.         }
  191.         return view;
  192.     }
  193.     // TODO: 2018/5/23 关注他
  194.     private void replaceSelfWithView(View view, ViewGroup parent) {
  195.         final int index = parent.indexOfChild(this);
  196.         // 3、将ViewStub自身从parent中移除
  197.         parent.removeViewInLayout(this);
  198.         final ViewGroup.LayoutParams layoutParams = getLayoutParams();
  199.         if (layoutParams != null) {
  200.             // 4、将目标布局的根元素添加到parent中,有参数
  201.             parent.addView(view, index, layoutParams);
  202.         } else {
  203.            // 5、将目标布局的根元素添加到parent中
  204.             parent.addView(view, index);
  205.         }
  206.     }
  207.     /**
  208.      * Inflates the layout resource identified by {@link #getLayoutResource()}
  209.      * and replaces this StubbedView in its parent by the inflated layout resource.
  210.      *
  211.      * @return The inflated layout resource.
  212.      *
  213.      */
  214.     public View inflate() {
  215.         // 1、获取ViewStub的parent view,也是目标布局根元素的parent view
  216.         final ViewParent viewParent = getParent();
  217.         if (viewParent != null && viewParent instanceof ViewGroup) {
  218.             if (mLayoutResource != 0) {
  219.                 final ViewGroup parent = (ViewGroup) viewParent;
  220.                 /// 2、加载目标布局  牛逼的方法
  221.                 final View view = inflateViewNoAdd(parent);
  222.                 // 3、将ViewStub自身从parent中移除
  223.                 replaceSelfWithView(view, parent);
  224.                 mInflatedViewRef = new WeakReference<>(view);
  225.                 if (mInflateListener != null) {
  226.                     mInflateListener.onInflate(this, view);
  227.                 }
  228.                 return view;
  229.             } else {
  230.                 // TODO: 2018/5/23 必须设置布局的文件
  231.                 throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
  232.             }
  233.         } else {
  234.             // TODO: 2018/5/23 iewParent instanceof ViewGroup 不属于的话,就好比在一个TextView创建一个ViewStub直接爆炸
  235.             throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
  236.         }
  237.     }
  238.     /**
  239.      * Specifies the inflate listener to be notified after this ViewStub successfully
  240.      * inflated its layout resource.
  241.      *
  242.      * @param inflateListener The OnInflateListener to notify of successful inflation.
  243.      *
  244.      * @see ViewStub.OnInflateListener
  245.      */
  246.     public void setOnInflateListener(OnInflateListener inflateListener) {
  247.         mInflateListener = inflateListener;
  248.     }
  249.     /**
  250.      * Listener used to receive a notification after a ViewStub has successfully
  251.      * inflated its layout resource.
  252.      *
  253.      * @see ViewStub#setOnInflateListener(ViewStub.OnInflateListener)
  254.      */
  255.     public static interface OnInflateListener {
  256.         /**
  257.          * Invoked after a ViewStub successfully inflated its layout resource.  分享文章:基于Android8.0分析源码的ViewStub源码解析
    转载来源:http://www.hantingmc.com/qtweb/news27/218077.html

    网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

    广告

    声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联