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