• Home
  • 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.view;
18 
19 import android.annotation.TargetApi;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.res.TypedArray;
23 import android.graphics.Canvas;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.os.Build.VERSION;
27 import android.os.Build.VERSION_CODES;
28 import android.util.AttributeSet;
29 import android.util.LayoutDirection;
30 import android.view.Gravity;
31 import android.view.ViewOutlineProvider;
32 import android.widget.FrameLayout;
33 import com.google.android.setupdesign.R;
34 
35 /**
36  * Class to draw the illustration of setup wizard. The {@code aspectRatio} attribute determines the
37  * aspect ratio of the top padding, which leaves space for the illustration. Draws the illustration
38  * drawable to fit the width of the view and fills the rest with the background.
39  *
40  * <p>If an aspect ratio is set, then the aspect ratio of the source drawable is maintained.
41  * Otherwise the aspect ratio will be ignored, only increasing the width of the illustration.
42  */
43 public class Illustration extends FrameLayout {
44 
45   // Size of the baseline grid in pixels
46   private float baselineGridSize;
47   private Drawable background;
48   private Drawable illustration;
49   private final Rect viewBounds = new Rect();
50   private final Rect illustrationBounds = new Rect();
51   private float scale = 1.0f;
52   private float aspectRatio = 0.0f;
53 
Illustration(Context context)54   public Illustration(Context context) {
55     super(context);
56     init(null, 0);
57   }
58 
Illustration(Context context, AttributeSet attrs)59   public Illustration(Context context, AttributeSet attrs) {
60     super(context, attrs);
61     init(attrs, 0);
62   }
63 
64   @TargetApi(VERSION_CODES.HONEYCOMB)
Illustration(Context context, AttributeSet attrs, int defStyleAttr)65   public Illustration(Context context, AttributeSet attrs, int defStyleAttr) {
66     super(context, attrs, defStyleAttr);
67     init(attrs, defStyleAttr);
68   }
69 
70   // All the constructors delegate to this init method. The 3-argument constructor is not
71   // available in FrameLayout before v11, so call super with the exact same arguments.
init(AttributeSet attrs, int defStyleAttr)72   private void init(AttributeSet attrs, int defStyleAttr) {
73     if (attrs != null) {
74       TypedArray a =
75           getContext().obtainStyledAttributes(attrs, R.styleable.SudIllustration, defStyleAttr, 0);
76       aspectRatio = a.getFloat(R.styleable.SudIllustration_sudAspectRatio, 0.0f);
77       a.recycle();
78     }
79     // Number of pixels of the 8dp baseline grid as defined in material design specs
80     baselineGridSize = getResources().getDisplayMetrics().density * 8;
81     setWillNotDraw(false);
82   }
83 
84   /**
85    * The background will be drawn to fill up the rest of the view. It will also be scaled by the
86    * same amount as the foreground so their textures look the same.
87    */
88   // Override the deprecated setBackgroundDrawable method to support API < 16. View.setBackground
89   // forwards to setBackgroundDrawable in the framework implementation.
90   @SuppressWarnings("deprecation")
91   @Override
setBackgroundDrawable(Drawable background)92   public void setBackgroundDrawable(Drawable background) {
93     if (background == this.background) {
94       return;
95     }
96     this.background = background;
97     invalidate();
98     requestLayout();
99   }
100 
101   /**
102    * Sets the drawable used as the illustration. The drawable is expected to have intrinsic width
103    * and height defined and will be scaled to fit the width of the view.
104    */
setIllustration(Drawable illustration)105   public void setIllustration(Drawable illustration) {
106     if (illustration == this.illustration) {
107       return;
108     }
109     this.illustration = illustration;
110     invalidate();
111     requestLayout();
112   }
113 
114   /**
115    * Set the aspect ratio reserved for the illustration. This overrides the top padding of the view
116    * according to the width of this view and the aspect ratio. Children views will start being laid
117    * out below this aspect ratio.
118    *
119    * @param aspectRatio A float value specifying the aspect ratio (= width / height). 0 to not
120    *     override the top padding.
121    */
setAspectRatio(float aspectRatio)122   public void setAspectRatio(float aspectRatio) {
123     this.aspectRatio = aspectRatio;
124     invalidate();
125     requestLayout();
126   }
127 
128   @Override
129   @Deprecated
setForeground(Drawable d)130   public void setForeground(Drawable d) {
131     setIllustration(d);
132   }
133 
134   @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)135   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
136     if (aspectRatio != 0.0f) {
137       int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
138       int illustrationHeight = (int) (parentWidth / aspectRatio);
139       illustrationHeight = (int) (illustrationHeight - (illustrationHeight % baselineGridSize));
140       setPadding(0, illustrationHeight, 0, 0);
141     }
142     if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
143       //noinspection AndroidLintInlinedApi
144       setOutlineProvider(ViewOutlineProvider.BOUNDS);
145     }
146     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
147   }
148 
149   @Override
onLayout(boolean changed, int left, int top, int right, int bottom)150   protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
151     final int layoutWidth = right - left;
152     final int layoutHeight = bottom - top;
153     if (illustration != null) {
154       int intrinsicWidth = illustration.getIntrinsicWidth();
155       int intrinsicHeight = illustration.getIntrinsicHeight();
156 
157       viewBounds.set(0, 0, layoutWidth, layoutHeight);
158       if (aspectRatio != 0f) {
159         scale = layoutWidth / (float) intrinsicWidth;
160         intrinsicWidth = layoutWidth;
161         intrinsicHeight = (int) (intrinsicHeight * scale);
162       }
163       Gravity.apply(
164           Gravity.FILL_HORIZONTAL | Gravity.TOP,
165           intrinsicWidth,
166           intrinsicHeight,
167           viewBounds,
168           illustrationBounds);
169       illustration.setBounds(illustrationBounds);
170     }
171     if (background != null) {
172       // Scale the background bounds by the same scale to compensate for the scale done to the
173       // canvas in onDraw.
174       background.setBounds(
175           0,
176           0,
177           (int) Math.ceil(layoutWidth / scale),
178           (int) Math.ceil((layoutHeight - illustrationBounds.height()) / scale));
179     }
180     super.onLayout(changed, left, top, right, bottom);
181   }
182 
183   @Override
onDraw(Canvas canvas)184   public void onDraw(Canvas canvas) {
185     if (background != null) {
186       // Draw the background filling parts not covered by the illustration
187       canvas.save();
188       canvas.translate(0, illustrationBounds.height());
189       // Scale the background so its size matches the foreground
190       canvas.scale(scale, scale, 0, 0);
191       if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1
192           && shouldMirrorDrawable(background, getLayoutDirection())) {
193         // Flip the illustration for RTL layouts
194         canvas.scale(-1, 1);
195         canvas.translate(-background.getBounds().width(), 0);
196       }
197       background.draw(canvas);
198       canvas.restore();
199     }
200     if (illustration != null) {
201       canvas.save();
202       if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN_MR1
203           && shouldMirrorDrawable(illustration, getLayoutDirection())) {
204         // Flip the illustration for RTL layouts
205         canvas.scale(-1, 1);
206         canvas.translate(-illustrationBounds.width(), 0);
207       }
208       // Draw the illustration
209       illustration.draw(canvas);
210       canvas.restore();
211     }
212     super.onDraw(canvas);
213   }
214 
shouldMirrorDrawable(Drawable drawable, int layoutDirection)215   private boolean shouldMirrorDrawable(Drawable drawable, int layoutDirection) {
216     if (layoutDirection == LayoutDirection.RTL) {
217       if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
218         return drawable.isAutoMirrored();
219       } else if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
220         final int flags = getContext().getApplicationInfo().flags;
221         //noinspection AndroidLintInlinedApi
222         return (flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0;
223       }
224     }
225     return false;
226   }
227 }
228