1 /*
2  * Copyright (C) 2014 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 androidx.appcompat.widget;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 
21 import android.annotation.SuppressLint;
22 import android.content.Context;
23 import android.content.res.TypedArray;
24 import android.graphics.Canvas;
25 import android.util.AttributeSet;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.view.ViewParent;
30 
31 import androidx.annotation.RestrictTo;
32 import androidx.appcompat.R;
33 
34 import java.lang.ref.WeakReference;
35 
36 /**
37  * Backport of {@link android.view.ViewStub} so that we can set the
38  * {@link android.view.LayoutInflater} on devices before Jelly Bean.
39  *
40  * @hide
41  */
42 @RestrictTo(LIBRARY_GROUP)
43 public final class ViewStubCompat extends View {
44     private int mLayoutResource = 0;
45     private int mInflatedId;
46 
47     private WeakReference<View> mInflatedViewRef;
48 
49     private LayoutInflater mInflater;
50     private OnInflateListener mInflateListener;
51 
ViewStubCompat(Context context, AttributeSet attrs)52     public ViewStubCompat(Context context, AttributeSet attrs) {
53         this(context, attrs, 0);
54     }
55 
ViewStubCompat(Context context, AttributeSet attrs, int defStyle)56     public ViewStubCompat(Context context, AttributeSet attrs, int defStyle) {
57         super(context, attrs, defStyle);
58 
59         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStubCompat,
60                 defStyle, 0);
61 
62         mInflatedId = a.getResourceId(R.styleable.ViewStubCompat_android_inflatedId, NO_ID);
63         mLayoutResource = a.getResourceId(R.styleable.ViewStubCompat_android_layout, 0);
64 
65         setId(a.getResourceId(R.styleable.ViewStubCompat_android_id, NO_ID));
66         a.recycle();
67 
68         setVisibility(GONE);
69         setWillNotDraw(true);
70     }
71 
72     /**
73      * Returns the id taken by the inflated view. If the inflated id is
74      * {@link View#NO_ID}, the inflated view keeps its original id.
75      *
76      * @return A positive integer used to identify the inflated view or
77      *         {@link #NO_ID} if the inflated view should keep its id.
78      *
79      * @see #setInflatedId(int)
80      * @attr name android:inflatedId
81      */
getInflatedId()82     public int getInflatedId() {
83         return mInflatedId;
84     }
85 
86     /**
87      * Defines the id taken by the inflated view. If the inflated id is
88      * {@link View#NO_ID}, the inflated view keeps its original id.
89      *
90      * @param inflatedId A positive integer used to identify the inflated view or
91      *                   {@link #NO_ID} if the inflated view should keep its id.
92      *
93      * @see #getInflatedId()
94      * @attr name android:inflatedId
95      */
setInflatedId(int inflatedId)96     public void setInflatedId(int inflatedId) {
97         mInflatedId = inflatedId;
98     }
99 
100     /**
101      * Returns the layout resource that will be used by {@link #setVisibility(int)} or
102      * {@link #inflate()} to replace this StubbedView
103      * in its parent by another view.
104      *
105      * @return The layout resource identifier used to inflate the new View.
106      *
107      * @see #setLayoutResource(int)
108      * @see #setVisibility(int)
109      * @see #inflate()
110      * @attr name android:layout
111      */
getLayoutResource()112     public int getLayoutResource() {
113         return mLayoutResource;
114     }
115 
116     /**
117      * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
118      * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
119      * used to replace this StubbedView in its parent.
120      *
121      * @param layoutResource A valid layout resource identifier (different from 0.)
122      *
123      * @see #getLayoutResource()
124      * @see #setVisibility(int)
125      * @see #inflate()
126      * @attr name android:layout
127      */
setLayoutResource(int layoutResource)128     public void setLayoutResource(int layoutResource) {
129         mLayoutResource = layoutResource;
130     }
131 
132     /**
133      * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
134      * to use the default.
135      */
setLayoutInflater(LayoutInflater inflater)136     public void setLayoutInflater(LayoutInflater inflater) {
137         mInflater = inflater;
138     }
139 
140     /**
141      * Get current {@link LayoutInflater} used in {@link #inflate()}.
142      */
getLayoutInflater()143     public LayoutInflater getLayoutInflater() {
144         return mInflater;
145     }
146 
147     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)148     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
149         setMeasuredDimension(0, 0);
150     }
151 
152     @SuppressLint("MissingSuperCall") // Intentionally not calling super method.
153     @Override
draw(Canvas canvas)154     public void draw(Canvas canvas) {
155     }
156 
157     @Override
dispatchDraw(Canvas canvas)158     protected void dispatchDraw(Canvas canvas) {
159     }
160 
161     /**
162      * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
163      * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
164      * by the inflated layout resource. After that calls to this function are passed
165      * through to the inflated view.
166      *
167      * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
168      *
169      * @see #inflate()
170      */
171     @Override
setVisibility(int visibility)172     public void setVisibility(int visibility) {
173         if (mInflatedViewRef != null) {
174             View view = mInflatedViewRef.get();
175             if (view != null) {
176                 view.setVisibility(visibility);
177             } else {
178                 throw new IllegalStateException("setVisibility called on un-referenced view");
179             }
180         } else {
181             super.setVisibility(visibility);
182             if (visibility == VISIBLE || visibility == INVISIBLE) {
183                 inflate();
184             }
185         }
186     }
187 
188     /**
189      * Inflates the layout resource identified by {@link #getLayoutResource()}
190      * and replaces this StubbedView in its parent by the inflated layout resource.
191      *
192      * @return The inflated layout resource.
193      *
194      */
inflate()195     public View inflate() {
196         final ViewParent viewParent = getParent();
197 
198         if (viewParent != null && viewParent instanceof ViewGroup) {
199             if (mLayoutResource != 0) {
200                 final ViewGroup parent = (ViewGroup) viewParent;
201                 final LayoutInflater factory;
202                 if (mInflater != null) {
203                     factory = mInflater;
204                 } else {
205                     factory = LayoutInflater.from(getContext());
206                 }
207                 final View view = factory.inflate(mLayoutResource, parent,
208                         false);
209 
210                 if (mInflatedId != NO_ID) {
211                     view.setId(mInflatedId);
212                 }
213 
214                 final int index = parent.indexOfChild(this);
215                 parent.removeViewInLayout(this);
216 
217                 final ViewGroup.LayoutParams layoutParams = getLayoutParams();
218                 if (layoutParams != null) {
219                     parent.addView(view, index, layoutParams);
220                 } else {
221                     parent.addView(view, index);
222                 }
223 
224                 mInflatedViewRef = new WeakReference<View>(view);
225 
226                 if (mInflateListener != null) {
227                     mInflateListener.onInflate(this, view);
228                 }
229 
230                 return view;
231             } else {
232                 throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
233             }
234         } else {
235             throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
236         }
237     }
238 
239     /**
240      * Specifies the inflate listener to be notified after this ViewStub successfully
241      * inflated its layout resource.
242      *
243      * @param inflateListener The OnInflateListener to notify of successful inflation.
244      *
245      * @see android.view.ViewStub.OnInflateListener
246      */
setOnInflateListener(OnInflateListener inflateListener)247     public void setOnInflateListener(OnInflateListener inflateListener) {
248         mInflateListener = inflateListener;
249     }
250 
251     /**
252      * Listener used to receive a notification after a ViewStub has successfully
253      * inflated its layout resource.
254      *
255      * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
256      */
257     public static interface OnInflateListener {
258         /**
259          * Invoked after a ViewStub successfully inflated its layout resource.
260          * This method is invoked after the inflated view was added to the
261          * hierarchy but before the layout pass.
262          *
263          * @param stub The ViewStub that initiated the inflation.
264          * @param inflated The inflated View.
265          */
onInflate(ViewStubCompat stub, View inflated)266         void onInflate(ViewStubCompat stub, View inflated);
267     }
268 }