2014年7月28日月曜日

Material Design のハンバーガーアイコン

ハンバーガーアイコンは Navigation Drawer を使うときに左上に表示する三本線のアイコンのことです。



Icons - Style - Google design guidelines にも Resources - Google design guidelines にもアイコンリソースが無かったので、Icons のページにあった絵と線の太さが2dpであるという情報からハンバーガーアイコンをaiファイルに起こしました。

ちなみに設計図は次のようになります。1マスが1dpです。



画像のサイズは 48dp x 48dp で、アイコン部分は 24dp x 24dp に収まるようにします。

aiファイルも置いておきます。



2014年7月23日水曜日

Polymer <a> 内の <content> は Chrome ではリンクにならない

リンクテキストの右側にアイコンを付けるだけのコンポーネントがあるとします。 <link rel="import" href="../core-icon/core-icon.html"> <polymer-element name="icon-link" attributes="href"> <template> <style> a { color: #0277bd; text-decoration: none; } </style> <p> <a href="{{href}}" target="new"> <content></content> <core-icon icon="launch"></core-icon> </a> </p> </template> <script> Polymer({ }); </script> </polymer-element> これを、こんな感じで使うと <icon-link href="http://y-anz-m.blogspot.com">Y.A.Mの雑記帳</icon-link> Chrome(Version 36.0.1985.125)では <content></content> に挿入される文字がリンクになりません。




一方、Firefox(31.0)ではリンクになります。





次のように <content> ではなく、attributes を使うと Chrome でも文字部分がリンクになります。 <link rel="import" href="../core-icon/core-icon.html"> <polymer-element name="icon-link" attributes="href label"> <template> <style> a { color: #0277bd; text-decoration: none; } </style> <p> <a href="{{href}}" target="new"> {{label}} <core-icon icon="launch"></core-icon> </a> </p> </template> <script> Polymer({ }); </script> </polymer-element> <icon-link href="http://y-anz-m.blogspot.com" label="Y.A.Mの雑記帳"></icon-link>


Web Components に詳しくないので、よくわからないのですが Chrome の挙動は仕様通りなのかしら?


追記:実際に見れるリンクないの?と言われたのでつくりました。http://www.yanzm.net/sample1.html

追記2:Chrome 38(Chrome Canary)だと直っているそうだ。

追記3:以下のように<span>で囲むと Chrome 36 でもリンクとして動作する。 <icon-link href="http://y-anz-m.blogspot.com"><span>Y.A.Mの雑記帳</span></icon-link> しかし、マウスカーソルの形はホバーしても変わらない。。。たぶん、https://code.google.com/p/chromium/issues/detail?id=314488 の問題。


2014年7月18日金曜日

Google I/O 2014 Polymer セッションまとめ

Google I/O 2014 - Polymer and the Web Components revolution



Topeka というサンプルアプリを作った。
Polymer と Material Design をテストするため。
http://polymer-project.org/apps/topeka
トリビアアプリ
Leaderboardがある

PCのChromeブラウザ上でのデモ。画面サイズを変えても動く。
Nexus5のChrome Betaでも動く。

"Polymer is a library that makes building applications easier"

"Polymer is different than what has come before"

"Polymer was built differently"

Polymer チームは Chrome チームの一部。2年前の今日(I/Oの日)プロジェクトを始めた。

Polymer は Web Components で作られている。
Web Components はすべてを変える。
Custom elements を作ることができる。Shadow DOM を使っている。
HTML の再利用ができる。

Chrome 36 からネイティブ対応。(I/Oのときは)beta channel。2、3週間で stable に入る。
Chrome だけ?そんなことない。 polyfills のおかげで思っているよりも多くのブラウザで動く。

Polymer は boilerplate を減らす。
Polymer は宣言的なシンタックス。

どうやって Poymer を使うのか?
1. エレメントを使う
2. エレメントを作る

エレメントを使う
1. 使いたいエレメントを見つける
2. Import する
<link rel="import" href="my-button.html">
3. 使う
<my-button label="Press Me!"></my-button>

Polymer のエレメントはただのHTML

エレメントを作る
1. 新しいタグとプロトタイプを登録
2. ビューを定義
3. イベントを処理
4. データとビューを紐づける
5. 属性の変更に対応する

Polymer Core Elements
ベーシックなエレメント。あまりスタイリングされていない。
<core-icon>
<core-ajax>
<core-localstorage>
<core-style>
<core-tooltip>

ajax や localstorage のタグもある


Polyme Paper Elements
Material Design っぽいエレメント
Button, Input, Tab, Card, Panel など...

mozilla は というのを出しているが、Polymer と対立するものではない。

デザイナーツールを用意した。
Polymer Designer

最適化
Polymer Vulcanizer
すべての import をまとめて CSS, JS, HTML にする

テストには Karma, Chai, Mocha を使っている。


QA

Q : Window を縮めたとき、エレメントがリサイズされませんでした。これは、いつ Window のリサイズに応じるかという Polymer paper のビジョンのよるものですか? media queries を行き来するよりも、エレメントはその場に残りますか?
(あんまり質問の意味がわからない)

A : 見ているスクリーンによると思います。つまり、どのようにセットアップされているかによると思います。ほとんどのウィジェットはコンテナーの幅に依存します。


Q : Polymer はとても cool ですが、まだまだプロトタイプですよね。Angular とかのライブラリを使う必要がある場合、Polymer paper とかを取り入れることはできますか? それとも Polymer で最初からやり直さないといけない?

A : custom elements はただの HTML エレメントです。どのように HTML を扱うか知っていれば、Polymer elements も使うことができます。element の外側と内側の世界があります。外側の世界では Polymer は他の HTML エレメントと同じようなものです。プロパティ、メソッド、属性があります。内側の世界では Polymer が提供する利点があります。Polymer の内側をフレームワークで作ろうとした場合にトラブルが起きることがあるかもしれません。なぜなら、それらは Shadow DOM などの扱い方を知らないからです。


Q : Polymer components と x-tagas components 両方含むような component directory はありますか?

A : customelements.io のような Polymer かどうかによらないレジストリがあります。Polymer かどうかによらず、書く web component を使うことができます。


Q : Safari と他のブラウザのサポートはどうですか?

A : native support のことですよね? Mozilla はかなり進んでいます。custom elements はリリースされています。Shadow DOM はまだです。でも活発に進んでいます。Safari は正直わかりません。彼らは計画を公開していないので。Salesforce.com のような polyfill を使っている人たちによると、mobile Safari では、かなり許容できるパフォーマンスがあるようです。


Q : IE については?

A : IE は最近少しだけ計画について公開するようになってきました。web components について興味があるようです。計画について誰にも言わないので、正直わかりませんが。ときどき私たちのMLにでできます。WinJS と Polymer を合わせた実験を内部では完了しているようです。Polymer と web components に興味がありそうですが、様子を見る必要があります。


Q : Chromeでの実行パフォーマンスは素晴らしかったです。どのくらいが Polymer の影響で、どのくらいが Chrome のパフォーマンス改善によるものですか?

A : 実際、それらは混在しています。我々は Chrome チームの一部ですし。両方のコンビネーションです。


Q : long-term のパフォーマンスについて。DOM elements + DOM 操作と、DOM を最小化しようとする React(これでよい?)のようなフレームワークとを比較するとどうですか?

A : それは別の世界ですよね? polyfills を使った現状から考えると、Polymer は基本的に他の JavaScript フレームワークと同じくらいよいです。Chrome ならなおよいです。native support がありますから。
その他のこととして、数年前 Polymer チームは webOS の Enyo(これでよい?)と呼ばれるフレームワークに関わっていました。哲学的に React、Polymer、Enyo は component のマインドセットが似ています。ただ、アイディアとして、DOM にタッチしません。DOM は遅い、DOM にさわりたくないでしょう。そこで、大きい HTML を吐いていました。それが速かったですから。Chrome チームに移ったとき、Chrome の人たちはそれはよくないと言いました。そこから時間をかけていろいろ変えていきました。DOM APIs はすごく速くなりました。なので、その領域は改善の余地があるということです。Chrome がそうできたように。





Google I/O 2014 - Polymer and Web Components change everything you know about Web development



(現状、字幕のタイミングがスピーチとずれています。。。)

1. State of the union
2. Problems solved by web components
3. "Thinking in components"

2010年に Web Components の旅は始まった。

2014年のサポート状況



platform.js を使ったときの polyfills のサポート状況



みんな IE のこと質問するよね。希望はあるよ!



・Chrome OS の Keyboard は Polymer を使ってる。
・Chrome OS の Media Player は Polymer を使ってる。

Web Components はどんな問題を解決するのか。
・Gmail は div soup
・tab strip を作ろうとしたら、標準的な構成やパターンもないし、APIはいろんなフレームワークで違うし。。。



Custom elements を使うと、宣言的で読みやすいHTMLになる。



タグの継承もできる



HTML Template と比較してどうか





Polymer 内でデータをバインディングしたりできる



Polymer はデフォルトで Shadow DOM を使う



Polymer のエレメントは HTML imports で使う



Polymer の Core elements



Material Design elements



<google-map> タグを用意したよ!便利!
他のタグもチェック!

googlewebcomponents.github.io







Google I/O 2014 - Unlock the next era of UI development with Polymer



HTML は document-centric model なので、アプリっぽいものを作るのは苦手。
Polymer はアプリの作成に適している。


core-elements : ベースになるブロックにようなもの。あまりスタイル化されていない。

Visual
・<core-toolbar>
・<core-header-panel>
・<core-drawer-panel>
・<core-menu>
・<core-icon>
・<core-overlay>
...

Non-visual
・<core-ajax>
・<core-localstorage>
・<core-range>
・<core-shared-lib>
・<core-media-query>
・<core-iconset>



paper-elements : よりスタイル化されている。Material Design システムの一部

・<core-toolbar>

タブやボタン、タイトルを置くためのコンテナ
Android の ActionBar みたいなもの
core-icon-button でハンバーガーアイコンを表示したり、class="tall" で高さをかえたり




・<core-header-panel>

header セクション用のコンテナ
core-toolbar を上に pin させたり、一緒にスクロールさせたり




・<core-scroll-header-panel>

スクロール時のエフェクトができる




・<core-drawer-panel>

Navigation Drawer みたいなの





flexbox CSS

Polymer 定義の中でも外(=Polymerを使っているページ)でも使える。

Android の layout_weight みたいなことができる。










中央揃えも楽々。








Material controls

<paper-checkbox> : Material Design にそった、アニメーション付きのチェックボックス

<paper-toggle-button> : Material Design にそった、アニメーション付きのスイッチ

<paper-input> : Material Design にそった、アニメーション付きの入力フォーム
validate="^[0-9]*$" のように正規表現でバリデーションを指定できる

<paper-tabs> & <paper-tab> : Material Design にそった、アニメーション付きのタブ

<paper-ripple> : タッチ&クリックしたときに ripple エフェクトがおこる

<paper-shadow> : z="5" のように elevation を指定することで影の量を変えられる



Theming

::shadow

エレメントの shadow dom 内部のノードのスタイルを指定できる



/deep/

shadow 境界に穴をあけ、shadow dom 内のすべてにスタイルを指定できる



<core-style>

(まだ experimental)
style をエレメント間で共有する



style binding





Transitions

<core-animated-pages>

あるビューから次のビューへのスムーズなアニメーションを作れる



QA

Q : attributes の順番に依存はありますか?

A : 順番は関係ないです。


Q : jQuery とかと一緒に使うのはどうですか?

A : 外側から利用する場合は、これまでと一緒です。custom element を作るだけです。内部で利用したい場合は、慎重になる必要があります。


Q : shadow dom の内部では JavaScript はサンドボックスになりますか?

A : いいえ、JavaScript はページの一部です。


Q : Angular JS と一緒に使うのはどうですか?

A : 一般的には、ちゃんと動きそうです。複雑なデータを binding するときに問題が起こるかもしれません。ただし、HTML と同じように扱う分においては、問題ないです。


Q : Accessibility はどうですか?チェックボックスは canvas を使っているようですが。タブをキーボードで移動しようとしてもできませんでした。なにかロードマップはありますか?

A : はい。Accessibility は我々にとっても大きな懸念事項です。Alice Boxhall が DevByte で詳しく説明しています。ロードマップがありますし、サポートする予定があります。


Q : レガシーな Android の WebView ではどうですか?

A : 私の理解では、KitKat の WebView は Polymer で動くようです。Chromium ベースではにものは動かないでしょう。


Q : IE ではどうですか?

A : 他のブラウザでも動きますよ。他のすべてのブラウザでテストしています。Firefox、Safari、IE。。。各ブラウザの最新の2バージョンはサポートしています。それがターゲットです。


Q : Bootstrap に比べてどうですか?

A : Bootstrap は素晴らしいです。でもたくさんマークアップが必要です。nav bar とか。将来的には Bootstrap はエレメントのコレクションになるかもしれません。



2014年7月10日木曜日

Android Wear に Notification で出来ること

参考

概要
  • スマホと Wear が接続されていると、Notification が Wear にも表示(同期)される。
  • Wear では通知はカードとして表示され、このカードが表示されるところを context stream という。
  • これまでの通知でももちろん Wear に表示されるが、Wear 用に Notification を拡張することができる。


Notification を作る

Notification の作成には NotificationCompat.Builder を使う。これで作っておけば、システムがかってにスマホと Wear で通知の見た目を変えてくれる。

通知の発行には NotificationManagerCompat を使う(NotificationManager ではなく)。



■ SmallIcon だけの Notification int notificationId = 001; NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ


Wear(左: ホーム画面、右: タップした状態)


Wearのカードの右上に表示されるアイコンは setSmallIcon() で指定したものではなく、アプリアイコンになる。 タップしたときの背景色はどこから来てるのかよくわからない。アプリアイコンのカラーパレット?



■ タイトルとメッセージありの Notification int notificationId = 001; NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ


Wear(左: ホーム画面、右: タップした状態)




■ Content Intent ありの Notification int notificationId = 001; // Content Intent 用の PendingIntent を作成 Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ


Wear(左: ホーム画面、中央: タップした状態、右: タップしたあと左にスワイプ)


setContentIntent() で PendingIntent を指定すると、Open on phone(携帯で開く)が追加され、それをタップすると指定した PendingIntent が実行される。



■ InboxStyle の Notification Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .setStyle(new NotificationCompat.InboxStyle() .addLine("1行目") .addLine("2行目") .setContentTitle("インボックス タイトル") .setSummaryText("+3 more")); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ


Wear(左: ホーム画面、中央: タップした状態、右: タップしたあと左にスワイプ)




■ BigTextStyle の Notification Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .setStyle(new NotificationCompat.BigTextStyle() .bigText(getString(R.string.long_text) .setContentTitle("ビッグテキスト") .setSummaryText("サマリー")); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ(上: 閉じてる状態、下: 開いた状態)



Wear(上: ホーム画面、中: タップした状態、下: 中でタップすると全文が見れる)






■ BigPictureStyle の Notification Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .setStyle(new NotificationCompat.BigPictureStyle() .bigPicture(bitmap) .setContentTitle("ビッグピクチャー") .setSummaryText("サマリー")); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ(上: 閉じてる状態、下: 開いた状態)



Wear(上: ホーム画面、下: タップした状態)



bitPicture() で指定した画像が背景になる。カードがなく背景にセットされた画像を見れるページが追加される。
タイトルには BigPictureStyle.setContentTitle() が表示されるが、その下は setSummaryText() ではなく setContentText() が表示される。




Action ボタンを追加する

Gmail の Notification の Archive に相当するもの。


*アイコンがぼけているように見えますが、実際ぼけています。Open on phone と比較すると明らかにぼけています。

NotificationCompat.Builder の addAction() で、画像、テキスト、タップされたときの PendingIntent を指定する。 Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); // Action 用の PendingIntent を作成 Intent mapIntent = new Intent(Intent.ACTION_VIEW); Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location)); mapIntent.setData(geoUri); PendingIntent mapPendingIntent = PendingIntent.getActivity(this, 0, mapIntent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .addAction(R.drawable.ic_map, "Map", mapPendingIntent); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); addAction() の第1引数に指定するアイコンは ActionBar のアイコンと同じサイズにする。
つまり、 32dp x 32dp(内枠 24dp x 24dp)。

Wear 側で表示される画像は、スマホの解像度に一致したリソース。スマホが xxhdpi の場合、hdpi のリソースがあっても、xxhdpi のリソースが Wear に表示される。

スマホ


Wear(上: ホーム画面、下: タップした状態)






Wear だけのアクションを追加

Wear にだけ表示されるアクションを追加できる。スマホの Notification には表示されない。

WearableExtender.addAction() を使う。
このメソッドを Action を追加すると、Wearable では NotificationCompatBuilder.addAction() で指定された Action は表示されない。 そのため、別の機能を WearableExtender.addAction() で与えるというよりは、スマホ用に NotificationCompatBuilder.addAction() で指定した機能を、Wear では Wear 用に適したものに置き換えたい場合に使う。

例えば、Gmail の返信機能は、スマホでは返信画面を開くだけだが、Wear では音声入力の結果を返信できるようにしている。 Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); // Action 用の PendingIntent を作成 Intent mapIntent = new Intent(Intent.ACTION_VIEW); Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location)); mapIntent.setData(geoUri); PendingIntent mapPendingIntent = PendingIntent.getActivity(this, 0, mapIntent, 0); // Wear 用 Action を作成 NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_map_for_wear, "Map for Wear", mapPendingIntent) .build(); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .addAction(R.drawable.ic_map, "Map for phone", mapPendingIntent) .extend(new NotificationCompat.WearableExtender().addAction(action)); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); Wear 用のアイコンには Action Bar の2倍、つまり 64dp x 64dp(内枠 48dp x 48dp)の画像を指定する。こうするとNotificationCompat.Builder.addAction() で Wear に表示していたときと違って画像がぼけない。

スマホ


Wear(上: ホーム画面、下: タップした状態)






背景画像を指定する

■ setLargeIcon() を使う

setLargeIcon() を使うと、カードをタップしたときの背景に利用される。 NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .setLargeIcon(bitmap); BigPictureStyle を使った場合、setLargeIcon() を指定していても Wear の背景は BigPictureStyle.bigPicture() で指定した画像になる。

スマホ


Wear(左: ホーム画面、右: タップした状態)


■ WearableExtender.setBackground() を使う

スマホの Notification で LargeIcon を使いたくない場合は、NotificationCompat.WearableExtender の setBackground() を使う。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setBackground(BitmapFactory.decodeResource( getResources(), R.drawable.sample)); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); スマホ


Wear(左: ホーム画面、右: タップした状態)





Wear のカードでアプリアイコンを表示しない

カードの右上に表示されるアプリアイコンを消すには、NotificationCompat.WearableExtender.setHintHideIcon() を使う。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setHintHideIcon(true); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); Wear(左: ホーム画面、右: タップした状態)





Wear のカード内にアイコンを表示する

NotificationCompat.WearableExtender.setContentIcon() を使う。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setContentIcon(R.drawable.ic_droid); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); ここでは、アプリアイコンサイズの画像を指定してみた。
ambient 時は白の tint がかかる。

Wear(上: ホーム画面、下: タップした状態)






Wear のカード内にアイコンを左に表示する

NotificationCompat.WearableExtender. setContentIconGravity() で GravityCompat.START を指定する。 指定できるのは START と END で、デフォルトは END。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setContentIcon(R.drawable.ic_droid) .setContentIconGravity(GravityCompat.START); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); Wear(上: ホーム画面、下: タップした状態)






カードの位置を変える

setGravity() を使う。指定できるのは Gravity.TOP、Gravity.CENTER_VERTICAL、Gravity.BOTTOM。デフォルトは BOTTOM。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setGravity(Gravity.TOP); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); Wear(左: ホーム画面、右: タップした状態)

Gravity.TOP


Gravity.CENTER_VERTICAL





オリジナルのカードを表示する

これは Wear アプリから Notification を発行するときにだけ使えます。
setDisplayIntent() で、カードをタップしたときに表示する Activity を持った PendingIntent を指定します。

Wear 上のコードなので、NotificationCompat ではなく Notification を使っています。 // この MainActivity は Wear アプリの MainActivity Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); // カードをタップしたときに表示される Activity // ここでは ImageView 1つだけのレイアウト Intent displayIntent = new Intent(this, DisplayActivity.class); PendingIntent displayPendingIntent = PendingIntent.getActivity(this, 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setDisplayIntent(displayPendingIntent); Notification.Builder notificationBuilder = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(notificationId, notificationBuilder.build()); Wear から Notification を発行する場合、setSmallIcon() で指定したアイコンがカードの右上に表示される。
setContentIntent() で指定した PendingIntent は Open(開く)という Action になる。

Wear(左: ホーム画面、右: タップした状態)





オリジナルのカードの大きさを変える

setCustomSizePreset()を使う。
指定できるのは、SIZE_DEFAULT、SIZE_FULL_SCREEN、SIZE_LEARGE、SIZE_MEDIUM、SIZE_SMALL、SIZE_XSMALL。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setDisplayIntent(displayPendingIntent) .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_FULL_SCREEN); Notification.Builder notificationBuilder = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); Wear(左: ホーム画面、右: タップした状態)

SIZE_FULL_SCREEN


SIZE_LARGE


SIZE_MEDIUM


SIZE_SMALL


SIZE_XSMALL





Notification から音声入力を使う

Notification に返信するなどテキストを入力するアクションがある場合、Wear にはキーボードがない代わりに RemoteInput を使って、音声入力を使うことができる。

*エミュレータには音声入力がないので、AVDの設定で Hardware keyboard present を有効にしておくと、代わりにキーボードで入力できる。

RemoteInput は RemoteInput.Builder を使って作る。コントラクタには音声入力結果に紐づけるキー(文字列)を渡す。 private static final String EXTRA_VOICE_REPLY = "extra_voice_reply"; ... String replyLabel = getResources().getString(R.string.reply_label); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel(replyLabel) .build(); setLabel() でラベルを指定すると、上部の青色部分に表示される。 複数の項目を入力させたい場合などのに便利。
例えば、"Ok google, remind me" というと、About what? と When? を別々に入力する画面になる。



RemoteInput を Notification に組み込むには、NotificationCompat.Action.Builder の addRemoteInput() を使う。 // 音声入力の結果を受けとるための PendingIntent を作る Intent replyIntent = new Intent(this, ReplyActivity.class); PendingIntent replyPendingIntent = PendingIntent.getActivity(this, 0, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT); // RemoteInput 用の Action を作る NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_reply, "Reply message", replyPendingIntent) .addRemoteInput(remoteInput) .build(); // WearableExtender に addAction() で RemoteInput の Action を追加 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .addAction(action); Notification.Builder notificationBuilder = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); 音声入力が正しく終ると、NotificationCompat.Action.Builder の第3引数に指定した PendingIntent が実行される。

PendingIntent に指定された Activity もしくは Service では、getIntent() で取得した Intent を RemoteInput.getResultsFromIntent() に渡して Bundle を取得し、RemoteInput に指定したキーで文字列を取り出す。 /** * Activity.getIntent() を渡す */ private CharSequence getMessageText(Intent intent) { Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); if (remoteInput != null) { return remoteInput.getCharSequence(EXTRA_VOICE_REPLY); } return null; } * 音声入力の結果は ClipData として保存されているため、Intent.getExtras() を使わずに getResultsFromIntent() を使うこと。

Wear(上: ホーム画面、中: タップした状態、下: reply actionをタップ&入力した状態)




setLabel() を指定しない場合。





■ 音声入力時に選択肢を与える

選択肢をあらかじめ用意しておくこともできる。選択肢は5つまで。setChoices() で文字列の配列を渡して指定する。 <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="reply_choices"> <item>Yes</item> <item>No</item> <item>Maybe</item> </string-array> </resources> public static final EXTRA_VOICE_REPLY = "extra_voice_reply"; ... String replyLabel = getResources().getString(R.string.reply_label); // 選択肢 String[] replyChoices = getResources().getStringArray(R.array.reply_choices); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel(replyLabel) .setChoices(replyChoices) .build();



■ 選択肢だけから選択させる

音声入力を使わず、選択肢だけから選択させることもできる。 そのためには、setAllowFreeFormInput() で false を指定する。
この場合、setChoices() で選択肢を指定しておかないと IllegalArgumentException になる。 public static final EXTRA_VOICE_REPLY = "extra_voice_reply"; ... String replyLabel = getResources().getString(R.string.reply_label); // 選択肢 String[] replyChoices = getResources().getStringArray(R.array.reply_choices); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel(replyLabel) .setChoices(replyChoices) .setAllowFreeFormInput(false) .build();





Page を追加する

追加の情報を表示したい場合などに、Page を追加することができる。

例えば、ハングアウトのカードでは、メインのカードに最新のメッセージが表示され、次のページに最近の投稿メッセージが表示される。
Google Now の天気カードでは、メインのカードに今日の天気、次のページに明日から四日間の天気が表示される。



Page を追加するには NotificationCompat.WearableExtender の addPage()addPages() を使う。 // メインの Notification の NotificationCompat.Builder を作る NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Page 1") .setContentText("吾輩は猫である") .setContentIntent(pendingIntent); // 2ページ目の Notification を作る Notification secondPageNotification = new NotificationCompat.Builder(this) .setContentTitle("Page 2") .setContentText(getString(R.string.long_text)) .build(); // addPage() で2ページ目を追加し、extend() で1ページ目を拡張する Notification twoPageNotification = new NotificationCompat.WearableExtender() .addPage(secondPageNotification) .extend(notificationBuilder) .build(); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, twoPageNotification); スマホ


Wear(上: ホーム画面、中: タップした状態)







Notification をまとめる

同じような通知はまとめて表示することが推奨されているが、Wearでは個別の通知の中がみれないのは不便。
そこで、1つのカードにグループ化し、カードをタップするとそれぞれの Notification が個別のカードに別れるよう作ることができる。

Gmailのカードはまさにこの形になっている。

カードをグループ化するには NotificationCompat.Builder.setGroup() で同じ文字列を指定する。 final static String GROUP_KEY_EMAILS = "group_key_emails"; // Build the notification, setting the group appropriately Notification notif = new NotificationCompat.Builder(mContext) .setContentTitle("New mail from " + sender1) .setContentText(subject1) .setSmallIcon(R.drawable.new_mail); .setGroup(GROUP_KEY_EMAILS) .build(); // Issue the notification NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId1, notif); setGroup() に同じ文字列を指定し、notify() で指定する ID は別にする。 Notification notif2 = new NotificationCompat.Builder(mContext) .setContentTitle("New mail from " + sender2) .setContentText(subject2) .setSmallIcon(R.drawable.new_mail); .setGroup(GROUP_KEY_EMAILS) .build(); notificationManager.notify(notificationId2, notif2); Wear(上: ホーム画面、中: タップした状態、下: 中をタップした状態)




グループ化されたカードがスタックされる順番は、新しく発行されたものが上になる(デフォルト)。
setSortKey() を使うと、順番を任意に指定することができる。


setGroup() を指定した Notification はスマホでは表示されないため、Summary Notification を用意する。 Summary Notification では setGroup() に同じ文字列を指定し、setGroupSummry() に true を指定する。この Summary Notification は Wear では表示されない。 Notification summaryNotification = new NotificationCompat.Builder(mContext) .setContentTitle("2 new messages") .setSmallIcon(R.drawable.ic_notif) .setStyle(new NotificationCompat.InboxStyle() .addLine(line1) .addLine(line2) .setBigContentTitle("2 new messages") .setSummaryText(summary)) .setGroup(GROUP_KEY_EMAILS) .setGroupSummary(true) .build(); notificationManager.notify(notificationId3, summaryNotification); スマホ


Summary Notification は Wear に直接表示されることはないが、 NotificationCompat.WearableExtender を使ってスタック全体の背景やスタック全体に対する Action を指定できる。 Bitmap background = BitmapFactory.decodeResource(getResources(), R.drawable.ic_background); NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setBackground(background); // extend() で背景を指定 Notification summaryNotificationWithBackground = new NotificationCompat.Builder(mContext) .setContentTitle("2 new messages") ... .extend(wearableExtender) .setGroup(GROUP_KEY_EMAILS) .setGroupSummary(true) .build(); Wear