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 }