[SuwLib] Implement pattern drawable background

- Implement the pattern background for GLIF using a custom drawable.
  The drawable draws the paths manually as specified in the SVG, for
  backwards compatibility.
- Use a FrameLayout subclass to draw the custom status bar background
  in the case of phone layouts, and set draw the background directly
  for tablet / clamshell layouts.

Bug: 25650709
Change-Id: I527bb47efa143ec43c1030b57087fd2414d7045b
This commit is contained in:
Maurice Lam 2015-11-13 18:19:09 -08:00
parent 38064bc73a
commit c3eebe9f66
15 changed files with 520 additions and 8 deletions

View file

@ -67,7 +67,7 @@
<item name="android:indeterminateTintMode" tools:ignore="NewApi">src_in</item>
<item name="android:listPreferredItemHeight">@dimen/suw_items_preferred_height</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@android:color/black</item>
<item name="android:statusBarColor" tools:ignore="NewApi">?attr/colorPrimary</item>
<item name="android:statusBarColor" tools:ignore="NewApi">@android:color/transparent</item>
<item name="android:textColorLink">@color/suw_link_color_light</item>
<item name="android:windowAnimationStyle">@style/Animation.SuwWindowAnimation</item>
<item name="android:windowDisablePreview">true</item>

View file

@ -16,9 +16,11 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suw_pattern_bg"
style="@style/SuwGlifCardBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
style="@style/SuwGlifCardContainer"

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.setupwizardlib.view.StatusBarBackgroundLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suw_pattern_bg"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/suw_glif_recycler_template_content" />
</com.android.setupwizardlib.view.StatusBarBackgroundLayout>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2015 The Android Open Source Project
@ -20,7 +20,7 @@
<item name="suw_recycler_template" type="layout">@layout/suw_recycler_template_header</item>
<item name="suw_recycler_template_short" type="layout">@layout/suw_recycler_template_header_collapsed</item>
<item name="suw_glif_recycler_template" type="layout">@layout/suw_glif_recycler_template_content</item>
<item name="suw_glif_recycler_template" type="layout">@layout/suw_glif_recycler_template_compact</item>
</resources>

View file

@ -16,9 +16,11 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suw_pattern_bg"
style="@style/SuwGlifCardBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
style="@style/SuwGlifCardContainer"

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.setupwizardlib.view.StatusBarBackgroundLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suw_pattern_bg"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/suw_glif_list_template_content" />
</com.android.setupwizardlib.view.StatusBarBackgroundLayout>

View file

@ -16,9 +16,11 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suw_pattern_bg"
style="@style/SuwGlifCardBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
style="@style/SuwGlifCardContainer"

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.setupwizardlib.view.StatusBarBackgroundLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suw_pattern_bg"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/suw_glif_template_content" />
</com.android.setupwizardlib.view.StatusBarBackgroundLayout>

View file

@ -60,6 +60,10 @@
<attr name="suwDividerInset" />
</declare-styleable>
<declare-styleable name="SuwStatusBarBackgroundLayout">
<attr name="suwStatusBarBackground" format="color|reference" />
</declare-styleable>
<declare-styleable name="SuwSetupWizardLayout">
<attr name="suwBackground" format="color|reference" />
<attr name="suwBackgroundTile" format="color|reference" />

View file

@ -24,8 +24,8 @@
<item name="suw_no_scroll_template" type="layout">@layout/suw_no_scroll_template_header</item>
<item name="suw_no_scroll_template_short" type="layout">@layout/suw_no_scroll_template_header_collapsed</item>
<item name="suw_glif_template" type="layout">@layout/suw_glif_template_content</item>
<item name="suw_glif_list_template" type="layout">@layout/suw_glif_list_template_content</item>
<item name="suw_glif_template" type="layout">@layout/suw_glif_template_compact</item>
<item name="suw_glif_list_template" type="layout">@layout/suw_glif_list_template_compact</item>
</resources>

View file

@ -21,6 +21,7 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@ -30,6 +31,8 @@ import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import com.android.setupwizardlib.view.StatusBarBackgroundLayout;
/**
* Layout for the GLIF theme used in Setup Wizard for N.
*
@ -103,6 +106,19 @@ public class GlifLayout extends TemplateLayout {
}
a.recycle();
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
final View patternBg = findViewById(R.id.suw_pattern_bg);
if (patternBg != null) {
final GlifPatternDrawable background = GlifPatternDrawable.getDefault(getContext());
if (patternBg instanceof StatusBarBackgroundLayout) {
((StatusBarBackgroundLayout) patternBg).setStatusBarBackground(background);
} else {
patternBg.setBackground(background);
}
}
}
}
@Override

View file

@ -0,0 +1,198 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.setupwizardlib;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import com.android.setupwizardlib.annotations.VisibleForTesting;
public class GlifPatternDrawable extends Drawable {
/* static section */
@SuppressLint("InlinedApi")
private static final int[] ATTRS_PRIMARY_COLOR = new int[]{ android.R.attr.colorPrimary };
private static final float VIEWBOX_HEIGHT = 768f;
private static final float VIEWBOX_WIDTH = 1366f;
private static final float SCALE_FOCUS_X = 200f;
private static final float SCALE_FOCUS_Y = 175f;
public static GlifPatternDrawable getDefault(Context context) {
int colorPrimary = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final TypedArray a = context.obtainStyledAttributes(ATTRS_PRIMARY_COLOR);
colorPrimary = a.getColor(0, Color.BLACK);
a.recycle();
}
return new GlifPatternDrawable(colorPrimary);
}
/* non-static section */
private int mColor;
private Paint mPaint;
private float[] mTempHsv = new float[3];
private Path mTempPath = new Path();
public GlifPatternDrawable(int color) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
setColor(color);
}
@Override
public void draw(Canvas canvas) {
canvas.save();
canvas.clipRect(getBounds());
Color.colorToHSV(mColor, mTempHsv);
scaleCanvasToBounds(canvas);
// Draw the pattern by creating the paths, adjusting the colors and drawing them. The path
// values are extracted from the SVG of the pattern file.
mTempHsv[2] = 0.753f;
Path p = mTempPath;
p.reset();
p.moveTo(1029.4f, 357.5f);
p.lineTo(1366f, 759.1f);
p.lineTo(1366f, 0f);
p.lineTo(1137.7f, 0f);
p.close();
drawPath(canvas, p, mTempHsv);
mTempHsv[2] = 0.78f;
p.reset();
p.moveTo(1138.1f, 0f);
p.rLineTo(-144.8f, 768f);
p.rLineTo(372.7f, 0f);
p.rLineTo(0f, -524f);
p.cubicTo(1290.7f, 121.6f, 1219.2f, 41.1f, 1178.7f, 0f);
p.close();
drawPath(canvas, p, mTempHsv);
mTempHsv[2] = 0.804f;
p.reset();
p.moveTo(949.8f, 768f);
p.rCubicTo(92.6f, -170.6f, 213f, -440.3f, 269.4f, -768f);
p.lineTo(585f, 0f);
p.rLineTo(2.1f, 766f);
p.close();
drawPath(canvas, p, mTempHsv);
mTempHsv[2] = 0.827f;
p.reset();
p.moveTo(471.1f, 768f);
p.rMoveTo(704.5f, 0f);
p.cubicTo(1123.6f, 563.3f, 1027.4f, 275.2f, 856.2f, 0f);
p.lineTo(476.4f, 0f);
p.rLineTo(-5.3f, 768f);
p.close();
drawPath(canvas, p, mTempHsv);
mTempHsv[2] = 0.867f;
p.reset();
p.moveTo(323.1f, 768f);
p.moveTo(777.5f, 768f);
p.cubicTo(661.9f, 348.8f, 427.2f, 21.4f, 401.2f, 25.4f);
p.lineTo(323.1f, 768f);
p.close();
drawPath(canvas, p, mTempHsv);
mTempHsv[2] = 0.894f;
p.reset();
p.moveTo(178.44286f, 766.85714f);
p.lineTo(308.7f, 768f);
p.cubicTo(381.7f, 604.6f, 481.6f, 344.3f, 562.2f, 0f);
p.lineTo(0f, 0f);
p.close();
drawPath(canvas, p, mTempHsv);
mTempHsv[2] = 0.929f;
p.reset();
p.moveTo(146f, 0f);
p.lineTo(0f, 0f);
p.lineTo(0f, 768f);
p.lineTo(394.2f, 768f);
p.cubicTo(327.7f, 475.3f, 228.5f, 201f, 146f, 0f);
p.close();
drawPath(canvas, p, mTempHsv);
canvas.restore();
}
@VisibleForTesting
public void scaleCanvasToBounds(Canvas canvas) {
final Rect bounds = getBounds();
final int height = bounds.height();
final int width = bounds.width();
float scaleY = height / VIEWBOX_HEIGHT;
float scaleX = width / VIEWBOX_WIDTH;
// First scale both sides to fit independently.
canvas.scale(scaleX, scaleY);
if (scaleY > scaleX) {
// Adjust x-scale to maintain aspect ratio, but using (200, 0) as the pivot instead,
// so that more of the texture and less of the blank space on the left edge is seen.
canvas.scale(scaleY / scaleX, 1f, SCALE_FOCUS_X, 0f);
} else {
// Adjust y-scale to maintain aspect ratio, but using (0, 175) as the pivot instead,
// so that an intersection of two "circles" can always be seen.
canvas.scale(1f, scaleX / scaleY, 0f, SCALE_FOCUS_Y);
}
}
private void drawPath(Canvas canvas, Path path, float[] hsv) {
mPaint.setColor(Color.HSVToColor(hsv));
canvas.drawPath(path, mPaint);
}
@Override
public void setAlpha(int i) {
// Ignore
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
// Ignore
}
@Override
public int getOpacity() {
return 0;
}
public void setColor(int color) {
mColor = color;
mPaint.setColor(color);
invalidateSelf();
}
public int getColor() {
return mColor;
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.setupwizardlib.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import com.android.setupwizardlib.R;
/**
* A FrameLayout subclass that will responds to onApplyWindowInsets to draw a drawable in the top
* inset area, making a background effect for the navigation bar. To make use of this layout,
* specify the system UI visibility {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} and
* set specify fitsSystemWindows.
*
* <p>This view is a normal FrameLayout if either of those are not set, or if the platform version
* is lower than Lollipop.
*/
public class StatusBarBackgroundLayout extends FrameLayout {
private Drawable mStatusBarBackground;
private Object mLastInsets; // Use generic Object type for compatibility
public StatusBarBackgroundLayout(Context context) {
super(context);
init(context, null, 0);
}
public StatusBarBackgroundLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
@TargetApi(VERSION_CODES.HONEYCOMB)
public StatusBarBackgroundLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SuwStatusBarBackgroundLayout, defStyleAttr, 0);
final Drawable statusBarBackground =
a.getDrawable(R.styleable.SuwStatusBarBackgroundLayout_suwStatusBarBackground);
setStatusBarBackground(statusBarBackground);
a.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
if (mLastInsets != null) {
final int insetTop = ((WindowInsets) mLastInsets).getSystemWindowInsetTop();
if (insetTop > 0) {
mStatusBarBackground.setBounds(0, 0, getWidth(), insetTop);
mStatusBarBackground.draw(canvas);
}
}
}
}
public void setStatusBarBackground(Drawable background) {
mStatusBarBackground = background;
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
setWillNotDraw(background == null);
setFitsSystemWindows(background != null);
invalidate();
}
}
public Drawable getStatusBarBackground() {
return mStatusBarBackground;
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mLastInsets = insets;
return super.onApplyWindowInsets(insets);
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.setupwizardlib.test;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.setupwizardlib.GlifPatternDrawable;
public class GlifPatternDrawableTest extends AndroidTestCase {
@SmallTest
public void testScaleToCanvasSquare() {
final Canvas canvas = new Canvas();
Matrix expected = new Matrix(canvas.getMatrix());
final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED);
drawable.setBounds(0, 0, 683, 384); // half each side of the view box
drawable.scaleCanvasToBounds(canvas);
expected.postScale(0.5f, 0.5f);
assertEquals("Matrices should match", expected, canvas.getMatrix());
}
@SmallTest
public void testScaleToCanvasTall() {
final Canvas canvas = new Canvas();
final Matrix expected = new Matrix(canvas.getMatrix());
final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED);
drawable.setBounds(0, 0, 683, 768); // half the width only
drawable.scaleCanvasToBounds(canvas);
expected.postScale(1f, 1f);
expected.postTranslate(-100f, 0f);
assertEquals("Matrices should match", expected, canvas.getMatrix());
}
@SmallTest
public void testScaleToCanvasWide() {
final Canvas canvas = new Canvas();
final Matrix expected = new Matrix(canvas.getMatrix());
final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED);
drawable.setBounds(0, 0, 1366, 384); // half the height only
drawable.scaleCanvasToBounds(canvas);
expected.postScale(1f, 1f);
expected.postTranslate(0f, -87.5f);
assertEquals("Matrices should match", expected, canvas.getMatrix());
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.setupwizardlib.test;
import android.graphics.drawable.ShapeDrawable;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.setupwizardlib.view.StatusBarBackgroundLayout;
public class StatusBarBackgroundLayoutTest extends AndroidTestCase {
@SmallTest
public void testSetStatusBarBackground() {
final StatusBarBackgroundLayout layout = new StatusBarBackgroundLayout(getContext());
final ShapeDrawable drawable = new ShapeDrawable();
layout.setStatusBarBackground(drawable);
assertSame("Status bar background drawable should be same as set",
drawable, layout.getStatusBarBackground());
}
}