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
17package android.view;
18
19import android.annotation.IdRes;
20import android.annotation.LayoutRes;
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Canvas;
24import android.util.AttributeSet;
25import android.widget.RemoteViews.RemoteView;
26
27import com.android.internal.R;
28
29import 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
73public 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
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     */
92    public ViewStub(Context context, @LayoutRes int layoutResource) {
93        this(context, null);
94
95        mLayoutResource = layoutResource;
96    }
97
98    public ViewStub(Context context, AttributeSet attrs) {
99        this(context, attrs, 0);
100    }
101
102    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
103        this(context, attrs, defStyleAttr, 0);
104    }
105
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        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
112        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
113        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
114        a.recycle();
115
116        setVisibility(GONE);
117        setWillNotDraw(true);
118    }
119
120    /**
121     * Returns the id taken by the inflated view. If the inflated id is
122     * {@link View#NO_ID}, the inflated view keeps its original id.
123     *
124     * @return A positive integer used to identify the inflated view or
125     *         {@link #NO_ID} if the inflated view should keep its id.
126     *
127     * @see #setInflatedId(int)
128     * @attr ref android.R.styleable#ViewStub_inflatedId
129     */
130    @IdRes
131    public int getInflatedId() {
132        return mInflatedId;
133    }
134
135    /**
136     * Defines the id taken by the inflated view. If the inflated id is
137     * {@link View#NO_ID}, the inflated view keeps its original id.
138     *
139     * @param inflatedId A positive integer used to identify the inflated view or
140     *                   {@link #NO_ID} if the inflated view should keep its id.
141     *
142     * @see #getInflatedId()
143     * @attr ref android.R.styleable#ViewStub_inflatedId
144     */
145    @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
146    public void setInflatedId(@IdRes int inflatedId) {
147        mInflatedId = inflatedId;
148    }
149
150    /** @hide **/
151    public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
152        mInflatedId = inflatedId;
153        return null;
154    }
155
156    /**
157     * Returns the layout resource that will be used by {@link #setVisibility(int)} or
158     * {@link #inflate()} to replace this StubbedView
159     * in its parent by another view.
160     *
161     * @return The layout resource identifier used to inflate the new View.
162     *
163     * @see #setLayoutResource(int)
164     * @see #setVisibility(int)
165     * @see #inflate()
166     * @attr ref android.R.styleable#ViewStub_layout
167     */
168    @LayoutRes
169    public int getLayoutResource() {
170        return mLayoutResource;
171    }
172
173    /**
174     * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
175     * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
176     * used to replace this StubbedView in its parent.
177     *
178     * @param layoutResource A valid layout resource identifier (different from 0.)
179     *
180     * @see #getLayoutResource()
181     * @see #setVisibility(int)
182     * @see #inflate()
183     * @attr ref android.R.styleable#ViewStub_layout
184     */
185    @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
186    public void setLayoutResource(@LayoutRes int layoutResource) {
187        mLayoutResource = layoutResource;
188    }
189
190    /** @hide **/
191    public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
192        mLayoutResource = layoutResource;
193        return null;
194    }
195
196    /**
197     * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
198     * to use the default.
199     */
200    public void setLayoutInflater(LayoutInflater inflater) {
201        mInflater = inflater;
202    }
203
204    /**
205     * Get current {@link LayoutInflater} used in {@link #inflate()}.
206     */
207    public LayoutInflater getLayoutInflater() {
208        return mInflater;
209    }
210
211    @Override
212    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
213        setMeasuredDimension(0, 0);
214    }
215
216    @Override
217    public void draw(Canvas canvas) {
218    }
219
220    @Override
221    protected void dispatchDraw(Canvas canvas) {
222    }
223
224    /**
225     * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
226     * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
227     * by the inflated layout resource. After that calls to this function are passed
228     * through to the inflated view.
229     *
230     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
231     *
232     * @see #inflate()
233     */
234    @Override
235    @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
236    public void setVisibility(int visibility) {
237        if (mInflatedViewRef != null) {
238            View view = mInflatedViewRef.get();
239            if (view != null) {
240                view.setVisibility(visibility);
241            } else {
242                throw new IllegalStateException("setVisibility called on un-referenced view");
243            }
244        } else {
245            super.setVisibility(visibility);
246            if (visibility == VISIBLE || visibility == INVISIBLE) {
247                inflate();
248            }
249        }
250    }
251
252    /** @hide **/
253    public Runnable setVisibilityAsync(int visibility) {
254        if (visibility == VISIBLE || visibility == INVISIBLE) {
255            ViewGroup parent = (ViewGroup) getParent();
256            return new ViewReplaceRunnable(inflateViewNoAdd(parent));
257        } else {
258            return null;
259        }
260    }
261
262    private View inflateViewNoAdd(ViewGroup parent) {
263        final LayoutInflater factory;
264        if (mInflater != null) {
265            factory = mInflater;
266        } else {
267            factory = LayoutInflater.from(mContext);
268        }
269        final View view = factory.inflate(mLayoutResource, parent, false);
270
271        if (mInflatedId != NO_ID) {
272            view.setId(mInflatedId);
273        }
274        return view;
275    }
276
277    private void replaceSelfWithView(View view, ViewGroup parent) {
278        final int index = parent.indexOfChild(this);
279        parent.removeViewInLayout(this);
280
281        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
282        if (layoutParams != null) {
283            parent.addView(view, index, layoutParams);
284        } else {
285            parent.addView(view, index);
286        }
287    }
288
289    /**
290     * Inflates the layout resource identified by {@link #getLayoutResource()}
291     * and replaces this StubbedView in its parent by the inflated layout resource.
292     *
293     * @return The inflated layout resource.
294     *
295     */
296    public View inflate() {
297        final ViewParent viewParent = getParent();
298
299        if (viewParent != null && viewParent instanceof ViewGroup) {
300            if (mLayoutResource != 0) {
301                final ViewGroup parent = (ViewGroup) viewParent;
302                final View view = inflateViewNoAdd(parent);
303                replaceSelfWithView(view, parent);
304
305                mInflatedViewRef = new WeakReference<>(view);
306                if (mInflateListener != null) {
307                    mInflateListener.onInflate(this, view);
308                }
309
310                return view;
311            } else {
312                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
313            }
314        } else {
315            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
316        }
317    }
318
319    /**
320     * Specifies the inflate listener to be notified after this ViewStub successfully
321     * inflated its layout resource.
322     *
323     * @param inflateListener The OnInflateListener to notify of successful inflation.
324     *
325     * @see android.view.ViewStub.OnInflateListener
326     */
327    public void setOnInflateListener(OnInflateListener inflateListener) {
328        mInflateListener = inflateListener;
329    }
330
331    /**
332     * Listener used to receive a notification after a ViewStub has successfully
333     * inflated its layout resource.
334     *
335     * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
336     */
337    public static interface OnInflateListener {
338        /**
339         * Invoked after a ViewStub successfully inflated its layout resource.
340         * This method is invoked after the inflated view was added to the
341         * hierarchy but before the layout pass.
342         *
343         * @param stub The ViewStub that initiated the inflation.
344         * @param inflated The inflated View.
345         */
346        void onInflate(ViewStub stub, View inflated);
347    }
348
349    /** @hide **/
350    public class ViewReplaceRunnable implements Runnable {
351        public final View view;
352
353        ViewReplaceRunnable(View view) {
354            this.view = view;
355        }
356
357        @Override
358        public void run() {
359            replaceSelfWithView(view, (ViewGroup) getParent());
360        }
361    }
362}
363