先日Android Wear上でTwitterのタイムラインを見ることができるアプリLookingをリリースしました。
Notificationを使用しないタイプのAndroid Wearアプリを実際に作成してみて、
得られた情報を簡単にまとめておきたいと思います。
開発環境
今回の開発は次の環境で行いました。
- Android Studio v0.9.2
- Nexus5
- LG G Watch
実機を使用したデバッグ方法はこちらを参考にさせていただきました。
Android Wearアプリの作り方
開発前の準備
アプリを作り始める前にAndroid Wearでどんなことができるのか、公式のドキュメントにざっと目を通しますとわかるかと思います。
http://developer.android.com/training/wearables/apps/index.html
Lookingを作る上で必要になったのは主に次の2点でした。
・カスタムUI
Android Wearでは既存のButton等のUIも同様に使用することができますが、
build.gradleに次のライブラリを追加することでAndroid Wear用のカスタムUIが使用できます。
compile 'com.google.android.support:wearable:+
非常に便利なUIとしてGridViewPager
があります。
・データの同期
Android Wearとペアリングしている端末(スマートフォン等、以下Handheld)とデータをやりとするための方法が提供されています。
今回のアプリでは次の2つのAPIを利用しました(他にもNodeAPIがあります)
Message API
Android WearとHandheld間で一方的な通知を送るためのもの。合わせてバイト配列も送れます。
例えば、Android WearからHandheldにTwitterのタイムラインをリクエストする場合に使用できます。
Data Layer API
Android WearとHandheld間でデータを同期するためのもので、
データを作成、変更、削除すると相手側に通知されるようになっています。
現状Android Wear単体ではネットワーク通信ができないので、Handheld側でTwitterからタイムラインを取得し、このAPIを使用してデータを同期します。
これらの仕組みを利用することで、簡単にWearable Appを作ることができます。
・プロジェクト
Android StudioでAndroid Wear用アプリを作成すると、デフォルトでは mobile と wear モジュールがあります。
各モジュールで端末毎のアプリを作成していくことになります。
実装
主にAndroid Wear側のアプリについての内容です。
・カスタムUI
アプリを作成するにあたり使用したカスタムUIのビューについてです。
GridViewPager
行と列から構成されるビューのコンテナです、上下左右のスワイプでビューを切り替えることができます。
このレイアウトを使用してタイムラインの表示を行いました。
使用するにあたり、関係するクラスは次の3クラスがあります。
-
GridPagerAdapter
GridViewPagerにビューを表示するためのアダプタ。
独自レイアウトのビューを使用したい場合はこちらを使用します。 -
FragmentGridPagerAdapter
GridPargerAdapterから派生したアダプタ。用途は同じですが、グリッド毎のビューをフラグメントで作成します。
次のCardFragmentとセットで使用することが多いと思われます。 -
CardFragment
冒頭のアプリ画像にもあるようなカードレイアウトのビューを表示するためのフラグメント。
個人的には、カードレイアウトを使用する場合は FragmentGridPagerAdapter を、使用しない場合は GridPagerAdapter を使用するのが簡単ではないかと思います。
GridViewPagerを使用してビューを表示する流れは次のようになります。
1. GridViewPagerのレイアウト定義
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.GridViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"/>
ビューコンテナ毎にandroid:keepScreenOn
フラグをtrue
にすると、画面が常時点灯になります。
2. CardFragmentの派生クラスの作成
CardFragmentは派生クラスを作成しなくても使用できますが、レイアウトは固定的になってしまいます。
Twitterの用に文字ベースの情報だとデフォルトのままでは若干文字が大きいのと、テキストの色等カスタマイズしたかったので派生クラスを作成しました。
このカスタム画面のレイアウトは次のようになります。定義するのはカード上のレイアウトのみになります。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:paddingTop="@dimen/card_content_padding_rect_top"
android:paddingRight="@dimen/card_content_padding_rect_right"
android:paddingLeft="@dimen/card_content_padding_rect_left">
<ImageView
android:id="@android:id/icon"
android:layout_width="26dp"
android:layout_height="26dp"
android:src="@drawable/ic_tweet_user" />
<TextView
android:id="@+id/tweet_user"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_toRightOf="@android:id/icon"
android:layout_alignBottom="@android:id/icon"
android:layout_marginLeft="6dp"
android:text="itadakit@droibit"
android:singleLine="true"
android:ellipsize="end"
android:textColor="@color/primary_color"
android:textSize="16.5sp" />
<TextView
android:id="@+id/tweet_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_below="@id/tweet_user"
android:paddingTop="4dp"
android:text="Hello."
android:textColor="@color/primary_text_light"
android:textSize="@dimen/tweet_text"
android:textColorLink="@color/primary_text_title_color"
android:linksClickable="false"/>
</RelativeLayout>
RealtiveLayoutのpaddingの値はサポートライブラリに定義されているのでそれを使用しています。
ライブラリのresフォルダを見てみると他にも参考になるかもしれません。
このレイアウトを使用するためにはCardFragmentの派生クラスを作成します。
ポイントは CardFragment#onCreateContentView(...) メソッドをオーバーライドするところです。
// タイムライン上の1ツイートを表示するためのフラグメント。
public class TweetCardFragment extends CardFragment {
// ファクトリメソッドは省略
/** {@inheritDoc} */
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.content_tweet_card, container, false);
}
/** {@inheritDoc} */
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final TextView userView = (TextView) view.findViewById(R.id.tweet_user);
userView.setText("ユーザ名");
final TextView textView = (TextView) view.findViewById(R.id.tweet_text);
textView.setText("テキスト");
// カードの背景
view.setBackgroundResource(R.drawable.tweet_background);
}
}
背景をカスタマイズしたい場合は、コンテナビューに画像リソースを設定します。
ここで背景を設定した場合は1グリッド毎にこの画像が使用されます。
3. FragmentGridPagerAdapter派生クラスの作成
項目2で作成したフラグメントを使用する新しいアダプタを作成します。
// ツイートを{@link GridViewPager}上に表示するためのアダプタ。
public class TweetPagerAdapter extends FragmentGridPagerAdapter {
private List<Tweet> mTweets;
/**
* 新しいインスタンスを作成する。
*
* @param timeline Twitterのタイムライン
* @param fm フラグメントマネージャ
*/
public TweetPagerAdapter(Timeline timeline, FragmentManager fm) {
super(fm);
mTweets = timeline.getTweets();
}
/** {@inheritDoc} */
@Override
public Fragment getFragment(int row, int column) {
final TinyTweet tweet = mTweets.get(row);
return TweetCardFragment.newInstance(tweet);
// デフォルトのCardFragmentを使用する場合
//return CardFragment.create(tweet.user.name, tweet.text, R.drawable.ic_user);
}
/** {@inheritDoc} */
@Override
public int getRowCount() {
return mTweets.size();
}
/** {@inheritDoc} */
@Override
public int getColumnCount(int row) {
return 1;
}
/** {@inheritDoc} */
@Override
public ImageReference getBackground(int row, int column) {
return super.getBackground(row, column);
}
}
後は GridViewPager#setAdapter(...)で作成したアダプタを設定します。
このアダプタだと縦方向のスワイプのみですが、ツイートに対するアクション用のフラグメントを作成し、アダプタを修正すれば横方向にもスワイプできます。
misc
主に使用したのはGridViewPagerでしたが、それ以外のサポートライブラリのUIを使用してみて
いろいろ分かったこともありました。
・CircledImageViewの問題点
これは丸いボタンのようなビューを簡単に作ることができます。
通知系のアプリではこの見た目のものがボタンとして使われているため、アプリでもCircledImageView
を使おうとしました。
しかし、CircledImageViewをタッチしたところ、丸が 四角につぶれてしまった ので使えませんでした。代替案として、青い丸の画像を用意して使用しました。
参考: SDK付属サンプル:JumpingJack
画像のサイズはmdpi-104pxで作成しています(サポートライブラリのvalues.xml参考)。
・DismissOverlayViewの問題点
これは[×]のような外観のキャンセル用のビューで、×ボタンをタッチするとこのビューを表示しているActivityごと終了してくれるというものです。
このビューは合わせてメッセージを表示することもできますが、×ボタンのレイアウトが崩れてしまうため使用できませんでした。
代替案として、ConfirmationActivityの FAILURE_ANIMATION を使用しました。
・データの同期
データの同期にはGooglePlayサービスを使用します。
同期するためにはMessage, Data Layer APIのイベントリスナーを追加し、イベントが発生した際に色々と処理をします。
Lookingのようなタイムラインのやりとり(リクエストとレスポンス)を行う場合は、
Android WearとHandheld両方でイベントリスナーを実装する必要があります。その際、方法は主に2種類あります。
- Activityで実装
- WearableListenerServiceの派生クラスの作成
Activityを表示している間だけやりとりする場合はActivityで、Activityを表示しない場合はServiceを使用するのがいいのではないかと思います。
Lookingの場合は、Android Wear側はActivityで実装、Handheld側はServiceを作成しています。
実装ははSDK付属:DataLayerおよびAndroid WearのData Layer APIを試してみたを参考にさせていただきました。
Data Layer APIをを使用して独自クラスのリストを同期するためにこちらを利用させていただきました。
DataMapParcelableUtils
利用するにあたり、独自クラスはParcelableの実装が必要になります。
・おまけ
現在のAndroidでは画面を作成する際にActivityもしくはFragmentを利用できます。
同様にAndroid Wearでも両クラスを使用することができます。
// OK
// ※ Activityの場合はスワイプで元の画面に戻ることができます。
final Intent intent = new Intent(srcActivity, DestActivity.class);
srcActivity.startActivity(intent);
// OK
// ※ Fragmentの場合はスワイプで元の画面に戻ることができません。
final Fragment newFragment = new NewFragment();
srcActivity.getFragmentManager().beginTransaction()
.replace(R.id.cotent, newFragment)
// or
.add(R.id.content, newFragment)
.commit();
Lookingは当初Fragmentベースで画面を構成していましたが問題がありました。
画面が消灯しバックグラウンドに回った後、アプリをリスタートすると、Fragment上のビューが全て破棄され画面が真っ白になってしまいました。
また、ConfirmationActivity等のオーバーレイActivityを表示した場合も同様に画面が白くなりました。
スマートフォン/タブレットではこのようなことはありませんでしたが、Android Wear上ではこのような挙動になりますので、現状では Activityベース で画面を作成するほうがいいのではないかと思われます。
リリース
Wearable Appのapkを作成する方法はPackaging Wearable Appsに詳しく説明されています。
あとは、mobileおよびwearモジュールの applicationId が同一のものであれば問題無いと思います。
ビルドする際はmobileモジュールを選択し、作成されたapkをGoogle Playに提出します。
Google Playに提出する際に必要な画像はAndroid Studioから撮れます。
アップデート
アプリをアップデートする際は、最低限mobileモジュール側のbuilde.gradleの versionCode 及び versionName 修正します。
以上、簡単ですがWearable Appを初めてリリースするまでの流れになります。