欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

布局优化之ViewStub源码分析

程序员文章站 2024-01-07 10:29:40
源码分析 这是什么玩应儿呢?其实就是一个轻量级的页面,我们通常使用它来做预加载处理,来改善页面加载速度和提高流畅性,ViewStub本身不会占用层级,它最终会被它指定的层级取代。 在一些场合取代android:visibility=”gone”的用法,因为被gone掉的布局不断是会同时创建对象的。那 ......

源码分析

  1 @RemoteView  
  2 public final class ViewStub extends View {  
  3     private int mInflatedId;  
  4     private int mLayoutResource;  
  5   
  6     private WeakReference<View> mInflatedViewRef;  
  7   
  8     private LayoutInflater mInflater;  
  9     private OnInflateListener mInflateListener;  
 10   
 11     public ViewStub(Context context) {  
 12         this(context, 0);  
 13     }  
 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   
 24         mLayoutResource = layoutResource;  
 25     }  
 26   
 27     public ViewStub(Context context, AttributeSet attrs) {  
 28         this(context, attrs, 0);  
 29     }  
 30   
 31     public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {  
 32         this(context, attrs, defStyleAttr, 0);  
 33     }  
 34   
 35     public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
 36         super(context);  
 37   
 38         final TypedArray a = context.obtainStyledAttributes(attrs,  
 39                 R.styleable.ViewStub, defStyleAttr, defStyleRes);  
 40         mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);  
 41         mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);  
 42         mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);  
 43         a.recycle();  
 44   
 45         setVisibility(GONE); // 默认不可见  
 46         setWillNotDraw(true); // 如果View不绘制任何内容,设置这个标记可以优化性能,默认View没有设置这个标记,如果重写onDraw,就不要设置这个标记  
 47     }  
 48   
 49     @Override  
 50     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
 51         setMeasuredDimension(0, 0); // 测量时尺寸为0  
 52     }  
 53   
 54     @Override  
 55     public void draw(Canvas canvas) { // 不绘制内容  
 56     }  
 57   
 58     @Override  
 59     protected void dispatchDraw(Canvas canvas) {  
 60     }  
 61 ..... 省去部分代码  
 62   
 63     private View inflateViewNoAdd(ViewGroup parent) {  
 64         final LayoutInflater factory;  
 65         if (mInflater != null) {  
 66             factory = mInflater;  
 67         } else {  
 68             factory = LayoutInflater.from(mContext);  
 69         } // 通过inflate填充布局  
 70         final View view = factory.inflate(mLayoutResource, parent, false);  
 71   
 72         if (mInflatedId != NO_ID) {  
 73             view.setId(mInflatedId);  
 74         }  
 75         return view;  
 76     }  
 77   
 78     private void replaceSelfWithView(View view, ViewGroup parent) {  
 79         final int index = parent.indexOfChild(this);  
 80         parent.removeViewInLayout(this); // 移除ViewStub,后面不能在inflate  
 81   
 82         final ViewGroup.LayoutParams layoutParams = getLayoutParams(); // 获得ViewStub的布局参数  
 83         if (layoutParams != null) {  
 84             parent.addView(view, index, layoutParams); // 把ViewStub指定的布局添加到parent中  
 85         } else {  
 86             parent.addView(view, index);  
 87         }  
 88     }  
 89   
 90     /**  
 91      * Inflates the layout resource identified by {@link #getLayoutResource()}  
 92      * and replaces this StubbedView in its parent by the inflated layout resource.  
 93      *  
 94      * @return The inflated layout resource.  
 95      *  
 96      */  
 97     public View inflate() {  
 98         final ViewParent viewParent = getParent(); // 获取ViewStub的parent  
 99   
100         if (viewParent != null && viewParent instanceof ViewGroup) {  
101             if (mLayoutResource != 0) {  
102                 final ViewGroup parent = (ViewGroup) viewParent;  
103                 final View view = inflateViewNoAdd(parent);  
104                 replaceSelfWithView(view, parent);  
105   
106                 mInflatedViewRef = new WeakReference<>(view);  
107                 if (mInflateListener != null) {  
108                     mInflateListener.onInflate(this, view);  
109                 }  
110   
111                 return view;  
112             } else {  
113                 throw new IllegalArgumentException("ViewStub must have a valid layoutResource");  
114             }  
115         } else {  
116             throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");  
117         }  
118     }  
119   
120     /**  
121      * Specifies the inflate listener to be notified after this ViewStub successfully  
122      * inflated its layout resource.  
123      *  
124      * @param inflateListener The OnInflateListener to notify of successful inflation.  
125      *  
126      * @see android.view.ViewStub.OnInflateListener  
127      */  
128     public void setOnInflateListener(OnInflateListener inflateListener) {  
129         mInflateListener = inflateListener;  
130     }  
131   
132     /**  
133      * Listener used to receive a notification after a ViewStub has successfully  
134      * inflated its layout resource.  
135      *  
136      * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)   
137      */  
138     public static interface OnInflateListener {  
139         /**  
140          * Invoked after a ViewStub successfully inflated its layout resource.  
141          * This method is invoked after the inflated view was added to the  
142          * hierarchy but before the layout pass.  
143          *  
144          * @param stub The ViewStub that initiated the inflation.  
145          * @param inflated The inflated View.  
146          */  
147         void onInflate(ViewStub stub, View inflated);  
148     }  
149   
150     /** @hide **/  
151     public class ViewReplaceRunnable implements Runnable {  
152         public final View view;  
153   
154         ViewReplaceRunnable(View view) {  
155             this.view = view;  
156         }  
157   
158         @Override  
159         public void run() {  
160             replaceSelfWithView(view, (ViewGroup) getParent());  
161         }  
162     }  
163 }  

这是什么玩应儿呢?其实就是一个轻量级的页面,我们通常使用它来做预加载处理,来改善页面加载速度和提高流畅性,ViewStub本身不会占用层级,它最终会被它指定的层级取代。 
在一些场合取代android:visibility=”gone”的用法,因为被gone掉的布局不断是会同时创建对象的。那为什么使用ViewStub就高效呢, 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
setMeasuredDimension(0, 0); 
}

@Override 
public void draw(Canvas canvas) { 

由onMeasure()方法和draw()方法可以看出, ViewStub的初始宽高都是零,所以他开始不会占用空间,其次draw()方法也没有执行任何的绘制,由这两个方法就可以看出,ViewStub的确很高效。 
在代码中要操纵ViewStub的时候,要首先使用viewstub.inflate()方法,将其所拥有的View初始化进去。否则会报空指针错误。 
但ViewStub也不是万能的,下面总结下ViewStub能做的事儿和什么时候该用ViewStub,什么时候该用可见性的控制。

首先来说说ViewStub的一些特点:

1. ViewStub只能Inflate一次,之后ViewStub对象会被置为空。按句话说,某个被ViewStub指定的布局被Inflate后,就不会够再通过ViewStub来控制它了。
2. ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。

基于以上的特点,那么可以考虑使用ViewStub的情况有:
1. 在程序的运行期间,某个布局在Inflate后,就不会有变化,除非重新启动。
因为ViewStub只能Inflate一次,之后会被置空,所以无法指望后面接着使用ViewStub来控制布局。所以当需要在运行时不止一次的显示和隐藏某个布局,那么ViewStub是做不到的。这时就只能使用View的可见性来控制了。
2. 想要控制显示与隐藏的是一个布局文件,而非某个View。
因为设置给ViewStub的只能是某个布局文件的Id,所以无法让它来控制某个View。
所以,如果想要控制某个View(如Button或TextView)的显示与隐藏,或者想要在运行时不断的显示与隐藏某个布局或View,只能使用View的可见性来控制。