1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view;
18 
19 import android.annotation.IdRes;
20 import android.annotation.LayoutRes;
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.graphics.Canvas;
24 import android.util.AttributeSet;
25 import android.widget.RemoteViews.RemoteView;
26 
27 import com.android.internal.R;
28 
29 import java.lang.ref.WeakReference;
30 
31 /**
32  * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
33  * layout resources at runtime.
34  *
35  * When a ViewStub is made visible, or when {@link #inflate()}  is invoked, the layout resource
36  * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
37  * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
38  * {@link #inflate()} is invoked.
39  *
40  * The inflated View is added to the ViewStub's parent with the ViewStub's layout
41  * parameters. Similarly, you can define/override the inflate View's id by using the
42  * ViewStub's inflatedId property. For instance:
43  *
44  * <pre>
45  *     &lt;ViewStub android:id="@+id/stub"
46  *               android:inflatedId="@+id/subTree"
47  *               android:layout="@layout/mySubTree"
48  *               android:layout_width="120dip"
49  *               android:layout_height="40dip" /&gt;
50  * </pre>
51  *
52  * The ViewStub thus defined can be found using the id "stub." After inflation of
53  * the layout resource "mySubTree," the ViewStub is removed from its parent. The
54  * View created by inflating the layout resource "mySubTree" can be found using the
55  * id "subTree," specified by the inflatedId property. The inflated View is finally
56  * assigned a width of 120dip and a height of 40dip.
57  *
58  * The preferred way to perform the inflation of the layout resource is the following:
59  *
60  * <pre>
61  *     ViewStub stub = findViewById(R.id.stub);
62  *     View inflated = stub.inflate();
63  * </pre>
64  *
65  * When {@link #inflate()} is invoked, the ViewStub is replaced by the inflated View
66  * and the inflated View is returned. This lets applications get a reference to the
67  * inflated View without executing an extra findViewById().
68  *
69  * @attr ref android.R.styleable#ViewStub_inflatedId
70  * @attr ref android.R.styleable#ViewStub_layout
71  */
72 @RemoteView
73 public final class ViewStub extends View {
74     private int mInflatedId;
75     private int mLayoutResource;
76 
77     private WeakReference<View> mInflatedViewRef;
78 
79     private LayoutInflater mInflater;
80     private OnInflateListener mInflateListener;
81 
ViewStub(Context context)82     public ViewStub(Context context) {
83         this(context, 0);
84     }
85 
86     /**
87      * Creates a new ViewStub with the specified layout resource.
88      *
89      * @param context The application's environment.
90      * @param layoutResource The reference to a layout resource that will be inflated.
91      */
ViewStub(Context context, @LayoutRes int layoutResource)92     public ViewStub(Context context, @LayoutRes int layoutResource) {
93         this(context, null);
94 
95         mLayoutResource = layoutResource;
96     }
97 
ViewStub(Context context, AttributeSet attrs)98     public ViewStub(Context context, AttributeSet attrs) {
99         this(context, attrs, 0);
100     }
101 
ViewStub(Context context, AttributeSet attrs, int defStyleAttr)102     public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
103         this(context, attrs, defStyleAttr, 0);
104     }
105 
ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)106     public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
107         super(context);
108 
109         final TypedArray a = context.obtainStyledAttributes(attrs,
110                 R.styleable.ViewStub, defStyleAttr, defStyleRes);
111         saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
112                 defStyleRes);
113 
114         mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
115         mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
116         mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
117         a.recycle();
118 
119         setVisibility(GONE);
120         setWillNotDraw(true);
121     }
122 
123     /**
124      * Returns the id taken by the inflated view. If the inflated id is
125      * {@link View#NO_ID}, the inflated view keeps its original id.
126      *
127      * @return A positive integer used to identify the inflated view or
128      *         {@link #NO_ID} if the inflated view should keep its id.
129      *
130      * @see #setInflatedId(int)
131      * @attr ref android.R.styleable#ViewStub_inflatedId
132      */
133     @IdRes
getInflatedId()134     public int getInflatedId() {
135         return mInflatedId;
136     }
137 
138     /**
139      * Defines the id taken by the inflated view. If the inflated id is
140      * {@link View#NO_ID}, the inflated view keeps its original id.
141      *
142      * @param inflatedId A positive integer used to identify the inflated view or
143      *                   {@link #NO_ID} if the inflated view should keep its id.
144      *
145      * @see #getInflatedId()
146      * @attr ref android.R.styleable#ViewStub_inflatedId
147      */
148     @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
setInflatedId(@dRes int inflatedId)149     public void setInflatedId(@IdRes int inflatedId) {
150         mInflatedId = inflatedId;
151     }
152 
153     /** @hide **/
setInflatedIdAsync(@dRes int inflatedId)154     public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
155         mInflatedId = inflatedId;
156         return null;
157     }
158 
159     /**
160      * Returns the layout resource that will be used by {@link #setVisibility(int)} or
161      * {@link #inflate()} to replace this StubbedView
162      * in its parent by another view.
163      *
164      * @return The layout resource identifier used to inflate the new View.
165      *
166      * @see #setLayoutResource(int)
167      * @see #setVisibility(int)
168      * @see #inflate()
169      * @attr ref android.R.styleable#ViewStub_layout
170      */
171     @LayoutRes
getLayoutResource()172     public int getLayoutResource() {
173         return mLayoutResource;
174     }
175 
176     /**
177      * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
178      * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
179      * used to replace this StubbedView in its parent.
180      *
181      * @param layoutResource A valid layout resource identifier (different from 0.)
182      *
183      * @see #getLayoutResource()
184      * @see #setVisibility(int)
185      * @see #inflate()
186      * @attr ref android.R.styleable#ViewStub_layout
187      */
188     @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
setLayoutResource(@ayoutRes int layoutResource)189     public void setLayoutResource(@LayoutRes int layoutResource) {
190         mLayoutResource = layoutResource;
191     }
192 
193     /** @hide **/
setLayoutResourceAsync(@ayoutRes int layoutResource)194     public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
195         mLayoutResource = layoutResource;
196         return null;
197     }
198 
199     /**
200      * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
201      * to use the default.
202      */
setLayoutInflater(LayoutInflater inflater)203     public void setLayoutInflater(LayoutInflater inflater) {
204         mInflater = inflater;
205     }
206 
207     /**
208      * Get current {@link LayoutInflater} used in {@link #inflate()}.
209      */
getLayoutInflater()210     public LayoutInflater getLayoutInflater() {
211         return mInflater;
212     }
213 
214     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)215     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
216         setMeasuredDimension(0, 0);
217     }
218 
219     @Override
draw(Canvas canvas)220     public void draw(Canvas canvas) {
221     }
222 
223     @Override
dispatchDraw(Canvas canvas)224     protected void dispatchDraw(Canvas canvas) {
225     }
226 
227     /**
228      * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
229      * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
230      * by the inflated layout resource. After that calls to this function are passed
231      * through to the inflated view.
232      *
233      * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
234      *
235      * @see #inflate()
236      */
237     @Override
238     @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
setVisibility(int visibility)239     public void setVisibility(int visibility) {
240         if (mInflatedViewRef != null) {
241             View view = mInflatedViewRef.get();
242             if (view != null) {
243                 view.setVisibility(visibility);
244             } else {
245                 throw new IllegalStateException("setVisibility called on un-referenced view");
246             }
247         } else {
248             super.setVisibility(visibility);
249             if (visibility == VISIBLE || visibility == INVISIBLE) {
250                 inflate();
251             }
252         }
253     }
254 
255     /** @hide **/
setVisibilityAsync(int visibility)256     public Runnable setVisibilityAsync(int visibility) {
257         if (visibility == VISIBLE || visibility == INVISIBLE) {
258             ViewGroup parent = (ViewGroup) getParent();
259             return new ViewReplaceRunnable(inflateViewNoAdd(parent));
260         } else {
261             return null;
262         }
263     }
264 
inflateViewNoAdd(ViewGroup parent)265     private View inflateViewNoAdd(ViewGroup parent) {
266         final LayoutInflater factory;
267         if (mInflater != null) {
268             factory = mInflater;
269         } else {
270             factory = LayoutInflater.from(mContext);
271         }
272         final View view = factory.inflate(mLayoutResource, parent, false);
273 
274         if (mInflatedId != NO_ID) {
275             view.setId(mInflatedId);
276         }
277         return view;
278     }
279 
replaceSelfWithView(View view, ViewGroup parent)280     private void replaceSelfWithView(View view, ViewGroup parent) {
281         final int index = parent.indexOfChild(this);
282         parent.removeViewInLayout(this);
283 
284         final ViewGroup.LayoutParams layoutParams = getLayoutParams();
285         if (layoutParams != null) {
286             parent.addView(view, index, layoutParams);
287         } else {
288             parent.addView(view, index);
289         }
290     }
291 
292     /**
293      * Inflates the layout resource identified by {@link #getLayoutResource()}
294      * and replaces this StubbedView in its parent by the inflated layout resource.
295      *
296      * @return The inflated layout resource.
297      *
298      */
inflate()299     public View inflate() {
300         final ViewParent viewParent = getParent();
301 
302         if (viewParent != null && viewParent instanceof ViewGroup) {
303             if (mLayoutResource != 0) {
304                 final ViewGroup parent = (ViewGroup) viewParent;
305                 final View view = inflateViewNoAdd(parent);
306                 replaceSelfWithView(view, parent);
307 
308                 mInflatedViewRef = new WeakReference<>(view);
309                 if (mInflateListener != null) {
310                     mInflateListener.onInflate(this, view);
311                 }
312 
313                 return view;
314             } else {
315                 throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
316             }
317         } else {
318             throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
319         }
320     }
321 
322     /**
323      * Specifies the inflate listener to be notified after this ViewStub successfully
324      * inflated its layout resource.
325      *
326      * @param inflateListener The OnInflateListener to notify of successful inflation.
327      *
328      * @see android.view.ViewStub.OnInflateListener
329      */
setOnInflateListener(OnInflateListener inflateListener)330     public void setOnInflateListener(OnInflateListener inflateListener) {
331         mInflateListener = inflateListener;
332     }
333 
334     /**
335      * Listener used to receive a notification after a ViewStub has successfully
336      * inflated its layout resource.
337      *
338      * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
339      */
340     public static interface OnInflateListener {
341         /**
342          * Invoked after a ViewStub successfully inflated its layout resource.
343          * This method is invoked after the inflated view was added to the
344          * hierarchy but before the layout pass.
345          *
346          * @param stub The ViewStub that initiated the inflation.
347          * @param inflated The inflated View.
348          */
onInflate(ViewStub stub, View inflated)349         void onInflate(ViewStub stub, View inflated);
350     }
351 
352     /** @hide **/
353     public class ViewReplaceRunnable implements Runnable {
354         public final View view;
355 
ViewReplaceRunnable(View view)356         ViewReplaceRunnable(View view) {
357             this.view = view;
358         }
359 
360         @Override
run()361         public void run() {
362             replaceSelfWithView(view, (ViewGroup) getParent());
363         }
364     }
365 }
366