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
17package androidx.appcompat.widget;
18
19import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.annotation.SuppressLint;
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.util.AttributeSet;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.ViewParent;
30
31import androidx.annotation.RestrictTo;
32import androidx.appcompat.R;
33
34import 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)
43public 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
52    public ViewStubCompat(Context context, AttributeSet attrs) {
53        this(context, attrs, 0);
54    }
55
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     */
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     */
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     */
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     */
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     */
136    public void setLayoutInflater(LayoutInflater inflater) {
137        mInflater = inflater;
138    }
139
140    /**
141     * Get current {@link LayoutInflater} used in {@link #inflate()}.
142     */
143    public LayoutInflater getLayoutInflater() {
144        return mInflater;
145    }
146
147    @Override
148    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
149        setMeasuredDimension(0, 0);
150    }
151
152    @SuppressLint("MissingSuperCall") // Intentionally not calling super method.
153    @Override
154    public void draw(Canvas canvas) {
155    }
156
157    @Override
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
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     */
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     */
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         */
266        void onInflate(ViewStubCompat stub, View inflated);
267    }
268}