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.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Canvas;
22import android.util.AttributeSet;
23import android.widget.RemoteViews.RemoteView;
24
25import com.android.internal.R;
26
27import java.lang.ref.WeakReference;
28
29/**
30 * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
31 * layout resources at runtime.
32 *
33 * When a ViewStub is made visible, or when {@link #inflate()}  is invoked, the layout resource
34 * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
35 * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
36 * {@link #inflate()} is invoked.
37 *
38 * The inflated View is added to the ViewStub's parent with the ViewStub's layout
39 * parameters. Similarly, you can define/override the inflate View's id by using the
40 * ViewStub's inflatedId property. For instance:
41 *
42 * <pre>
43 *     &lt;ViewStub android:id="@+id/stub"
44 *               android:inflatedId="@+id/subTree"
45 *               android:layout="@layout/mySubTree"
46 *               android:layout_width="120dip"
47 *               android:layout_height="40dip" /&gt;
48 * </pre>
49 *
50 * The ViewStub thus defined can be found using the id "stub." After inflation of
51 * the layout resource "mySubTree," the ViewStub is removed from its parent. The
52 * View created by inflating the layout resource "mySubTree" can be found using the
53 * id "subTree," specified by the inflatedId property. The inflated View is finally
54 * assigned a width of 120dip and a height of 40dip.
55 *
56 * The preferred way to perform the inflation of the layout resource is the following:
57 *
58 * <pre>
59 *     ViewStub stub = (ViewStub) findViewById(R.id.stub);
60 *     View inflated = stub.inflate();
61 * </pre>
62 *
63 * When {@link #inflate()} is invoked, the ViewStub is replaced by the inflated View
64 * and the inflated View is returned. This lets applications get a reference to the
65 * inflated View without executing an extra findViewById().
66 *
67 * @attr ref android.R.styleable#ViewStub_inflatedId
68 * @attr ref android.R.styleable#ViewStub_layout
69 */
70@RemoteView
71public final class ViewStub extends View {
72    private int mLayoutResource = 0;
73    private int mInflatedId;
74
75    private WeakReference<View> mInflatedViewRef;
76
77    private LayoutInflater mInflater;
78    private OnInflateListener mInflateListener;
79
80    public ViewStub(Context context) {
81        initialize(context);
82    }
83
84    /**
85     * Creates a new ViewStub with the specified layout resource.
86     *
87     * @param context The application's environment.
88     * @param layoutResource The reference to a layout resource that will be inflated.
89     */
90    public ViewStub(Context context, int layoutResource) {
91        mLayoutResource = layoutResource;
92        initialize(context);
93    }
94
95    public ViewStub(Context context, AttributeSet attrs) {
96        this(context, attrs, 0);
97    }
98
99    @SuppressWarnings({"UnusedDeclaration"})
100    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
101        this(context, attrs, defStyleAttr, 0);
102    }
103
104    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
105        TypedArray a = context.obtainStyledAttributes(
106                attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes);
107
108        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
109        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
110
111        a.recycle();
112
113        a = context.obtainStyledAttributes(
114                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
115        mID = a.getResourceId(R.styleable.View_id, NO_ID);
116        a.recycle();
117
118        initialize(context);
119    }
120
121    private void initialize(Context context) {
122        mContext = context;
123        setVisibility(GONE);
124        setWillNotDraw(true);
125    }
126
127    /**
128     * Returns the id taken by the inflated view. If the inflated id is
129     * {@link View#NO_ID}, the inflated view keeps its original id.
130     *
131     * @return A positive integer used to identify the inflated view or
132     *         {@link #NO_ID} if the inflated view should keep its id.
133     *
134     * @see #setInflatedId(int)
135     * @attr ref android.R.styleable#ViewStub_inflatedId
136     */
137    public int getInflatedId() {
138        return mInflatedId;
139    }
140
141    /**
142     * Defines the id taken by the inflated view. If the inflated id is
143     * {@link View#NO_ID}, the inflated view keeps its original id.
144     *
145     * @param inflatedId A positive integer used to identify the inflated view or
146     *                   {@link #NO_ID} if the inflated view should keep its id.
147     *
148     * @see #getInflatedId()
149     * @attr ref android.R.styleable#ViewStub_inflatedId
150     */
151    @android.view.RemotableViewMethod
152    public void setInflatedId(int inflatedId) {
153        mInflatedId = inflatedId;
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    public int getLayoutResource() {
169        return mLayoutResource;
170    }
171
172    /**
173     * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
174     * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
175     * used to replace this StubbedView in its parent.
176     *
177     * @param layoutResource A valid layout resource identifier (different from 0.)
178     *
179     * @see #getLayoutResource()
180     * @see #setVisibility(int)
181     * @see #inflate()
182     * @attr ref android.R.styleable#ViewStub_layout
183     */
184    @android.view.RemotableViewMethod
185    public void setLayoutResource(int layoutResource) {
186        mLayoutResource = layoutResource;
187    }
188
189    /**
190     * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
191     * to use the default.
192     */
193    public void setLayoutInflater(LayoutInflater inflater) {
194        mInflater = inflater;
195    }
196
197    /**
198     * Get current {@link LayoutInflater} used in {@link #inflate()}.
199     */
200    public LayoutInflater getLayoutInflater() {
201        return mInflater;
202    }
203
204    @Override
205    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
206        setMeasuredDimension(0, 0);
207    }
208
209    @Override
210    public void draw(Canvas canvas) {
211    }
212
213    @Override
214    protected void dispatchDraw(Canvas canvas) {
215    }
216
217    /**
218     * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
219     * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
220     * by the inflated layout resource. After that calls to this function are passed
221     * through to the inflated view.
222     *
223     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
224     *
225     * @see #inflate()
226     */
227    @Override
228    @android.view.RemotableViewMethod
229    public void setVisibility(int visibility) {
230        if (mInflatedViewRef != null) {
231            View view = mInflatedViewRef.get();
232            if (view != null) {
233                view.setVisibility(visibility);
234            } else {
235                throw new IllegalStateException("setVisibility called on un-referenced view");
236            }
237        } else {
238            super.setVisibility(visibility);
239            if (visibility == VISIBLE || visibility == INVISIBLE) {
240                inflate();
241            }
242        }
243    }
244
245    /**
246     * Inflates the layout resource identified by {@link #getLayoutResource()}
247     * and replaces this StubbedView in its parent by the inflated layout resource.
248     *
249     * @return The inflated layout resource.
250     *
251     */
252    public View inflate() {
253        final ViewParent viewParent = getParent();
254
255        if (viewParent != null && viewParent instanceof ViewGroup) {
256            if (mLayoutResource != 0) {
257                final ViewGroup parent = (ViewGroup) viewParent;
258                final LayoutInflater factory;
259                if (mInflater != null) {
260                    factory = mInflater;
261                } else {
262                    factory = LayoutInflater.from(mContext);
263                }
264                final View view = factory.inflate(mLayoutResource, parent,
265                        false);
266
267                if (mInflatedId != NO_ID) {
268                    view.setId(mInflatedId);
269                }
270
271                final int index = parent.indexOfChild(this);
272                parent.removeViewInLayout(this);
273
274                final ViewGroup.LayoutParams layoutParams = getLayoutParams();
275                if (layoutParams != null) {
276                    parent.addView(view, index, layoutParams);
277                } else {
278                    parent.addView(view, index);
279                }
280
281                mInflatedViewRef = new WeakReference<View>(view);
282
283                if (mInflateListener != null) {
284                    mInflateListener.onInflate(this, view);
285                }
286
287                return view;
288            } else {
289                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
290            }
291        } else {
292            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
293        }
294    }
295
296    /**
297     * Specifies the inflate listener to be notified after this ViewStub successfully
298     * inflated its layout resource.
299     *
300     * @param inflateListener The OnInflateListener to notify of successful inflation.
301     *
302     * @see android.view.ViewStub.OnInflateListener
303     */
304    public void setOnInflateListener(OnInflateListener inflateListener) {
305        mInflateListener = inflateListener;
306    }
307
308    /**
309     * Listener used to receive a notification after a ViewStub has successfully
310     * inflated its layout resource.
311     *
312     * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
313     */
314    public static interface OnInflateListener {
315        /**
316         * Invoked after a ViewStub successfully inflated its layout resource.
317         * This method is invoked after the inflated view was added to the
318         * hierarchy but before the layout pass.
319         *
320         * @param stub The ViewStub that initiated the inflation.
321         * @param inflated The inflated View.
322         */
323        void onInflate(ViewStub stub, View inflated);
324    }
325}
326