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 * <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" /> 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