slightly advanced android wear ;)

62
Slightly Advanced Android Wear ;-) +AlfredoMorresi @rainbowbreeze

Upload: alfredo-morresi

Post on 29-Nov-2014

280 views

Category:

Software


0 download

DESCRIPTION

Android Wear advanced dev topics

TRANSCRIPT

Page 1: Slightly Advanced Android Wear ;)

Slightly Advanced Android Wear ;-)

+AlfredoMorresi@rainbowbreeze

Page 2: Slightly Advanced Android Wear ;)

UX basics

Page 3: Slightly Advanced Android Wear ;)

Design for Wear IS NOT design for a small phone

Page 4: Slightly Advanced Android Wear ;)

5 seconds or less per interaction

Page 5: Slightly Advanced Android Wear ;)

Design for big gestures

Page 6: Slightly Advanced Android Wear ;)

Think about stream cards first

Page 7: Slightly Advanced Android Wear ;)

Do one thing, really fast

Design for the corner of the eye

Don’t be a constant shoulder tapper

Page 8: Slightly Advanced Android Wear ;)

Activity Lifecycle

Page 9: Slightly Advanced Android Wear ;)

Voice Capabilities

Page 10: Slightly Advanced Android Wear ;)

<activity android:name="MyNoteActivity">

<intent-filter>

<action android:name="android.intent.action.SEND" />

<category android:name="com.google.android.voicesearch.SELF_NOTE" />

</intent-filter>

</activity>

AndroidManifest.xml

Page 11: Slightly Advanced Android Wear ;)

<application>

<activity

android:name="StartRunActivity"

android:label="MyRunningApp">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

AndroidManifest.xml

Page 12: Slightly Advanced Android Wear ;)

private static final int SPEECH_REQUEST_CODE = 0;

// Create an intent that can start the Speech Recognizer activity

private void displaySpeechRecognizer() {

Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);

intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,

RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);

// Start the activity, the intent will be populated with the speech text

startActivityForResult(intent, SPEECH_REQUEST_CODE);

}

MyActivity.java

Page 13: Slightly Advanced Android Wear ;)

// This callback is invoked when the Speech Recognizer returns.

// This is where you process the intent and extract the speech text from

the intent.

@Override

protected void onActivityResult(int requestCode, int resultCode,

Intent data) {

if (requestCode == SPEECH_REQUEST_CODE && resultCode == RESULT_OK) {

List<String> results = data.getStringArrayListExtra(

RecognizerIntent.EXTRA_RESULTS);

String spokenText = results.get(0);

// Do something with spokenText

}

super.onActivityResult(requestCode, resultCode, data);

}

MyActivity.java

Page 14: Slightly Advanced Android Wear ;)

Notifications background

Page 15: Slightly Advanced Android Wear ;)

Background

400x400640x400 (parallax scrolling)

Page 16: Slightly Advanced Android Wear ;)

res/drawable-nodpi(background)

res/drawable-hdpi(all the other icons)

Page 17: Slightly Advanced Android Wear ;)

// Create a WearableExtender to add functionality

// for wearables

NotificationCompat.WearableExtender wearableExtender =

new NotificationCompat.WearableExtender()

.setHintHideIcon(true)

.setBackground(mBitmap);

MyClass.java

Page 18: Slightly Advanced Android Wear ;)

Data APIs

Page 19: Slightly Advanced Android Wear ;)

WearableListenerService(background)

DataApi.DataListener(for Activity)

Page 20: Slightly Advanced Android Wear ;)

PutDataMapRequest putDataMapRequest =

PutDataMapRequest.create(Constant.LANDING_SITE_RESPONSE);

DataMap dataMap = putDataMapRequest.getDataMap();

dataMap.putInt(Constant.KEY_MISSION_NUMBER, 123);

dataMap.putString(Constant.KEY_LANDING_SITE_NAME, "...");

dataMap.putLong(Constant.KEY_TIMESTAMP, new Date().getTime());

PutDataRequest request = putDataMapRequest.asPutDataRequest();

PendingResult<DataApi.DataItemResult> pendingResult =

Wearable.DataApi.putDataItem(mDataClient, request);

DataApi.DataItemResult result = pendingResult.await();

Avoid DataAPI caching

Page 21: Slightly Advanced Android Wear ;)

Data Sync & Messaging Library for Android Wear

Mariuxtheone / Teleport

Page 22: Slightly Advanced Android Wear ;)

Networking on Wear

Page 23: Slightly Advanced Android Wear ;)

HttpUrlConnect IS NOT available

Doh!

Page 24: Slightly Advanced Android Wear ;)

Transferring bitmaps(handheld side)

Page 25: Slightly Advanced Android Wear ;)

String url = "...";

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

try {

byte[] byteChunk = new byte[1024];

int n;

InputStream in = new java.net.URL(url).openStream();

while ( (n = in.read(byteChunk)) > 0 ) {

outputStream.write(byteChunk, 0, n);

}

// Check for bitmap correctness …

Asset asset = Asset.createFromBytes(outputStream.toByteArray());

}

Page 26: Slightly Advanced Android Wear ;)

PutDataMapRequest dataMap = PutDataMapRequest.create("/image");

dataMap.getDataMap().putAsset("profileImage", asset)

dataMap.getDataMap().putLong("timestamp", new Date().getTime());

PutDataRequest request = dataMap.asPutDataRequest();

PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi

.putDataItem(mDataClient, request);

DataApi.DataItemResult result = pendingResult.await();

// result.getDataItem().getUri());

Page 27: Slightly Advanced Android Wear ;)

private boolean isDataAValidBitmap(byte[] data)

Bitmap checkBitmap = BitmapFactory.decodeByteArray(

data, 0, data.length);

// Checks bitmap for null or sizes with .getWidth()

return null != checkBitmap;

}

Page 28: Slightly Advanced Android Wear ;)

// From Bitmap to asset

private static Asset createAssetFromBitmap(Bitmap bitmap) {

final ByteArrayOutputStream bs =

new ByteArrayOutputStream();

// Resize or apply other transformations...

bitmap.compress(Bitmap.CompressFormat.PNG, 100, bs);

return Asset.createFromBytes(bs.toByteArray());

}

Page 29: Slightly Advanced Android Wear ;)

Bitmap bitmap = BitmapFactory.

decodeResource(

getResources(),

R.drawable.image);

From resources

Page 30: Slightly Advanced Android Wear ;)

String url = "...";

ImageRequest request = new ImageRequest(url,

new Response.Listener() {

@Override

public void onResponse(Bitmap bitmap) {

// Your Android Wear code here

}

}, 0, 0, null,

new Response.ErrorListener() {

public void onErrorResponse(VolleyError error) {

// Manages loading errors

}

});

// Access the RequestQueue through your singleton class.

MySingleton.getInstance(this).addToRequestQueue(request);

Using Volley

Page 31: Slightly Advanced Android Wear ;)

Picasso.with(context)

.load(url)

.resize(50, 50)

.transform(myTransformation)

.into(myTarget)

// Put transferring logic inside MyTarget class

Using Picasso

Page 32: Slightly Advanced Android Wear ;)

Transferring bitmaps(Wear side)

Page 33: Slightly Advanced Android Wear ;)

@Override

public void onDataChanged(DataEventBuffer dataEvents) {

for (DataEvent event : dataEvents) {

if (event.getType() == DataEvent.TYPE_CHANGED &&

event.getDataItem().getUri().getPath().equals("/image")) {

DataMapItem dataMapItem =

DataMapItem.fromDataItem(event.getDataItem());

Asset profileAsset =

dataMapItem.getDataMap().getAsset("profileImage");

Bitmap bitmap = loadBitmapFromAsset(profileAsset);

// Do something with the bitmap

}

}

}

Page 34: Slightly Advanced Android Wear ;)

public Bitmap loadBitmapFromAsset(Asset asset) {

if (asset == null) {

throw new IllegalArgumentException("Asset must be non-null");

}

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(context)

.addApi(Wearable.API)

.build();

ConnectionResult result =

mGoogleApiClient.blockingConnect(TIMEOUT_MS, TimeUnit.MILLISECONDS);

if (!result.isSuccess()) {

return null;

}

Page 35: Slightly Advanced Android Wear ;)

// convert asset into a file descriptor and block until it's ready

InputStream assetInputStream = Wearable.DataApi.getFdForAsset(

mGoogleApiClient, asset).await().getInputStream();

mGoogleApiClient.disconnect();

if (assetInputStream == null) {

Log.w(TAG, "Requested an unknown Asset.");

return null;

}

// decode the stream into a bitmap

return BitmapFactory.decodeStream(assetInputStream);

}

Page 36: Slightly Advanced Android Wear ;)

The mark of a great man is one who knows when to set aside the important things in order to accomplish the vital ones. Brandon Sanderson

“’’

takahirom / WearHttp

Page 37: Slightly Advanced Android Wear ;)

Advanced UI

Page 38: Slightly Advanced Android Wear ;)

Custom Notifications

Page 39: Slightly Advanced Android Wear ;)

Create custom activity

Display it as notification

Page 40: Slightly Advanced Android Wear ;)

<activity

android:name="com.example.NotificationActivity"

android:exported="true"

android:allowEmbedded="true"

android:taskAffinity=""

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

/>

AndroidManifest.xml

Page 41: Slightly Advanced Android Wear ;)

public class NotificationActivity extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_notification);

mImageView = (ImageView) findViewById(R.id.image_view);

mTextView = (TextView) findViewById(R.id.text_view);

Intent intent = getIntent();

if (intent != null) {

mTextView.setText(intent.getStringExtra(EXTRA_TITLE));

final Asset asset = intent.getParcelableExtra(EXTRA_IMAGE);

loadBitmapFromAsset(this, asset, mImageView); // do async!

}

}

NotificationActivity.java

Page 42: Slightly Advanced Android Wear ;)

<service android:name=".OngoingNotificationListenerService">

<intent-filter>

<action

android:name="com.google.android.gms.wearable.BIND_LISTENER"

/>

</intent-filter>

</service>

AndroidManifest.xml

Page 43: Slightly Advanced Android Wear ;)

public class OngoingNotificationListenerService extends WearableListenerService {

private GoogleApiClient mGoogleApiClient;

@Override

public void onCreate() {

super.onCreate();

mGoogleApiClient = new GoogleApiClient.Builder(this)

.addApi(Wearable.API)

.build();

mGoogleApiClient.connect();

}

OngoingNotificationListenerService.java

Page 44: Slightly Advanced Android Wear ;)

@Override

public void onDataChanged(DataEventBuffer dataEvents) {

final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);

dataEvents.close();

if (!mGoogleApiClient.isConnected()) {

ConnectionResult connectionResult = mGoogleApiClient

.blockingConnect(30, TimeUnit.SECONDS);

if (!connectionResult.isSuccess()) {

Log.e(TAG, "Service failed to connect to GoogleApiClient.");

return;

}

}

OngoingNotificationListenerService.java

Page 45: Slightly Advanced Android Wear ;)

for (DataEvent event : events) {

if (event.getType() == DataEvent.TYPE_CHANGED) {

String path = event.getDataItem().getUri().getPath();

if (PATH.equals(path)) {

// Get the data out of the event

DataMapItem dataMapItem =

DataMapItem.fromDataItem(event.getDataItem());

final String title = dataMapItem.getDataMap().getString(KEY_TITLE);

Asset asset = dataMapItem.getDataMap().getAsset(KEY_IMAGE);

OngoingNotificationListenerService.java

Page 46: Slightly Advanced Android Wear ;)

// Build the intent to display our custom notification

Intent notificationIntent =

new Intent(this, NotificationActivity.class);

notificationIntent.putExtra(

NotificationActivity.EXTRA_TITLE, title);

notificationIntent.putExtra(

NotificationActivity.EXTRA_IMAGE, asset);

PendingIntent notificationPendingIntent = PendingIntent.getActivity(

this,

0,

notificationIntent,

PendingIntent.FLAG_UPDATE_CURRENT);

OngoingNotificationListenerService.java

Page 47: Slightly Advanced Android Wear ;)

// Create the ongoing notification

Notification.Builder notificationBuilder =

new Notification.Builder(this)

.setSmallIcon(R.drawable.ic_launcher)

.setLargeIcon(BitmapFactory.decodeResource(

getResources(), R.drawable.ic_launcher))

.setOngoing(true)

.extend(new Notification.WearableExtender()

.setDisplayIntent(notificationPendingIntent)

.setBackground(myCustomBackground));

OngoingNotificationListenerService.java

Page 48: Slightly Advanced Android Wear ;)

// Build the notification and show it

NotificationManager notificationManager =

(NotificationManager)getSystemService(NOTIFICATION_SERVICE);

notificationManager.notify(

NOTIFICATION_ID, notificationBuilder.build());

} else {

Log.d(TAG, "Unrecognized path: " + path);

}

}

}

}

OngoingNotificationListenerService.java

Page 49: Slightly Advanced Android Wear ;)

Disable swipe to dismiss

Page 50: Slightly Advanced Android Wear ;)

<application

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

AndroidManifest.xml

Page 51: Slightly Advanced Android Wear ;)

<resources>

<style

name="AppTheme"

parent="@android:style/Theme.DeviceDefault.Light"

>

<item name="android:windowSwipeToDismiss">false</item>

</style>

</resources>

Styles.xml

Page 52: Slightly Advanced Android Wear ;)

Long press to dismiss

Page 53: Slightly Advanced Android Wear ;)

public class ApolloDiceActivity extends Activity implements View.

OnTouchListener, View.OnLongClickListener {

private DismissOverlayView mDismissOverlayView;

@Override

protected void onCreate(Bundle savedInstanceState) {

// ...

RelativeLayout parentFrame = (RelativeLayout)

findViewById(R.id.parent_frame);

// Add DismissOverlayView to cover the whole screen

mDismissOverlayView = new DismissOverlayView(this);

parentFrame.addView(mDismissOverlayView,new RelativeLayout.

LayoutParams(

RelativeLayout.LayoutParams.MATCH_PARENT,

RelativeLayout.LayoutParams.MATCH_PARENT));

parentFrame.setOnLongClickListener(this);

Page 54: Slightly Advanced Android Wear ;)

// Shows the default dismiss overlay

public boolean onLongClick(View v) {

mDismissOverlayView.show();

return true;

}

// Set onTouch to return false so it doesn’t

// intercept the LongClick event:

public boolean onTouch(View v, MotionEvent event) {

//...

return false;

}

Page 55: Slightly Advanced Android Wear ;)

Round and Rect layout

Page 56: Slightly Advanced Android Wear ;)

<?xml version="1.0" encoding="utf-8"?>

<android.support.wearable.view.WatchViewStub

xmlns:android="http://schemas.android.com/apk/res/android"

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

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/watch_view_stub"

android:layout_width="match_parent"

android:layout_height="match_parent"

app:rectLayout="@layout/rect_activity_match_timer"

app:roundLayout="@layout/round_activity_match_timer"

tools:context=".MatchTimerNotificationActivity"

tools:deviceIds="wear" />

res/layout/activity_match_timer.xml

Page 57: Slightly Advanced Android Wear ;)

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/timer"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MatchTimerNotificationActivity"

tools:deviceIds="wear_square">

<TextView

android:id="@+id/elapsed"

res/layout/rect_activity_match_timer.xml

Page 58: Slightly Advanced Android Wear ;)

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_match_timer);

final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);

stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener()

{

@Override

public void onLayoutInflated(WatchViewStub stub) {

elapsed = (TextView) stub.findViewById(R.id.elapsed);

// ...

}

});

}

MatchTimerNotificationActivity.java

Page 59: Slightly Advanced Android Wear ;)

Complex examples

Page 60: Slightly Advanced Android Wear ;)

How We Customized Google Apps for Android Wear

Page 61: Slightly Advanced Android Wear ;)

What’s next?Getting Started with Android Weardeveloper.android.com/wear

Developers Italiadevelopersitalia.blogspot.com

Page 62: Slightly Advanced Android Wear ;)

+AlfredoMorresi@rainbowbreeze

Thank you!

Credits:Hoi Lam: http://viewbeyond.blogspot.co.uk

Styling Android: http://blog.stylingandroid.comImages: all images are under CC licence