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         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