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