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