74
Android Wear Essen-als Improve your Android skills, building watch faces

Android Wear Essentials

  • Upload
    nilhcem

  • View
    104

  • Download
    1

Embed Size (px)

Citation preview

Android Wear Essen-als Improve your Android skills, building watch faces

Android Wear

• Android SDK

• Specific wear APIs in external libraries

(support:wearable, play-services-wearable)

• All devices are compa>ble with API 23

(minSdkVersion 23)

• Only a few devices are compa>ble with API 24

("Android Wear 2.0")

Crea%ng a wear apph"ps://developer.android.com/training/building-wearables.html

Crea%ng a wear app

• Ac$vity

• AndroidManifest.xml

• LayoutInflater

• You know the stuff...

Crea%ng a watch faceh"ps://developer.android.com/training/wearables/watch-faces/index.html

Crea%ng a watch face

1. Create a class that extends CanvasWatchFaceService.Engine

2. Create a Service that extends CanvasWatchFaceService and override onCreateEngine

Prefer OpenGL ES instead of the Canvas API ?Use Gles2WatchFaceService instead

Create a watch face project

Ba#ery usage

AMOLED

Interac(ve / Ambient / Low-bit Ambient

Low-bit Ambient / Ambient

Burn-in effect

Burn-in effect

h"ps://en.wikipedia.org/wiki/Screen_burn-in

Burn-in effect

Burn-in effect

Avoid burn-in

• Do not draw large areas of pixels in ambient mode

• Do not place content within 10 pixels of the edge of the screen

I'm bored, can I see some code?

class Engine extends CanvasWatchFaceService.Engine { Paint paint;

@Override void onCreate(SurfaceHolder holder) { paint = new Paint(); paint.setTextSize(80_DIP); paint.setColor(Color.MAGENTA); paint.setStyle(Style.FILL); paint.setTextAlign(Align.CENTER); paint.setAntiAlias(true); }

@Override void onDraw(Canvas canvas, Rect bounds) { canvas.drawText("18:42", bounds.centerX(), bounds.centerY(), paint); }}

Style.STROKE

if (inAmbientMode) { paint.setColor(WHITE); paint.setStyle(STROKE); paint.setStrokeWidth(1_DIP);

if (lowBitEnabled) { paint.setAntiAlias(false); }}

Lifecycle

Enough theory

10mn to create a watch face

#1: Draw the background

Paint backgroundPaint = new Paint();

@Override void onCreate(SurfaceHolder holder) { super.onCreate(holder);

backgroundPaint.setStyle(FILL); backgroundPaint.setColor(DKGRAY); backgroundPaint.setAntiAlias(true);}

@Override void onDraw(Canvas canvas, Rect bounds) { canvas.drawRect(0, 0, bounds.width(), bounds.height(), backgroundPaint);}

Background

Gradient: Linear / Sweep / Radial

LinearGradient(0, centerY, width, centerY, WHITE, BLACK, CLAMP));SweepGradient(radius, radius, WHITE, BLACK));RadialGradient(centerX, centerY, radius, WHITE, BLACK, CLAMP));

paint.setShader(shader);

Tile mode: Clamp / Mirror / Repeat

tileMode = Shader.TileMode.CLAMP;tileMode = Shader.TileMode.MIRROR;tileMode = Shader.TileMode.REPEAT;

new RadialGradient(radius, radius, radius / 10, WHITE, BLACK, tileMode));

Gradient posi-ons

int[] colors = new int[] { RED, GREEN, BLUE, MAGENTA, CYAN};float[] positions = new float[] { 0f, 0.1f, 0.4f, 0.8f, 1.0f};paint.setShader(new LinearGradient(0, radius, width, radius, colors, positions, CLAMP));

Gradient posi-ons

Shader shader = new LinearGradient( 0, centerY, width, centerY, new int[] {blue, blue, white, white, red, red}, new float[] {0f, 0.33f, 0.33f, 0.66f, 0.66f, 1f}, CLAMP);bgPaint.setShader(shader);

Back to our background

int[] colors = new int[] {DKGRAY, DKGRAY, BLACK, BLACK};float[] positions = new float[] {0, 0.25f, 0.25f, 1f};bgPaint.setShader(new RadialGradient(centerX, centerY, 6_DIP, colors, positions, REPEAT));

#2 Place the minutes indicators

Path path;

@Override void onApplyWindowInsets(WindowInsets insets) { [...] path = createMinutesIndicators(centerX, centerY, radius - 10_DP);}

@Override void onDraw(Canvas canvas, Rect bounds) { [...] canvas.drawPath(path, paint);}

Minutes indicators

Add shadowmnPaint.setShadowLayer(4f, 2f, 2f, Color.GRAY);

#3 Create the watch hands

Paint handHourPaint;Path handHourPath;

@Override void onCreate(SurfaceHolder holder) { [...] handHourPaint = new Paint(); handHourPaint.setStyle(Paint.Style.FILL); handHourPaint.setColor(Color.WHITE); handHourPaint.setAntiAlias(true); handHourPaint.setPathEffect(new CornerPathEffect(2_DP));}

@Override void onApplyWindowInsets(WindowInsets insets) { [...] handHourPath = createHandHour(centerX, centerY, radius - 20_DP);}

@Override void onDraw(Canvas canvas, Rect bounds) { [...] canvas.drawPath(handHourPath, handHourPaint);}

Hour hand

path.moveTo( centerX - 16_DIP, centerY);

path.lineTo( centerX - 10_DIP, centerY);

path.arcTo( new RectF( centerX - 10_DIP, centerY - 10_DIP, centerX + 10_DIP, centerY + 10_DIP ), 180f, -180f);

path.lineTo( centerX + 16_DIP, centerY);

path.quadTo( centerX, centerY - 20_DIP, centerX + 1_DIP, centerY - needleHeight);

path.quadTo( centerX, centerY - 20_DIP, centerX - 16_DIP, centerY);

int[] colors = new int[] { 0xff878191, 0xffaba6b3, 0xffb9b1c5, 0xffa9a2b3};

float[] positions = new float[] { 0, 0.49f, 0.51f, 1f},

Shader gradient = new LinearGradient( radius - 10_DIP, 0, radius + 10_DIP, 0, colors, positions, Shader.TileMode.CLAMP);

handHourPaint.setShader(gradient);

canvas.save();

canvas.rotate( 10 * 360 / 12, centerX, centerY);

canvas.drawPath(path, paint);

canvas.restore();

Paint shadowPaint = new Paint();shadowPaint.setAntiAlias(true);shadowPaint.setColor(GRAY);shadowPaint.setShadowLayer(4f, 4f, 2f, GRAY);[...]canvas.drawPath(handHourPath, shadowPaint);canvas.drawPath(handHourPath, watchHandPaint);

Add the minute hand

Add the second hand

#4 Create the ambient mode

#5 No step 5

Tips & Tricks

#1: Official documenta2on is !

developer.android.com/wear/index.html

#2: Share code with a common module

#3: Custom WatchFrame Layouth"ps://github.com/Nilhcem/hexawatch/blob/master/

companion/src/main/java/com/nilhcem/hexawatch/ui/widget/

WearFrameLayout.java

<com.nilhcem.hexawatch.ui.widget.WearFrameLayout android:layout_width="wrap_content" android:layout_height="match_parent">

<com.nilhcem.hexawatch.ui.widget.HexawatchView android:layout_width="match_parent" android:layout_height="match_parent"/></com.nilhcem.hexawatch.ui.widget.WearFrameLayout>

#4: Consider using Protobuf over Json

#5: Want to cut bitmaps? Use Xfermode

#6: Check out ustwo clockwise SDK

h"ps://github.com/ustwo/clockwise

#7: Stripes shaderaka "A burn-in friendly way to fill a large

surface"

paint.setStyle(Paint.Style.FILL);paint.setShader(new LinearGradient( 0f, 0f, TWO_DIP, TWO_DIP, new int[] { WHITE, WHITE, TRANSPARENT, TRANSPARENT }, new float[] { 0, 0.25f, 0.25f, 1f }, Shader.TileMode.REPEAT ));

#8: Bitmap shadergithub.com/Nilhcem/shammane-

androidwear

Bitmap dotPattern = BitmapFactory.decodeResource( context.getResources(), R.drawable.dot_pattern);

paint.setShader( new BitmapShader( dotPattern, TileMode.REPEAT, TileMode.REPEAT ));

#9: Experiment in an Android (not wear)

custom View

<com.nilhcem.experiments.ui.widget.WearFrameLayout android:layout_width="match_parent" android:layout_height="match_parent">

<com.nilhcem.experiments.ui.ExperimentalView android:layout_width="match_parent" android:layout_height="match_parent"/></com.nilhcem.experiments.ui.widget.WearFrameLayout>

#10: Use ValueAnimator for

onDraw anima7ons

private ValueAnimator animator;private final Handler handler = new Handler();

public void onTapCommand(int tapType, int x, int y, long e) { if (tapType == TAP_TYPE_TAP) { animator = ValueAnimator.ofInt(0, Math.round(MAX_RADIUS)); animator.setDuration(600L); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.start(); invalidate(); }}

public void onDraw(Canvas canvas, Rect bounds) { if (animator != null && animator.isRunning()) { int value = (Integer) animator.getAnimatedValue(); canvas.drawCircle(centerX, centerY, value, paint); // Invalidate at a 30fps ratio handler.postDelayed(() -> invalidate()), 1000L / 30); }}

#11: Path Interpolator Anima3on

Path path = new Path(); path.moveTo(0, 0); path.lineTo(0.250f, 0.250f); path.lineTo(0.500f, -0.500f); path.lineTo(0.750f, 0.625f); path.lineTo(0.875f, 0.500f); path.lineTo(1f, 1f);

ObjectAnimator animator = ObjectAnimator.ofFloat(bugdroid, View.TRANSLATION_X, 0, 100); animator.setRepeatCount(ObjectAnimator.INFINITE); animator.setRepeatMode(ObjectAnimator.REVERSE); animator.setInterpolator(PathInterpolatorCompat.create(path)); animator.setDuration(2000); animator.start();

#11: Path Interpolator Anima3on

#12: Move a view along a Path

h"p://stackoverflow.com/ques5ons/6154370/android-move-object-along-

a-path

Path path = new Path(); path.arcTo(new RectF(0, 0, 300, 300), 0, 180); // 1 -> 2 path.quadTo(200, 80, 400, 400); // 2 -> 3 path.lineTo(500f, 300f); // 3 -> 4 path.close(); // 4 -> 1

ObjectAnimator animator = ObjectAnimator.ofFloat(bugdroid, "x", "y", path); animator.setRepeatCount(ObjectAnimator.INFINITE); animator.setInterpolator(new DecelerateInterpolator()); animator.setDuration(7000); animator.start();

#13: Port your app to Tizen

• HTML5 Canvas api

• Low-Bit Ambient mode

• Burn-in support

• onTimeTick() becomes window.addEventListener("timetick", drawWatchContent);

Hexawatch

github.com/Nilhcem/hexawatch

• Square / Circular shapes

• Se1ngs app

• Protobuf

• Gear s2 port

• Custom views

• Custom wear frame layout

• Common module

Conclusion

Conclusion• Good to be curious

• Improve your skills

• Fun

• Rewarding

Android Wear Essen-als

• Twi%er: @Nilhcem

• Slides: slideshare.net/Nilhcem/android-wear-essen:als

• Hexawatch: github.com/Nilhcem/hexawatch

• 10mn-watchface: github.com/Nilhcem/the-10mn-watchface