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