SetupWizardLayout.java revision d61674efcfaa9f591a44fc75d59566cdd5b409eb
1/*
2 * Copyright (C) 2015 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 com.android.setupwizardlib;
18
19import android.annotation.SuppressLint;
20import android.annotation.TargetApi;
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Shader.TileMode;
24import android.graphics.drawable.BitmapDrawable;
25import android.graphics.drawable.Drawable;
26import android.graphics.drawable.LayerDrawable;
27import android.os.Build.VERSION;
28import android.os.Build.VERSION_CODES;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.util.AttributeSet;
32import android.view.Gravity;
33import android.view.LayoutInflater;
34import android.view.View;
35import android.view.ViewGroup;
36import android.view.ViewStub;
37import android.widget.FrameLayout;
38import android.widget.TextView;
39
40import com.android.setupwizardlib.view.Illustration;
41import com.android.setupwizardlib.view.NavigationBar;
42
43public class SetupWizardLayout extends FrameLayout {
44
45    private static final String TAG = "SetupWizardLayout";
46
47    /**
48     * The container of the actual content. This will be a view in the template, which child views
49     * will be added to when {@link #addView(android.view.View)} is called. This will be the layout
50     * in the template that has the ID of {@link #getContainerId()}. For the default implementation
51     * of SetupWizardLayout, that would be @id/suw_layout_content.
52     */
53    private ViewGroup mContainer;
54
55    public SetupWizardLayout(Context context) {
56        super(context);
57        init(0, null, R.attr.suwLayoutTheme);
58    }
59
60    public SetupWizardLayout(Context context, int template) {
61        super(context);
62        init(template, null, R.attr.suwLayoutTheme);
63    }
64
65    public SetupWizardLayout(Context context, AttributeSet attrs) {
66        super(context, attrs);
67        init(0, attrs, R.attr.suwLayoutTheme);
68    }
69
70    @TargetApi(VERSION_CODES.HONEYCOMB)
71    public SetupWizardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
72        super(context, attrs, defStyleAttr);
73        init(0, attrs, defStyleAttr);
74    }
75
76    @TargetApi(VERSION_CODES.HONEYCOMB)
77    public SetupWizardLayout(Context context, int template, AttributeSet attrs, int defStyleAttr) {
78        super(context, attrs, defStyleAttr);
79        init(template, attrs, defStyleAttr);
80    }
81
82    // All the constructors delegate to this init method. The 3-argument constructor is not
83    // available in LinearLayout before v11, so call super with the exact same arguments.
84    private void init(int template, AttributeSet attrs, int defStyleAttr) {
85        final TypedArray a = getContext().obtainStyledAttributes(attrs,
86                R.styleable.SuwSetupWizardLayout, defStyleAttr, 0);
87        if (template == 0) {
88            template = a.getResourceId(R.styleable.SuwSetupWizardLayout_android_layout, 0);
89        }
90        inflateTemplate(template);
91
92        // Set the background from XML, either directly or built from a bitmap tile
93        final Drawable background =
94                a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackground);
95        if (background != null) {
96            setLayoutBackground(background);
97        } else {
98            final Drawable backgroundTile =
99                    a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackgroundTile);
100            if (backgroundTile != null) {
101                setBackgroundTile(backgroundTile);
102            }
103        }
104
105        // Set the illustration from XML, either directly or built from image + horizontal tile
106        final Drawable illustration =
107                a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustration);
108        if (illustration != null) {
109            setIllustration(illustration);
110        } else {
111            final Drawable illustrationImage =
112                    a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustrationImage);
113            final Drawable horizontalTile = a.getDrawable(
114                    R.styleable.SuwSetupWizardLayout_suwIllustrationHorizontalTile);
115            if (illustrationImage != null && horizontalTile != null) {
116                setIllustration(illustrationImage, horizontalTile);
117            }
118        }
119
120        // Set the header text
121        final CharSequence headerText =
122                a.getText(R.styleable.SuwSetupWizardLayout_suwHeaderText);
123        if (headerText != null) {
124            setHeaderText(headerText);
125        }
126
127        a.recycle();
128    }
129
130    @Override
131    protected Parcelable onSaveInstanceState() {
132        final Parcelable parcelable = super.onSaveInstanceState();
133        final SavedState ss = new SavedState(parcelable);
134        ss.isProgressBarShown = isProgressBarShown();
135        return ss;
136    }
137
138    @Override
139    protected void onRestoreInstanceState(Parcelable state) {
140        final SavedState ss = (SavedState) state;
141        super.onRestoreInstanceState(ss.getSuperState());
142        final boolean isProgressBarShown = ss.isProgressBarShown;
143        if (isProgressBarShown) {
144            showProgressBar();
145        } else {
146            hideProgressBar();
147        }
148    }
149
150    @Override
151    public void addView(View child, int index, ViewGroup.LayoutParams params) {
152        mContainer.addView(child, index, params);
153    }
154
155    private void addViewInternal(View child) {
156        super.addView(child, -1, generateDefaultLayoutParams());
157    }
158
159    private void inflateTemplate(int templateResource) {
160        final LayoutInflater inflater = LayoutInflater.from(getContext());
161        final View templateRoot = onInflateTemplate(inflater, templateResource);
162        addViewInternal(templateRoot);
163
164        mContainer = (ViewGroup) findViewById(getContainerId());
165        onTemplateInflated();
166    }
167
168    /**
169     * This method inflates the template. Subclasses can override this method to customize the
170     * template inflation, or change to a different default template. The root of the inflated
171     * layout should be returned, and not added to the view hierarchy.
172     *
173     * @param inflater A LayoutInflater to inflate the template.
174     * @param template The resource ID of the template to be inflated, or 0 if no template is
175     *                 specified.
176     * @return Root of the inflated layout.
177     */
178    protected View onInflateTemplate(LayoutInflater inflater, int template) {
179        if (template == 0) {
180            template = R.layout.suw_template;
181        }
182        return inflater.inflate(template, this, false);
183    }
184
185    /**
186     * This is called after the template has been inflated and added to the view hierarchy.
187     * Subclasses can implement this method to modify the template as necessary, such as caching
188     * views retrieved from findViewById, or other view operations that need to be done in code.
189     * You can think of this as {@link android.view.View#onFinishInflate()} but for inflation of the
190     * template instead of for child views.
191     */
192    protected void onTemplateInflated() {
193    }
194
195    protected int getContainerId() {
196        return R.id.suw_layout_content;
197    }
198
199    public NavigationBar getNavigationBar() {
200        final View view = findViewById(R.id.suw_layout_navigation_bar);
201        return view instanceof NavigationBar ? (NavigationBar) view : null;
202    }
203
204    public void setHeaderText(int title) {
205        final TextView titleView = (TextView) findViewById(R.id.suw_layout_title);
206        if (titleView != null) {
207            titleView.setText(title);
208        }
209    }
210
211    public void setHeaderText(CharSequence title) {
212        final TextView titleView = (TextView) findViewById(R.id.suw_layout_title);
213        if (titleView != null) {
214            titleView.setText(title);
215        }
216    }
217
218    /**
219     * Set the illustration of the layout. The drawable will be applied as is, and the bounds will
220     * be set as implemented in {@link com.android.setupwizardlib.view.Illustration}. To create
221     * a suitable drawable from an asset and a horizontal repeating tile, use
222     * {@link #setIllustration(int, int)} instead.
223     *
224     * @param drawable The drawable specifying the illustration.
225     */
226    public void setIllustration(Drawable drawable) {
227        final View view = findViewById(R.id.suw_layout_decor);
228        if (view instanceof Illustration) {
229            final Illustration illustration = (Illustration) view;
230            illustration.setIllustration(drawable);
231        }
232    }
233
234    /**
235     * Set the illustration of the layout, which will be created asset and the horizontal tile as
236     * suitable. On phone layouts (not sw600dp), the asset will be scaled, maintaining aspect ratio.
237     * On tablets (sw600dp), the assets will always have 256dp height and the rest of the
238     * illustration area that the asset doesn't fill will be covered by the horizontalTile.
239     *
240     * @param asset Resource ID of the illustration asset.
241     * @param horizontalTile Resource ID of the horizontally repeating tile for tablet layout.
242     */
243    public void setIllustration(int asset, int horizontalTile) {
244        final View view = findViewById(R.id.suw_layout_decor);
245        if (view instanceof Illustration) {
246            final Illustration illustration = (Illustration) view;
247            final Drawable illustrationDrawable = getIllustration(asset, horizontalTile);
248            illustration.setIllustration(illustrationDrawable);
249        }
250    }
251
252    private void setIllustration(Drawable asset, Drawable horizontalTile) {
253        final View view = findViewById(R.id.suw_layout_decor);
254        if (view instanceof Illustration) {
255            final Illustration illustration = (Illustration) view;
256            final Drawable illustrationDrawable = getIllustration(asset, horizontalTile);
257            illustration.setIllustration(illustrationDrawable);
258        }
259    }
260
261    /**
262     * Set the background of the layout, which is expected to be able to extend infinitely. If it is
263     * a bitmap tile and you want it to repeat, use {@link #setBackgroundTile(int)} instead.
264     */
265    public void setLayoutBackground(Drawable background) {
266        final View view = findViewById(R.id.suw_layout_decor);
267        if (view != null) {
268            //noinspection deprecation
269            view.setBackgroundDrawable(background);
270        }
271    }
272
273    /**
274     * Set the background of the layout to a repeating bitmap tile. To use a different kind of
275     * drawable, use {@link #setLayoutBackground(android.graphics.drawable.Drawable)} instead.
276     */
277    public void setBackgroundTile(int backgroundTile) {
278        final Drawable backgroundTileDrawable =
279                getContext().getResources().getDrawable(backgroundTile);
280        setBackgroundTile(backgroundTileDrawable);
281    }
282
283    private void setBackgroundTile(Drawable backgroundTile) {
284        if (backgroundTile instanceof BitmapDrawable) {
285            ((BitmapDrawable) backgroundTile).setTileModeXY(TileMode.REPEAT, TileMode.REPEAT);
286        }
287        setLayoutBackground(backgroundTile);
288    }
289
290    private Drawable getIllustration(int asset, int horizontalTile) {
291        final Context context = getContext();
292        final Drawable assetDrawable = context.getResources().getDrawable(asset);
293        final Drawable tile = context.getResources().getDrawable(horizontalTile);
294        return getIllustration(assetDrawable, tile);
295    }
296
297    @SuppressLint("RtlHardcoded")
298    private Drawable getIllustration(Drawable asset, Drawable horizontalTile) {
299        final Context context = getContext();
300        if (context.getResources().getBoolean(R.bool.suwUseTabletLayout)) {
301            // If it is a "tablet" (sw600dp), create a LayerDrawable with the horizontal tile.
302            if (horizontalTile instanceof BitmapDrawable) {
303                ((BitmapDrawable) horizontalTile).setTileModeX(TileMode.REPEAT);
304                ((BitmapDrawable) horizontalTile).setGravity(Gravity.TOP);
305            }
306            if (asset instanceof BitmapDrawable) {
307                // Always specify TOP | LEFT, Illustration will flip the entire LayerDrawable.
308                ((BitmapDrawable) asset).setGravity(Gravity.TOP | Gravity.LEFT);
309            }
310            final LayerDrawable layers =
311                    new LayerDrawable(new Drawable[] { horizontalTile, asset });
312            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
313                layers.setAutoMirrored(true);
314            }
315            return layers;
316        } else {
317            // If it is a "phone" (not sw600dp), simply return the illustration
318            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
319                asset.setAutoMirrored(true);
320            }
321            return asset;
322        }
323    }
324
325    public boolean isProgressBarShown() {
326        final View progressBar = findViewById(R.id.suw_layout_progress);
327        return progressBar != null && progressBar.getVisibility() == View.VISIBLE;
328    }
329
330    public void showProgressBar() {
331        final View progressBar = findViewById(R.id.suw_layout_progress);
332        if (progressBar != null) {
333            progressBar.setVisibility(View.VISIBLE);
334        } else {
335            final ViewStub progressBarStub = (ViewStub) findViewById(R.id.suw_layout_progress_stub);
336            if (progressBarStub != null) {
337                progressBarStub.inflate();
338            }
339        }
340    }
341
342    public void hideProgressBar() {
343        final View progressBar = findViewById(R.id.suw_layout_progress);
344        if (progressBar != null) {
345            progressBar.setVisibility(View.GONE);
346        }
347    }
348
349    protected static class SavedState extends BaseSavedState {
350
351        boolean isProgressBarShown = false;
352
353        public SavedState(Parcelable parcelable) {
354            super(parcelable);
355        }
356
357        public SavedState(Parcel source) {
358            super(source);
359            isProgressBarShown = source.readInt() != 0;
360        }
361
362        @Override
363        public void writeToParcel(Parcel dest, int flags) {
364            super.writeToParcel(dest, flags);
365            dest.writeInt(isProgressBarShown ? 1 : 0);
366        }
367
368        public static final Parcelable.Creator<SavedState> CREATOR =
369                new Parcelable.Creator<SavedState>() {
370
371                    @Override
372                    public SavedState createFromParcel(Parcel parcel) {
373                        return new SavedState(parcel);
374                    }
375
376                    @Override
377                    public SavedState[] newArray(int size) {
378                        return new SavedState[size];
379                    }
380                };
381    }
382}
383