/*
* 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.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;
/**
* ViewStub 是不可见的不占用布局空间的视图,用于在运行时延迟加载布局资源.
*
* 当ViewStub可见,或者调用 {@link #inflate()}函数时,才会加载布局资源.
* 之后在父容器中用展开的一个或多个视图替换它本身.因此,调用
* {@link #setVisibility(int)}或者{@link #inflate()}之前,ViewStub
* 会一直存在于视图层次中.
*
* 展开的视图会与ViewStub的布局参数一同添加到其父容器中.
* 同样,你可以使用ViewStub的inflatedId属性定义或重写加载视图的ID.
* 例如:
* <pre>
* <ViewStub android:id="@+id/stub"
* android:inflatedId="@+id/subTree"
* android:layout="@layout/mySubTree"
* android:layout_width="120dip"
* android:layout_height="40dip" />
* </pre>
*
* 我们看到的用“stub”ID定义的 ViewStub. 加载布局资源“mySubTree”之后,会从其父容器中移除ViewStub.
* 由加载的“mySubTree”布局资源创建的视图的ID,被inflatedId属性指定为“subTree”.
* 为加载的视图最终分配120dpi的宽度和40dpi的高度.
*
* 执行加载布局资源的首选方式如下:
*
* <pre>
* ViewStub stub = (ViewStub) findViewById(R.id.stub);
* View inflated = stub.inflate();
* </pre>
*
* 当执行 {@link #inflate()}时,ViewStub被加载的视图取代,并返回加载的视图.
* 这使应用程序不必执行额外的findViewById()方法即可得到对加载的视图的引用.
*
* @attr ref android.R.styleable#ViewStub_inflatedId
* @attr ref android.R.styleable#ViewStub_layout
*/
@RemoteView
public final class ViewStub extends View {
private int mLayoutResource = 0;
private int mInflatedId;
private WeakReference<View> mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
public ViewStub(Context context) {
initialize(context);
}
/**
* 使用指定的布局资源创建一个新的ViewStub对象.
*
* @param context 应用程序上下文.
* @param layoutResource 对要加载的布局资源的引用.
*/
public ViewStub(Context context, int layoutResource) {
mLayoutResource = layoutResource;
initialize(context);
}
public ViewStub(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@SuppressWarnings({"UnusedDeclaration"})
public ViewStub(Context context, AttributeSet attrs, int defStyle) {
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub,
defStyle, 0);
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
a.recycle();
a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, 0);
mID = a.getResourceId(R.styleable.View_id, NO_ID);
a.recycle();
initialize(context);
}
private void initialize(Context context) {
mContext = context;
setVisibility(GONE);
setWillNotDraw(true);
}
/**
* 返回用于要加载视图的ID.如果该ID为{@link View#NO_ID},
* 则保持要展开视图原始的ID.
*
* @return 标识要展开视图的正整数ID;如果要保持其原始ID则返回{@link #NO_ID}.
*
* @see #setInflatedId(int)
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
public int getInflatedId() {
return mInflatedId;
}
/**
* 定义用于要加载视图的ID.如果该ID为{@link View#NO_ID},
* 则保持要展开视图原始的ID.
*
* @param inflatedId 标识要展开视图的正整数ID;如果要保持其原始ID
* 则应使用{@link #NO_ID}.
*
* @see #getInflatedId()
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
@android.view.RemotableViewMethod
public void setInflatedId(int inflatedId) {
mInflatedId = inflatedId;
}
/**
* 返回{@link #setVisibility(int)}或{@link #inflate()}函数执行时用于替换
* StubbedView 的布局资源.
*
* @return 用于载入新视图的布局资源ID.
*
* @see #setLayoutResource(int)
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
public int getLayoutResource() {
return mLayoutResource;
}
/**
* 指定当该 StubbedView 变为可见、不可见或者执行{@link #inflate()}方法时要载入的布局资源.
* 布局载入创建的视图用于替换父视图中的StubbedView.
*
* @param layoutResource 有效的布局资源ID(不等于0).
*
* @see #getLayoutResource()
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
@android.view.RemotableViewMethod
public void setLayoutResource(int layoutResource) {
mLayoutResource = layoutResource;
}
/**
* 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) {
}
/**
* 可视性设为 {@link #VISIBLE} 或 {@link #INVISIBLE}时,执行
* {@link #inflate()} 用载入的布局资源替换父视图中的StubbedView.
* After that calls to this function are passed
* through to the inflated view.
*
* @param visibility {@link #VISIBLE}、{@link #INVISIBLE}或者{@link #GONE}.
*
* @see #inflate()
*/
@Override
@android.view.RemotableViewMethod
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();
}
}
}
/**
* 展开由{@link #getLayoutResource()}指定的布局资源,并用该资源替换父视图中的
* StubbedView.
*
* @return 展开的布局资源.
*
*/
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
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);
}
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);
}
mInflatedViewRef = new WeakReference<View>(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");
}
}
/**
* 指定展开监听器一遍获得ViewStub成功展开其布局资源的通知.
*
* @param inflateListener 成功展开后接受通知的OnInflateListener.
*
* @see android.view.ViewStub.OnInflateListener
*/
public void setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener = inflateListener;
}
/**
* 用于接收 ViewStub 成功展开其布局资源的监听器.
*
* @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
*/
public static interface OnInflateListener {
/**
* ViewStub 成功展开其布局资源后执行.该方法在展开的视图加入视图层次之后、
* 布局完成之前执行.
*
* @param stub 发生展开事件的 ViewStub.
* @param inflated 展开的视图.
*/
void onInflate(ViewStub stub, View inflated);
}
}