Wearable UI Libraryまとめ

#### Wearable UI Library について

Android Wear向けのアプリケーションを提供する場合、フォンやタブレットといった現行のUIデザインで実装しようとすると使用ケースやデバイスが異なるため、見辛い、操作しにくいといったことに陥りがちです。

Wearデザインに適したアプリを容易に開発者が実装できるように、Wear向けのUIコンポーネントをまとめたライブラリが(Wearable UI Library)提供されています。

導入方法はbuild.gradleファイルに以下のコードを追加するだけです。

```xml:build.gradle
compile 'com.google.android.support:wearable:+'`
```

#### Wearable UI Library で提供されているコンポーネント

提供さているコンポーネントを列挙していきます。全てではありませんが簡単な説明をまとめています。ここでは四角型のWearデバイスをShapeデバイス、丸型のWearデバイスをRoundデバイスと呼んでいます。

サンプルコードは[https://github.com/Tazake/SampleWearUI](https://github.com/Tazake/SampleWearUI)にあります。

##### BoxInsetLayout

BoxInsetLayoutは、画面の形状を認識するFrameLayoutです。
Roundデバイス画面上に入る正方形の中に子Viewを配置します。

![box_inset_layout_round.png](https://qiita-image-store.s3.amazonaws.com/0/41759/6c95920c-775c-7319-484e-9358c5f41933.png)
![box_inset_layout_square.png](https://qiita-image-store.s3.amazonaws.com/0/41759/21edb233-2581-4a6c-568b-c6bc4fd930b4.png)

子Viewにlayout_box属性に正方形の端( top , left , right , button , all )を指定すると、子Viewの指定した端の部分がはみ出ないように調整されます。
Shapeデバイスの場合は指定しても無視されます。

```xml:activity_box_inset_layout.xml
<android.support.wearable.view.BoxInsetLayout
    android:id="@+id/box_inset_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_launcher"
        app:layout_box="top|left"/>
</android.support.wearable.view.BoxInsetLayout>
```

ShapeデバイスとRoundデバイスでレイアウトを変更する方法は、BoxInsetLayout以外にはWatchViewStubでレイアウトファイルを切り替える方法と、onApplyWindowInsetsでJavaコード上でレイアウトを調整する方法があります。

##### CardFrame と CardScrollView
CardFrameは背景が白く、白角丸でドロップシャドウがかかったカードスタイルを提供します。
CardFrame内に各Viewを配置することでカードスタイルのレイアウトを作成することができます。
但し、スクロールはできないので必要な場合はCardScrollViewも使用することになります。

CardScrollViewはCardFrameにスクロール機能を提供するViewです。CardScrollViewの子要素にはCardFrameのみ配置することができます。

![card_scroll_view.png](https://qiita-image-store.s3.amazonaws.com/0/41759/ffc5449c-d7b8-1bb7-e513-889e85df7a75.png)


```xml
<android.support.wearable.view.CardScrollView
    android:id="@+id/grid_card_scroll_view"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.wearable.view.CardFrame
        android:id="@+id/grid_card_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="20dp"
                android:text="Page1"
                android:textColor="#333333"/>
            <TextView ... />
        </LinearLayout>

    </android.support.wearable.view.CardFrame>
</android.support.wearable.view.CardScrollView>
```


カードスタイルのデザインを表現するするには、CardScrollView内にCardFrameを配置しての使用。もしくは、CardFragment（内部ではCardScrollViewとCardFrameを使用している）を使用するケースが大半になります。

##### CircledImageView

円で囲まれた画像です。
円の色、半径、枠線と枠線の幅を指定することができます。

![circled_image_view.png](https://qiita-image-store.s3.amazonaws.com/0/41759/97e8c847-8030-392a-0ed7-8dcd18ad1e49.png)

```xml:activity_circled_image_view.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:app="http://schemas.android.com/apk/res-auto"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:background="@color/white">

    <android.support.wearable.view.CircledImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@android:drawable/ic_menu_rotate"
        app:circle_border_color="#999999"
        app:circle_border_width="2dp"
        app:circle_color="#3d3d3d"
        app:circle_radius="30dp"
        app:circle_radius_pressed="60dp"/>

</FrameLayout>
```

##### ConfirmationActivity
ユーザーがアクションを完了した後に、確認のアニメーションを表示するActivityです。
アクションの成功、失敗、phoneで開くといったものが提供されています。

![confirmation_activity_success.png](https://qiita-image-store.s3.amazonaws.com/0/41759/310ed47f-18b6-893f-fed0-cd607130aa0b.png)
![confirmation_activity_failure.png](https://qiita-image-store.s3.amazonaws.com/0/41759/d2801d49-8e6d-73d3-d039-b7da0244bdf3.png)
![confirmation_activity_open_phone.png](https://qiita-image-store.s3.amazonaws.com/0/41759/41032478-1cc4-9041-4010-8ce2d9866a01.png)


ConfirmationActivityを使用するにはAndroidManifest.xmlに宣言を追加する必要があります。

```xml:AndroidManifest.xml
<activity android:name="android.support.wearable.activity.ConfirmationActivity" />
```

表示する時はIntentを生成してstartActivityを呼ぶだけです。

```java:ConfirmationMainActivity.java
Intent intent = new Intent(this, ConfirmationActivity.class);
intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, ConfirmationActivity.SUCCESS_ANIMATION);
                intent.putExtra(ConfirmationActivity.EXTRA_MESSAGE, getString(R.string.confirm_success));
startActivity(intent);
```

Intentのextraに各種パラメータを指定することで、アニメーションの種類、メッセージを変更することができます。

|パラメータ名|内容|
|---|---|
|EXTRA_ANIMATION_TYPE|アニメーションの種類（SUCCESS_ANIMATION、SUCCESS_ANIMATION,OPEN_ON_PHONE_ANIMATION）|
|EXTRA_MESSAGE|表示するメッセージ|

デフォルトだと、アニメーションはSUCCESS_ANIMATION、EXTRA_MESSAGEは未設定となっています。

##### DismissOverlayView

long-press-to-dismissを実装したViewです。
エラー情報を画面上にフルスクリーンで覆い被さるように表示します。
GestureDetectorでロングタップを検知し、DismissOverlayViewを表示するように使用します。
DismissOverlayViewをタップすると自動的に表示しているActivityは閉じられます。

![dismiss_overlay_view.png](https://qiita-image-store.s3.amazonaws.com/0/41759/3776d500-ae75-b18d-a95e-1a79b8b7fe07.png)


```java:DismissOverlayViewActivity.java
    private GestureDetectorCompat mGestureDetector;
    private DismissOverlayView mDismissOverlayView;

    private class LongPressListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public void onLongPress(MotionEvent event) {
            mDismissOverlayView.show();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dismiss_overlay_view);

        mGestureDetector = new GestureDetectorCompat(DismissOverlayViewActivity.this, new LongPressListener());
        mDismissOverlayView = (DismissOverlayView) findViewById(R.id.dismiss_overlay);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return mGestureDetector.onTouchEvent(event) || super.dispatchTouchEvent(event);
    }
```

##### GridViewPager

上下左右に移動できるViewPagerです。
GridPagerAdapterを実装して使用します。


##### WatchViewStub
デバイスの形状の違いを判定して、レイアウトを変更するコンポーネントです。
Wearプロジェクトを作成するとデフォルトで組み込まれています。


##### WearableListView

小さい画面であるウェアラブルデバイスに最適化されたListViewです。
縦にスクロールできるリストが表示され、ユーザーがスクロールを止めると、近いリストアイテムに自動的にsnapします。
RecyclerViewを継承して作成されており、ListViewとは使用方法が少し異なります。

![wearable_list_view.png](https://qiita-image-store.s3.amazonaws.com/0/41759/8731a719-b4f1-addb-e28a-0a7c44253b2b.png)


```xml:rect_activity_wear.xml
<android.support.wearable.view.WearableListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
```

```java:WearActivity.java
private WearableListView mListView;

mListView.setAdapter(new SampleAdapter(this, mWidgetList));
mListView.setHasFixedSize(true);
mListView.setClickListener(new WearableListView.ClickListener() {
        @Override
            public void onClick(WearableListView.ViewHolder viewHolder) {
             //do something            }

        @Override
        public void onTopEmptyRegionClick() {
            //do something
});
```

```java
    private class SampleAdapter extends WearableListView.Adapter {

        private LayoutInflater mLayoutInflater;
        private List<WearUI> mWidgetList;

        public SampleAdapter(Context context, List<WearUI> widgetList) {
            super();
            mLayoutInflater = LayoutInflater.from(context);
            mWidgetList = widgetList;
        }

        @Override
        public WearableListView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            View v = mLayoutInflater.inflate(android.R.layout.simple_list_item_1, viewGroup, false);
            MyViewHolder viewHolder = new MyViewHolder(v);
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(WearableListView.ViewHolder viewHolder, int position) {
            MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
            String data = mWidgetList.get(position).getWidgetName(getApplicationContext());
            myViewHolder.text.setText(data);
        }

        @Override
        public int getItemCount() {
            return mWidgetList.size();
        }
    }

    static class MyViewHolder extends WearableListView.ViewHolder {
        public TextView text;

        public MyViewHolder(View v) {
            super(v);
            text = (TextView) v.findViewById(android.R.id.text1);
        }
    }
```