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