Home | History | Annotate | Download | only in view
      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 
     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         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
    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")
    149     public void setInflatedId(@IdRes int inflatedId) {
    150         mInflatedId = inflatedId;
    151     }
    152 
    153     /** @hide **/
    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
    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")
    189     public void setLayoutResource(@LayoutRes int layoutResource) {
    190         mLayoutResource = layoutResource;
    191     }
    192 
    193     /** @hide **/
    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      */
    203     public void setLayoutInflater(LayoutInflater inflater) {
    204         mInflater = inflater;
    205     }
    206 
    207     /**
    208      * Get current {@link LayoutInflater} used in {@link #inflate()}.
    209      */
    210     public LayoutInflater getLayoutInflater() {
    211         return mInflater;
    212     }
    213 
    214     @Override
    215     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    216         setMeasuredDimension(0, 0);
    217     }
    218 
    219     @Override
    220     public void draw(Canvas canvas) {
    221     }
    222 
    223     @Override
    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")
    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 **/
    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 
    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 
    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      */
    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      */
    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          */
    349         void onInflate(ViewStub stub, View inflated);
    350     }
    351 
    352     /** @hide **/
    353     public class ViewReplaceRunnable implements Runnable {
    354         public final View view;
    355 
    356         ViewReplaceRunnable(View view) {
    357             this.view = view;
    358         }
    359 
    360         @Override
    361         public void run() {
    362             replaceSelfWithView(view, (ViewGroup) getParent());
    363         }
    364     }
    365 }
    366