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.

Juan Gomez

July 15, 2015
Tweet

More Decks by Juan Gomez

Other Decks in Programming

Transcript

  1. Opportunities in developing for
    Android Wear
    Juan Gomez

    View Slide

  2. Intro
    Who am I?
    • Sr. Android Engineer at Netflix
    • Previously at Eventbrite and OneLouder Apps
    • Android & Python Developer

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. Design Principles

    View Slide

  7. • Launched automatically
    Design Principles

    View Slide

  8. • Launched automatically
    • Glanceable
    Design Principles

    View Slide

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

    View Slide

  10. • Launched automatically
    • Glanceable
    • Suggest and Demand
    • Zero or low interaction
    Design Principles

    View Slide

  11. Developing for
    Android Wear

    View Slide

  12. Notifications Applications
    Watch Faces

    View Slide

  13. Notifications

    View Slide

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

    View Slide

  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

    View Slide

  16. Can we do better?

    View Slide

  17. BigPictureStyle
    Enhanced Notifications

    View Slide

  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

    View Slide

  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

    View Slide

  20. Pages
    Enhanced Notifications

    View Slide

  21. ArrayList pages = new ArrayList();

    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

    View Slide

  22. ArrayList pages = new ArrayList();

    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

    View Slide

  23. ArrayList pages = new ArrayList();

    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

    View Slide

  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

    View Slide

  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

    View Slide

  26. ArrayList pages = new ArrayList();

    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

    View Slide

  27. ArrayList pages = new ArrayList();

    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

    View Slide

  28. ArrayList pages = new ArrayList();

    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

    View Slide

  29. Actions
    Enhanced Notifications

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  33. View Slide

  34. Applications

    View Slide

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

    View Slide

  36. android:icon="@drawable/greenlinelogo"

    android:label="@string/app_name"

    android:theme="@android:style/Theme.DeviceDefault" >

    android:name="de.peterfriese.weartravel.MainActivity"

    android:label="@string/app_name_voice" >








    AndroidManifest.xml
    Launching

    View Slide

  37. Custom Layouts
    Wearable Apps

    View Slide

  38. 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">


    android:id="@+id/frame_layout"

    android:layout_height="match_parent"

    android:layout_width="match_parent"

    app:layout_box="left|bottom|right">


    android:id="@+id/checkin_list"

    android:layout_height="match_parent"

    android:layout_width="match_parent">




    activity_checkin.xml
    Layout - List

    View Slide

  39. 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">


    android:id="@+id/frame_layout"

    android:layout_height="match_parent"

    android:layout_width="match_parent"

    app:layout_box="left|bottom|right">


    android:id="@+id/checkin_list"

    android:layout_height="match_parent"

    android:layout_width="match_parent">




    activity_checkin.xml
    Layout - List

    View Slide


  40. xmlns:app="http://schemas.android.com/apk/res-auto">


    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"

    />


    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

    View Slide


  41. xmlns:app="http://schemas.android.com/apk/res-auto">


    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"

    />


    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"

    />


    Layout - Item
    checkin_listview_item.xml

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  47. Watch Faces

    View Slide

  48. View Slide

  49. Design Principles

    View Slide

  50. • Square vs round
    Design Principles

    View Slide

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

    View Slide

  52. • Square vs round
    • Ambient mode / low-bit
    • Legibility
    Design Principles
    56° - Cloudy
    56° - Cloudy

    View Slide

  53. Developing
    Watch Faces

    View Slide

  54. • Start using a sample
    Developing
    Watch Faces

    View Slide

  55. • Start using a sample
    • Add a new wearable module
    Developing
    Watch Faces

    View Slide

  56. Architecture
    Wearable App Mobile App
    CanvasWatchFaceService
    ConfigActivity
    Engine
    ConfigActivity
    WearableListenerService

    View Slide

  57. Architecture
    Wearable App
    CanvasWatchFaceService
    ConfigActivity
    Engine
    WearableListenerService

    View Slide

  58. Engine
    onCreate
    onPropertiesChanged
    onDraw
    onTimeTick
    onAmbientModeChanged
    onVisibilityChanged

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  68. View Slide

  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.

    View Slide

  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

    View Slide

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

    View Slide