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