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