Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Opportunities in developing for Android Wear

Opportunities in developing for Android Wear

Wearable devices are the new frontier of personal computing and Android Wear is the easiest way for Android developers to take advantage of this new way of interacting with users and enhance how they experience your app.

In this talk we’ll explore the different ways to extend your app into Android Wear, starting by optimizing your current notification to display correctly on a smartwatch and then examining some case studies of successful apps that extended their notifications to use things like pictures, barcodes or media controls on Android Wear. We’ll also learn how to create an app that actually runs on the watch and the limitations and constraints of this new environment. Finally we’ll take a quick look at creating your own Android Wear watch face through the Watch Face API.

898e54ca0e526384e3d0a4177edbb480?s=128

Juan Gomez

July 15, 2015
Tweet

Transcript

  1. Opportunities in developing for Android Wear Juan Gomez

  2. Intro Who am I? • Sr. Android Engineer at Netflix

    • Previously at Eventbrite and OneLouder Apps • Android & Python Developer
  3. None
  4. None
  5. None
  6. Design Principles

  7. • Launched automatically Design Principles

  8. • Launched automatically • Glanceable Design Principles

  9. • Launched automatically • Glanceable • Suggest and Demand Design

    Principles
  10. • Launched automatically • Glanceable • Suggest and Demand •

    Zero or low interaction Design Principles
  11. Developing for Android Wear

  12. Notifications Applications Watch Faces

  13. Notifications

  14. Simple Notifications Look, ma - no work required!

  15. Intent viewIntent = new Intent(context, DummyActivity.class);
 
 PendingIntent viewPendingIntent =

    PendingIntent.getActivity(context, 0, viewIntent, 0);
 Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.ic_launcher)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentText("Please proceed to gate C 17 to board. Have a nice flight!")
 .setContentIntent(viewPendingIntent)
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() Simple Notifications
  16. Can we do better?

  17. BigPictureStyle Enhanced Notifications

  18. Intent viewIntent = new Intent(context, DummyActivity.class);
 
 PendingIntent viewPendingIntent =

    PendingIntent.getActivity(context, 0, viewIntent, 0);
 Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.ic_launcher)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentText("Please proceed to gate C 17 to board. Have a nice flight!")
 .setStyle(
 new NotificationCompat.BigPictureStyle()
 .bigPicture(BitmapFactory.decodeResource(context.getResources(), R.drawable.sanfrancisco))
 .setBigContentTitle("Flight AW123 is ready to board.")
 .setSummaryText("Please proceed to gate C 17 to board. Have a nice flight!"))
 .setContentIntent(viewPendingIntent)
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() BigPictureStyle
  19. Intent viewIntent = new Intent(context, DummyActivity.class);
 
 PendingIntent viewPendingIntent =

    PendingIntent.getActivity(context, 0, viewIntent, 0);
 Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.ic_launcher)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentText("Please proceed to gate C 17 to board. Have a nice flight!")
 .setStyle(
 new NotificationCompat.BigPictureStyle()
 .bigPicture(BitmapFactory.decodeResource(context.getResources(), R.drawable.sanfrancisco))
 .setBigContentTitle("Flight AW123 is ready to board.")
 .setSummaryText("Please proceed to gate C 17 to board. Have a nice flight!"))
 .setContentIntent(viewPendingIntent)
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() BigPictureStyle Create BigPictureStyle
  20. Pages Enhanced Notifications

  21. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 .setContentTitle("Your seat")
 .setContentText("17A")


    .extend(new NotificationCompat.WearableExtender()
 .setBackground(BitmapFactory.decodeResource(context.getResources(), R.drawable.a380_seat)))
 .build()); sendNotification() Pages
  22. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 .setContentTitle("Your seat")
 .setContentText("17A")


    .extend(new NotificationCompat.WearableExtender()
 .setBackground(BitmapFactory.decodeResource(context.getResources(), R.drawable.a380_seat)))
 .build()); sendNotification() Pages Create page with title
  23. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 .setContentTitle("Your seat")
 .setContentText("17A")


    .extend(new NotificationCompat.WearableExtender()
 .setBackground(BitmapFactory.decodeResource(context.getResources(), R.drawable.a380_seat)))
 .build()); sendNotification() Pages Set background image
  24. pages.add(new NotificationCompat.Builder(context)
 .extend(new NotificationCompat.WearableExtender()
 .setHintShowBackgroundOnly(true) .setHintAvoidBackgroundClipping(true)
 .setHintScreenTimeout(NotificationCompat.WearableExtender.SCREEN_TIMEOUT_LONG)
 .setBackground( BitmapFactory.decodeResource( context.getResources(),

    R.drawable.qrcode)))
 .build()); sendNotification() Background Only Pages
  25. pages.add(new NotificationCompat.Builder(context)
 .extend(new NotificationCompat.WearableExtender()
 .setHintShowBackgroundOnly(true) .setHintAvoidBackgroundClipping(true)
 .setHintScreenTimeout(NotificationCompat.WearableExtender.SCREEN_TIMEOUT_LONG)
 .setBackground( BitmapFactory.decodeResource( context.getResources(),

    R.drawable.qrcode)))
 .build()); sendNotification() Background Only Pages Show background only Don’t clip on round displays Extended timeout
  26. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 // ... (set

    properties)
 .build()); Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentIntent(viewPendingIntent)
 .extend(new NotificationCompat.WearableExtender()
 .addPages(pages))
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() Adding Pages to Notifications
  27. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 // ... (set

    properties)
 .build()); Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentIntent(viewPendingIntent)
 .extend(new NotificationCompat.WearableExtender()
 .addPages(pages))
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() Adding Pages to Notifications Build pages
  28. ArrayList<Notification> pages = new ArrayList<Notification>();
 pages.add(new NotificationCompat.Builder(context)
 // ... (set

    properties)
 .build()); Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentIntent(viewPendingIntent)
 .extend(new NotificationCompat.WearableExtender()
 .addPages(pages))
 .build();
 
 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
 notificationManager.notify(notificationId++, notification);
 sendNotification() Adding Pages to Notifications Add pages to notification
  29. Actions Enhanced Notifications

  30. Intent mapIntent = new Intent(Intent.ACTION_VIEW);
 Uri geoUri = Uri.parse("geo:0,0?q=" +

    Uri.encode("London Heathrow"));
 mapIntent.setData(geoUri);
 PendingIntent mapPendingIntent = PendingIntent.getActivity(context, 0, mapIntent, 0); sendNotification() Actions
  31. Intent mapIntent = new Intent(Intent.ACTION_VIEW);
 Uri geoUri = Uri.parse("geo:0,0?q=" +

    Uri.encode("London Heathrow"));
 mapIntent.setData(geoUri);
 PendingIntent mapPendingIntent = PendingIntent.getActivity(context, 0, mapIntent, 0);
 
 NotificationCompat.Action walkingDirectionsAction =
 new NotificationCompat.Action.Builder( R.drawable.ic_full_directions_walking, "Directions to gate", mapPendingIntent)
 .build(); sendNotification() Actions
  32. Intent mapIntent = new Intent(Intent.ACTION_VIEW);
 Uri geoUri = Uri.parse("geo:0,0?q=" +

    Uri.encode("London Heathrow"));
 mapIntent.setData(geoUri);
 PendingIntent mapPendingIntent = PendingIntent.getActivity(context, 0, mapIntent, 0);
 
 NotificationCompat.Action walkingDirectionsAction =
 new NotificationCompat.Action.Builder( R.drawable.ic_full_directions_walking, "Directions to gate", mapPendingIntent)
 .build(); Notification notification = new NotificationCompat.Builder(context)
 .setSmallIcon(R.drawable.ic_launcher)
 .setSmallIcon(R.drawable.plane)
 .setContentTitle(String.format("Flight AW123 is ready to board", notificationId))
 .setContentText("Please proceed to gate C 17 to board. Have a nice flight!")
 .addAction(walkingDirectionsAction)
 .extend(new NotificationCompat.WearableExtender()
 .addPages(pages)
 .addAction(replyAction)
 .addAction(walkingDirectionsAction))
 .build();
 sendNotification() Actions
  33. None
  34. Applications

  35. Launching Wearable apps Using app-provided voice actions Using the start

    menu
  36. <application
 android:icon="@drawable/greenlinelogo"
 android:label="@string/app_name"
 android:theme="@android:style/Theme.DeviceDefault" >
 <activity
 android:name="de.peterfriese.weartravel.MainActivity"
 android:label="@string/app_name_voice" >
 <intent-filter>


    <action android:name="android.intent.action.MAIN" />
 
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 </application>
 AndroidManifest.xml Launching
  37. Custom Layouts Wearable Apps

  38. <android.support.wearable.view.BoxInsetLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:layout_height="match_parent"
 android:layout_width="match_parent">
 
 <FrameLayout
 android:id="@+id/frame_layout"
 android:layout_height="match_parent"
 android:layout_width="match_parent"


    app:layout_box="left|bottom|right">
 
 <android.support.wearable.view.WearableListView
 android:id="@+id/checkin_list"
 android:layout_height="match_parent"
 android:layout_width="match_parent">
 </android.support.wearable.view.WearableListView>
 </FrameLayout>
 </android.support.wearable.view.BoxInsetLayout> activity_checkin.xml Layout - List
  39. <android.support.wearable.view.BoxInsetLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:layout_height="match_parent"
 android:layout_width="match_parent">
 
 <FrameLayout
 android:id="@+id/frame_layout"
 android:layout_height="match_parent"
 android:layout_width="match_parent"


    app:layout_box="left|bottom|right">
 
 <android.support.wearable.view.WearableListView
 android:id="@+id/checkin_list"
 android:layout_height="match_parent"
 android:layout_width="match_parent">
 </android.support.wearable.view.WearableListView>
 </FrameLayout>
 </android.support.wearable.view.BoxInsetLayout> activity_checkin.xml Layout - List
  40. <?xml version="1.0" encoding="utf-8"?>
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 
 <android.support.wearable.view.CircledImageView
 android:id="@+id/image"
 android:alpha="0.5"


    android:layout_height="52dp"
 android:layout_marginLeft="16dp"
 android:layout_width="52dp"
 app:circle_border_color="#FFFFFFFF"
 app:circle_border_width="2dp"
 app:circle_color="#00000000"
 />
 
 <TextView
 android:id="@+id/text"
 android:alpha="0.5"
 android:fontFamily="sans-serif-condensed-light"
 android:gravity="center_vertical"
 android:layout_height="52dp"
 android:layout_marginLeft="72dp"
 checkin_listview_item.xml Layout - Item
  41. <?xml version="1.0" encoding="utf-8"?>
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 
 <android.support.wearable.view.CircledImageView
 android:id="@+id/image"
 android:alpha="0.5"


    android:layout_height="52dp"
 android:layout_marginLeft="16dp"
 android:layout_width="52dp"
 app:circle_border_color="#FFFFFFFF"
 app:circle_border_width="2dp"
 app:circle_color="#00000000"
 />
 
 <TextView
 android:id="@+id/text"
 android:alpha="0.5"
 android:fontFamily="sans-serif-condensed-light"
 android:gravity="center_vertical"
 android:layout_height="52dp"
 android:layout_marginLeft="72dp"
 android:layout_marginRight="16dp"
 android:layout_width="wrap_content"
 android:textColor="@color/white"
 android:textSize="14sp"
 />
 </merge> Layout - Item checkin_listview_item.xml
  42. private final class MyItemView extends FrameLayout implements WearableListView.OnCenterProximityListener {
 


    final CircledImageView image;
 final TextView text;
 
 public MyItemView(Context context) {
 super(context);
 View.inflate(context, R.layout.checkin_listview_item, this);
 image = (CircledImageView) findViewById(R.id.image);
 text = (TextView) findViewById(R.id.text);
 }
 
 @Override
 public void onCenterPosition(boolean b) {
 image.animate().scaleX(1f).scaleY(1f).alpha(1);
 text.animate().scaleX(1f).scaleY(1f).alpha(1);
 }
 
 @Override
 public void onNonCenterPosition(boolean b) {
 image.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
 CheckInActivity.java MyViewItem
  43. private final class MyItemView extends FrameLayout implements WearableListView.OnCenterProximityListener {
 


    final CircledImageView image;
 final TextView text;
 
 public MyItemView(Context context) {
 super(context);
 View.inflate(context, R.layout.checkin_listview_item, this);
 image = (CircledImageView) findViewById(R.id.image);
 text = (TextView) findViewById(R.id.text);
 }
 
 @Override
 public void onCenterPosition(boolean b) {
 image.animate().scaleX(1f).scaleY(1f).alpha(1);
 text.animate().scaleX(1f).scaleY(1f).alpha(1);
 }
 
 @Override
 public void onNonCenterPosition(boolean b) {
 image.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
 CheckInActivity.java MyViewItem
  44. private final class MyItemView extends FrameLayout implements WearableListView.OnCenterProximityListener {
 


    final CircledImageView image;
 final TextView text;
 
 public MyItemView(Context context) {
 super(context);
 View.inflate(context, R.layout.checkin_listview_item, this);
 image = (CircledImageView) findViewById(R.id.image);
 text = (TextView) findViewById(R.id.text);
 }
 
 @Override
 public void onCenterPosition(boolean b) {
 image.animate().scaleX(1f).scaleY(1f).alpha(1);
 text.animate().scaleX(1f).scaleY(1f).alpha(1);
 }
 
 @Override
 public void onNonCenterPosition(boolean b) {
 image.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
 text.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
 }
 } CheckInActivity.java MyViewItem
  45. public class CheckInActivity extends Activity implements WearableListView.ClickListener {
 
 @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_checkin);
 
 MyListAdapter adapter = new MyListAdapter();
 
 WearableListView listView = (WearableListView) findViewById(R.id.checkin_list);
 listView.setAdapter(adapter);
 listView.setClickListener(CheckInActivity.this);
 }
 
 @Override
 public void onClick(WearableListView.ViewHolder viewHolder) {
 Toast.makeText(this, String.format("You selected item #%s", viewHolder.getPosition()), Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void onTopEmptyRegionClick() {
 CheckInActivity.java Activity
  46. public class CheckInActivity extends Activity implements WearableListView.ClickListener {
 
 @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_checkin);
 
 MyListAdapter adapter = new MyListAdapter();
 
 WearableListView listView = (WearableListView) findViewById(R.id.checkin_list);
 listView.setAdapter(adapter);
 listView.setClickListener(CheckInActivity.this);
 }
 
 @Override
 public void onClick(WearableListView.ViewHolder viewHolder) {
 Toast.makeText(this, String.format("You selected item #%s", viewHolder.getPosition()), Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void onTopEmptyRegionClick() {
 Toast.makeText(this, "You tapped into the empty area above the list", Toast.LENGTH_SHORT).show();
 }
 CheckInActivity.java Activity
  47. Watch Faces

  48. None
  49. Design Principles

  50. • Square vs round Design Principles

  51. • Square vs round • Ambient mode / low-bit Design

    Principles
  52. • Square vs round • Ambient mode / low-bit •

    Legibility Design Principles 56° - Cloudy 56° - Cloudy
  53. Developing Watch Faces

  54. • Start using a sample Developing Watch Faces

  55. • Start using a sample • Add a new wearable

    module Developing Watch Faces
  56. Architecture Wearable App Mobile App CanvasWatchFaceService ConfigActivity Engine ConfigActivity WearableListenerService

  57. Architecture Wearable App CanvasWatchFaceService ConfigActivity Engine WearableListenerService

  58. Engine onCreate onPropertiesChanged onDraw onTimeTick onAmbientModeChanged onVisibilityChanged

  59. @Override
 public void onCreate(SurfaceHolder holder) {
 super.onCreate(holder);
 
 setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)


    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
 .setShowSystemUiTime(false)
 .build());
 
 Resources resources = AnalogWatchFaceService.this.getResources();
 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
 
 mHourPaint = new Paint();
 mHourPaint.setARGB(255, 200, 200, 200);
 mHourPaint.setStrokeWidth(5.f);
 mHourPaint.setAntiAlias(true);
 mHourPaint.setStrokeCap(Paint.Cap.ROUND); onCreate Lifecycle - initialise watch face elements
  60. @Override
 public void onCreate(SurfaceHolder holder) {
 super.onCreate(holder);
 
 setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)


    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
 .setShowSystemUiTime(false)
 .build());
 
 Resources resources = AnalogWatchFaceService.this.getResources();
 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
 
 mHourPaint = new Paint();
 mHourPaint.setARGB(255, 200, 200, 200);
 mHourPaint.setStrokeWidth(5.f);
 mHourPaint.setAntiAlias(true);
 mHourPaint.setStrokeCap(Paint.Cap.ROUND); onCreate Lifecycle - initialise watch face elements Single-line peek card Do not show system time Show background briefly for interruptive cards
  61. @Override
 public void onCreate(SurfaceHolder holder) {
 super.onCreate(holder);
 
 setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)


    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
 .setShowSystemUiTime(false)
 .build());
 
 Resources resources = AnalogWatchFaceService.this.getResources();
 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
 
 mHourPaint = new Paint();
 mHourPaint.setARGB(255, 200, 200, 200);
 mHourPaint.setStrokeWidth(5.f);
 mHourPaint.setAntiAlias(true);
 mHourPaint.setStrokeCap(Paint.Cap.ROUND); onCreate Lifecycle - initialise watch face elements Load background image
  62. @Override
 public void onCreate(SurfaceHolder holder) {
 super.onCreate(holder);
 
 setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)


    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
 .setShowSystemUiTime(false)
 .build());
 
 Resources resources = AnalogWatchFaceService.this.getResources();
 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
 
 mHourPaint = new Paint();
 mHourPaint.setARGB(255, 200, 200, 200);
 mHourPaint.setStrokeWidth(5.f);
 mHourPaint.setAntiAlias(true);
 mHourPaint.setStrokeCap(Paint.Cap.ROUND); onCreate Lifecycle - initialise watch face elements Create styles for graphic objects
  63. @Override
 public void onDraw(Canvas canvas, Rect bounds) {
 mTime.setToNow();
 


    canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); float centerX = width / 2f;
 float centerY = height / 2f;
 
 float innerTickRadius = centerX - 10;
 float outerTickRadius = centerX;
 for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
 float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
 float innerX = (float) Math.sin(tickRot) * innerTickRadius;
 float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
 float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 onDraw Lifecycle - draw the watch face
  64. @Override
 public void onDraw(Canvas canvas, Rect bounds) {
 mTime.setToNow();
 


    canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); float centerX = width / 2f;
 float centerY = height / 2f;
 
 float innerTickRadius = centerX - 10;
 float outerTickRadius = centerX;
 for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
 float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
 float innerX = (float) Math.sin(tickRot) * innerTickRadius;
 float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
 float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 onDraw Lifecycle - draw the watch face Update time Draw background image
  65. @Override
 public void onDraw(Canvas canvas, Rect bounds) {
 mTime.setToNow();
 


    canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); float centerX = width / 2f;
 float centerY = height / 2f;
 
 float innerTickRadius = centerX - 10;
 float outerTickRadius = centerX;
 for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
 float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
 float innerX = (float) Math.sin(tickRot) * innerTickRadius;
 float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
 float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 onDraw Lifecycle - draw the watch face Draw the ticks
  66. float centerX = width / 2f;
 float centerY = height

    / 2f;
 
 float innerTickRadius = centerX - 10;
 float outerTickRadius = centerX;
 for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
 float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
 float innerX = (float) Math.sin(tickRot) * innerTickRadius;
 float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
 float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 int minutes = mTime.minute;
 float minRot = minutes / 30f * (float) Math.PI;
 float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
 
 float secLength = centerX - 20;
 float minLength = centerX - 40;
 float hrLength = centerX - 80;
 if (!isInAmbientMode()) {
 float secX = (float) Math.sin(secRot) * secLength;
 float secY = (float) -Math.cos(secRot) * secLength;
 canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
 }
 
 onDraw Lifecycle - draw the watch face Determine hands geometry
  67. float outerX = (float) Math.sin(tickRot) * outerTickRadius;
 float outerY =

    (float) -Math.cos(tickRot) * outerTickRadius;
 canvas.drawLine(centerX + innerX, centerY + innerY,
 centerX + outerX, centerY + outerY, mTickPaint);
 }
 
 float secRot = mTime.second / 30f * (float) Math.PI;
 int minutes = mTime.minute;
 float minRot = minutes / 30f * (float) Math.PI;
 float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
 
 float secLength = centerX - 20;
 float minLength = centerX - 40;
 float hrLength = centerX - 80;
 if (!isInAmbientMode()) {
 float secX = (float) Math.sin(secRot) * secLength;
 float secY = (float) -Math.cos(secRot) * secLength;
 canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
 }
 
 float minX = (float) Math.sin(minRot) * minLength;
 float minY = (float) -Math.cos(minRot) * minLength;
 canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint);
 
 float hrX = (float) Math.sin(hrRot) * hrLength;
 float hrY = (float) -Math.cos(hrRot) * hrLength;
 canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint);
 } onDraw Lifecycle - draw the watch face Don’t draw seconds when in ambient mode Draw minutes hand Draw hours hand
  68. None
  69. Summary • Extending your app’s notifications is the easiest way

    to offer an Android Wear experience. • Watch apps and Watch faces are a great way to offer new functionality to you users. • Make sure you follow the Android Wear design principles to maintain platform consistency.
  70. Useful links • http://developer.android.com/training/building-wearables.html • https://www.youtube.com/playlist?list=PLWz5rJ2EKKc- kIrPiq098QH9dOle-fLef • http://developer.android.com/design/wear/index.html

  71. Thank You! Twitter: @_juandg Email: jgomez@netflix.com Lanyrd: lanyrd.com/profile/juandg/