SetupWizardLayout.java revision 9f9367672191190f903955d09a4314d40869acc6
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.util.AttributeSet;
30import android.view.Gravity;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.widget.FrameLayout;
35import android.widget.TextView;
36
37import com.android.setupwizardlib.view.Illustration;
38import com.android.setupwizardlib.view.NavigationBar;
39
40public class SetupWizardLayout extends FrameLayout {
41
42    private static final String TAG = "SetupWizardLayout";
43
44    /**
45     * The container of the actual content. This will be a view in the template, which child views
46     * will be added to when {@link #addView(android.view.View)} is called. This will be the layout
47     * in the template that has the ID of {@link #getContainerId()}. For the default implementation
48     * of SetupWizardLayout, that would be @id/suw_layout_content.
49     */
50    private ViewGroup mContainer;
51
52    public SetupWizardLayout(Context context) {
53        super(context);
54        init(0, null, R.attr.suwLayoutTheme);
55    }
56
57    public SetupWizardLayout(Context context, int template) {
58        super(context);
59        init(template, null, R.attr.suwLayoutTheme);
60    }
61
62    public SetupWizardLayout(Context context, AttributeSet attrs) {
63        super(context, attrs);
64        init(0, attrs, R.attr.suwLayoutTheme);
65    }
66
67    @TargetApi(VERSION_CODES.HONEYCOMB)
68    public SetupWizardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
69        super(context, attrs, defStyleAttr);
70        init(0, attrs, defStyleAttr);
71    }
72
73    @TargetApi(VERSION_CODES.HONEYCOMB)
74    public SetupWizardLayout(Context context, int template, AttributeSet attrs, int defStyleAttr) {
75        super(context, attrs, defStyleAttr);
76        init(template, attrs, defStyleAttr);
77    }
78
79    // All the constructors delegate to this init method. The 3-argument constructor is not
80    // available in LinearLayout before v11, so call super with the exact same arguments.
81    private void init(int template, AttributeSet attrs, int defStyleAttr) {
82        final TypedArray a = getContext().obtainStyledAttributes(attrs,
83                R.styleable.SuwSetupWizardLayout, defStyleAttr, 0);
84        if (template == 0) {
85            template = a.getResourceId(R.styleable.SuwSetupWizardLayout_android_layout, 0);
86        }
87        inflateTemplate(template);
88
89        // Set the background from XML, either directly or built from a bitmap tile
90        final Drawable background =
91                a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackground);
92        if (background != null) {
93            setLayoutBackground(background);
94        } else {
95            final Drawable backgroundTile =
96                    a.getDrawable(R.styleable.SuwSetupWizardLayout_suwBackgroundTile);
97            if (backgroundTile != null) {
98                setBackgroundTile(backgroundTile);
99            }
100        }
101
102        // Set the illustration from XML, either directly or built from image + horizontal tile
103        final Drawable illustration =
104                a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustration);
105        if (illustration != null) {
106            setIllustration(illustration);
107        } else {
108            final Drawable illustrationImage =
109                    a.getDrawable(R.styleable.SuwSetupWizardLayout_suwIllustrationImage);
110            final Drawable horizontalTile = a.getDrawable(
111                    R.styleable.SuwSetupWizardLayout_suwIllustrationHorizontalTile);
112            if (illustrationImage != null && horizontalTile != null) {
113                setIllustration(illustrationImage, horizontalTile);
114            }
115        }
116
117        // Set the header text
118        final CharSequence headerText =
119                a.getText(R.styleable.SuwSetupWizardLayout_suwHeaderText);
120        if (headerText != null) {
121            setHeaderText(headerText);
122        }
123
124        a.recycle();
125    }
126
127    @Override
128    public void addView(View child, int index, ViewGroup.LayoutParams params) {
129        mContainer.addView(child, index, params);
130    }
131
132    private void addViewInternal(View child) {
133        super.addView(child, -1, generateDefaultLayoutParams());
134    }
135
136    private void inflateTemplate(int templateResource) {
137        final LayoutInflater inflater = LayoutInflater.from(getContext());
138        final View templateRoot = onInflateTemplate(inflater, templateResource);
139        addViewInternal(templateRoot);
140
141        mContainer = (ViewGroup) findViewById(getContainerId());
142        onTemplateInflated();
143    }
144
145    /**
146     * This method inflates the template. Subclasses can override this method to customize the
147     * template inflation, or change to a different default template. The root of the inflated
148     * layout should be returned, and not added to the view hierarchy.
149     *
150     * @param inflater A LayoutInflater to inflate the template.
151     * @param template The resource ID of the template to be inflated, or 0 if no template is
152     *                 specified.
153     * @return Root of the inflated layout.
154     */
155    protected View onInflateTemplate(LayoutInflater inflater, int template) {
156        if (template == 0) {
157            template = R.layout.suw_template;
158        }
159        return inflater.inflate(template, this, false);
160    }
161
162    /**
163     * This is called after the template has been inflated and added to the view hierarchy.
164     * Subclasses can implement this method to modify the template as necessary, such as caching
165     * views retrieved from findViewById, or other view operations that need to be done in code.
166     * You can think of this as {@link android.view.View#onFinishInflate()} but for inflation of the
167     * template instead of for child views.
168     */
169    protected void onTemplateInflated() {
170    }
171
172    protected int getContainerId() {
173        return R.id.suw_layout_content;
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 void setHeaderText(int title) {
182        final TextView titleView = (TextView) findViewById(R.id.suw_layout_title);
183        if (titleView != null) {
184            titleView.setText(title);
185        }
186    }
187
188    public void setHeaderText(CharSequence title) {
189        final TextView titleView = (TextView) findViewById(R.id.suw_layout_title);
190        if (titleView != null) {
191            titleView.setText(title);
192        }
193    }
194
195    /**
196     * Set the illustration of the layout. The drawable will be applied as is, and the bounds will
197     * be set as implemented in {@link com.android.setupwizardlib.view.Illustration}. To create
198     * a suitable drawable from an asset and a horizontal repeating tile, use
199     * {@link #setIllustration(int, int)} instead.
200     *
201     * @param drawable The drawable specifying the illustration.
202     */
203    public void setIllustration(Drawable drawable) {
204        final View view = findViewById(R.id.suw_layout_decor);
205        if (view instanceof Illustration) {
206            final Illustration illustration = (Illustration) view;
207            illustration.setIllustration(drawable);
208        }
209    }
210
211    /**
212     * Set the illustration of the layout, which will be created asset and the horizontal tile as
213     * suitable. On phone layouts (not sw600dp), the asset will be scaled, maintaining aspect ratio.
214     * On tablets (sw600dp), the assets will always have 256dp height and the rest of the
215     * illustration area that the asset doesn't fill will be covered by the horizontalTile.
216     *
217     * @param asset Resource ID of the illustration asset.
218     * @param horizontalTile Resource ID of the horizontally repeating tile for tablet layout.
219     */
220    public void setIllustration(int asset, int horizontalTile) {
221        final View view = findViewById(R.id.suw_layout_decor);
222        if (view instanceof Illustration) {
223            final Illustration illustration = (Illustration) view;
224            final Drawable illustrationDrawable = getIllustration(asset, horizontalTile);
225            illustration.setIllustration(illustrationDrawable);
226        }
227    }
228
229    private void setIllustration(Drawable asset, Drawable horizontalTile) {
230        final View view = findViewById(R.id.suw_layout_decor);
231        if (view instanceof Illustration) {
232            final Illustration illustration = (Illustration) view;
233            final Drawable illustrationDrawable = getIllustration(asset, horizontalTile);
234            illustration.setIllustration(illustrationDrawable);
235        }
236    }
237
238    /**
239     * Set the background of the layout, which is expected to be able to extend infinitely. If it is
240     * a bitmap tile and you want it to repeat, use {@link #setBackgroundTile(int)} instead.
241     */
242    public void setLayoutBackground(Drawable background) {
243        final View view = findViewById(R.id.suw_layout_decor);
244        if (view != null) {
245            //noinspection deprecation
246            view.setBackgroundDrawable(background);
247        }
248    }
249
250    /**
251     * Set the background of the layout to a repeating bitmap tile. To use a different kind of
252     * drawable, use {@link #setLayoutBackground(android.graphics.drawable.Drawable)} instead.
253     */
254    public void setBackgroundTile(int backgroundTile) {
255        final Drawable backgroundTileDrawable =
256                getContext().getResources().getDrawable(backgroundTile);
257        setBackgroundTile(backgroundTileDrawable);
258    }
259
260    private void setBackgroundTile(Drawable backgroundTile) {
261        if (backgroundTile instanceof BitmapDrawable) {
262            ((BitmapDrawable) backgroundTile).setTileModeXY(TileMode.REPEAT, TileMode.REPEAT);
263        }
264        setLayoutBackground(backgroundTile);
265    }
266
267    private Drawable getIllustration(int asset, int horizontalTile) {
268        final Context context = getContext();
269        final Drawable assetDrawable = context.getResources().getDrawable(asset);
270        final Drawable tile = context.getResources().getDrawable(horizontalTile);
271        return getIllustration(assetDrawable, tile);
272    }
273
274    @SuppressLint("RtlHardcoded")
275    private Drawable getIllustration(Drawable asset, Drawable horizontalTile) {
276        final Context context = getContext();
277        if (context.getResources().getBoolean(R.bool.suwUseTabletLayout)) {
278            // If it is a "tablet" (sw600dp), create a LayerDrawable with the horizontal tile.
279            if (horizontalTile instanceof BitmapDrawable) {
280                ((BitmapDrawable) horizontalTile).setTileModeX(TileMode.REPEAT);
281                ((BitmapDrawable) horizontalTile).setGravity(Gravity.TOP);
282            }
283            if (asset instanceof BitmapDrawable) {
284                // Always specify TOP | LEFT, Illustration will flip the entire LayerDrawable.
285                ((BitmapDrawable) asset).setGravity(Gravity.TOP | Gravity.LEFT);
286            }
287            final LayerDrawable layers =
288                    new LayerDrawable(new Drawable[] { horizontalTile, asset });
289            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
290                layers.setAutoMirrored(true);
291            }
292            return layers;
293        } else {
294            // If it is a "phone" (not sw600dp), simply return the illustration
295            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
296                asset.setAutoMirrored(true);
297            }
298            return asset;
299        }
300    }
301}
302