/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import android.annotation.IdRes; import android.annotation.LayoutRes; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.util.AttributeSet; import android.widget.RemoteViews.RemoteView; import com.android.internal.R; import java.lang.ref.WeakReference; /** * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate * layout resources at runtime. * * When a ViewStub is made visible, or when {@link #inflate()} is invoked, the layout resource * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or * {@link #inflate()} is invoked. * * The inflated View is added to the ViewStub's parent with the ViewStub's layout * parameters. Similarly, you can define/override the inflate View's id by using the * ViewStub's inflatedId property. For instance: * *
 *     <ViewStub android:id="@+id/stub"
 *               android:inflatedId="@+id/subTree"
 *               android:layout="@layout/mySubTree"
 *               android:layout_width="120dip"
 *               android:layout_height="40dip" />
 * 
* * The ViewStub thus defined can be found using the id "stub." After inflation of * the layout resource "mySubTree," the ViewStub is removed from its parent. The * View created by inflating the layout resource "mySubTree" can be found using the * id "subTree," specified by the inflatedId property. The inflated View is finally * assigned a width of 120dip and a height of 40dip. * * The preferred way to perform the inflation of the layout resource is the following: * *
 *     ViewStub stub = findViewById(R.id.stub);
 *     View inflated = stub.inflate();
 * 
* * When {@link #inflate()} is invoked, the ViewStub is replaced by the inflated View * and the inflated View is returned. This lets applications get a reference to the * inflated View without executing an extra findViewById(). * * @attr ref android.R.styleable#ViewStub_inflatedId * @attr ref android.R.styleable#ViewStub_layout */ @RemoteView public final class ViewStub extends View { private int mInflatedId; private int mLayoutResource; private WeakReference mInflatedViewRef; private LayoutInflater mInflater; private OnInflateListener mInflateListener; public ViewStub(Context context) { this(context, 0); } /** * Creates a new ViewStub with the specified layout resource. * * @param context The application's environment. * @param layoutResource The reference to a layout resource that will be inflated. */ public ViewStub(Context context, @LayoutRes int layoutResource) { this(context, null); mLayoutResource = layoutResource; } public ViewStub(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr, defStyleRes); mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); setVisibility(GONE); setWillNotDraw(true); } /** * Returns the id taken by the inflated view. If the inflated id is * {@link View#NO_ID}, the inflated view keeps its original id. * * @return A positive integer used to identify the inflated view or * {@link #NO_ID} if the inflated view should keep its id. * * @see #setInflatedId(int) * @attr ref android.R.styleable#ViewStub_inflatedId */ @IdRes public int getInflatedId() { return mInflatedId; } /** * Defines the id taken by the inflated view. If the inflated id is * {@link View#NO_ID}, the inflated view keeps its original id. * * @param inflatedId A positive integer used to identify the inflated view or * {@link #NO_ID} if the inflated view should keep its id. * * @see #getInflatedId() * @attr ref android.R.styleable#ViewStub_inflatedId */ @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync") public void setInflatedId(@IdRes int inflatedId) { mInflatedId = inflatedId; } /** @hide **/ public Runnable setInflatedIdAsync(@IdRes int inflatedId) { mInflatedId = inflatedId; return null; } /** * Returns the layout resource that will be used by {@link #setVisibility(int)} or * {@link #inflate()} to replace this StubbedView * in its parent by another view. * * @return The layout resource identifier used to inflate the new View. * * @see #setLayoutResource(int) * @see #setVisibility(int) * @see #inflate() * @attr ref android.R.styleable#ViewStub_layout */ @LayoutRes public int getLayoutResource() { return mLayoutResource; } /** * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is * used to replace this StubbedView in its parent. * * @param layoutResource A valid layout resource identifier (different from 0.) * * @see #getLayoutResource() * @see #setVisibility(int) * @see #inflate() * @attr ref android.R.styleable#ViewStub_layout */ @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync") public void setLayoutResource(@LayoutRes int layoutResource) { mLayoutResource = layoutResource; } /** @hide **/ public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) { mLayoutResource = layoutResource; return null; } /** * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null} * to use the default. */ public void setLayoutInflater(LayoutInflater inflater) { mInflater = inflater; } /** * Get current {@link LayoutInflater} used in {@link #inflate()}. */ public LayoutInflater getLayoutInflater() { return mInflater; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(0, 0); } @Override public void draw(Canvas canvas) { } @Override protected void dispatchDraw(Canvas canvas) { } /** * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE}, * {@link #inflate()} is invoked and this StubbedView is replaced in its parent * by the inflated layout resource. After that calls to this function are passed * through to the inflated view. * * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. * * @see #inflate() */ @Override @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync") public void setVisibility(int visibility) { if (mInflatedViewRef != null) { View view = mInflatedViewRef.get(); if (view != null) { view.setVisibility(visibility); } else { throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { super.setVisibility(visibility); if (visibility == VISIBLE || visibility == INVISIBLE) { inflate(); } } } /** @hide **/ public Runnable setVisibilityAsync(int visibility) { if (visibility == VISIBLE || visibility == INVISIBLE) { ViewGroup parent = (ViewGroup) getParent(); return new ViewReplaceRunnable(inflateViewNoAdd(parent)); } else { return null; } } private View inflateViewNoAdd(ViewGroup parent) { final LayoutInflater factory; if (mInflater != null) { factory = mInflater; } else { factory = LayoutInflater.from(mContext); } final View view = factory.inflate(mLayoutResource, parent, false); if (mInflatedId != NO_ID) { view.setId(mInflatedId); } return view; } private void replaceSelfWithView(View view, ViewGroup parent) { final int index = parent.indexOfChild(this); parent.removeViewInLayout(this); final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { parent.addView(view, index, layoutParams); } else { parent.addView(view, index); } } /** * Inflates the layout resource identified by {@link #getLayoutResource()} * and replaces this StubbedView in its parent by the inflated layout resource. * * @return The inflated layout resource. * */ public View inflate() { final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; final View view = inflateViewNoAdd(parent); replaceSelfWithView(view, parent); mInflatedViewRef = new WeakReference<>(view); if (mInflateListener != null) { mInflateListener.onInflate(this, view); } return view; } else { throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); } } /** * Specifies the inflate listener to be notified after this ViewStub successfully * inflated its layout resource. * * @param inflateListener The OnInflateListener to notify of successful inflation. * * @see android.view.ViewStub.OnInflateListener */ public void setOnInflateListener(OnInflateListener inflateListener) { mInflateListener = inflateListener; } /** * Listener used to receive a notification after a ViewStub has successfully * inflated its layout resource. * * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener) */ public static interface OnInflateListener { /** * Invoked after a ViewStub successfully inflated its layout resource. * This method is invoked after the inflated view was added to the * hierarchy but before the layout pass. * * @param stub The ViewStub that initiated the inflation. * @param inflated The inflated View. */ void onInflate(ViewStub stub, View inflated); } /** @hide **/ public class ViewReplaceRunnable implements Runnable { public final View view; ViewReplaceRunnable(View view) { this.view = view; } @Override public void run() { replaceSelfWithView(view, (ViewGroup) getParent()); } } }