Wear で手首の操作を使用する

タッチ スクリーンが使いづらいときでも、手首の動きを使用して、アプリを片手で簡単に操作できます。

たとえば、ユーザーは片手に飲み物のコップを持ったまま、もう片方の手で通知をスクロールできます。手首の操作の他の例を以下に示します。

  • ジョギング アプリで、歩数、走行時間、現在のペースの各表示画面を移動する
  • 空港で荷物を持っているときに、フライトや搭乗口の情報をスクロールする
  • ニュース記事をスクロールする

スマートウォッチでの手首の操作を確認するには、[設定] > [操作] > [手首の操作: ON] を選択し、手首の操作が有効になっていることを確認します。次に、スマートウォッチで操作チュートリアルを実施します([設定] > [操作] > [チュートリアルの起動])。

Wear OS by Google ヘルプの次の操作は、アプリでは使用できません。

  • 手首を振る

手首の操作は以下の方法で使用できます。

次の表に示すように、手首の操作はそれぞれ KeyEvent クラスの int 定数にマッピングされています。

操作 KeyEvent 説明
手首を外側にすばやく振る KEYCODE_NAVIGATE_NEXT このキーコードが次のアイテムに進みます。
手首を内側にすばやく振る KEYCODE_NAVIGATE_PREVIOUS このキーコードが前のアイテムに戻ります。

曲線レイアウトを使用して手首の操作をサポートする

WearableRecyclerView クラスはリスト用の曲線レイアウトを提供し、手首の操作を自動的にサポートします。このクラスには、ビューがフォーカスを保持しているときに手首の操作によって実行される事前定義済みのアクションが含まれています。WearableRecyclerView クラスの使い方について詳しくは、リストを作成するをご覧ください。また、おすすめの方法もご覧ください。

注: WearableRecyclerView クラスは、ウェアラブル サポート ライブラリのサポートが終了した同種のクラスの後継クラスです。

WearableRecyclerView を使用する場合でも、KeyEvent クラスの定数を使用できます。WearableRecyclerView をサブクラス化して onKeyDown() コールバックを再実装することで、事前定義済みのアクションをオーバーライドできます。この動作を完全に無効化するには、setEnableGestureNavigation(false) を使用します。キーボード アクションの処理もご覧ください。

キーイベントを直接使用する

WearableRecyclerView の外部でキーイベントを使用すると、操作イベントに対する新しいアクションをトリガーできます。これらの操作イベントは、次のように処理される点が重要です。

  • デバイスがアクティブ モードの場合は認識される
  • すべてのキーイベントと同じ方法で配信される

具体的には、これらのイベントは最上位のアクティビティと、キーボード フォーカスを保持するビューに配信されます。他のキーイベントと同様に、 KeyEvent.Callback を実装した、ユーザー操作に関連するクラス(View や Activity など)は、手首の操作に関連するキーイベントをリッスンできます。Android フレームワークは、キーイベントを通じてフォーカスを保持するビューまたはアクティビティを呼び出します。操作が行われたときは、onKeyDown() メソッドのコールバックが呼び出されます。

たとえば、アプリは View または Activity(どちらも KeyEvent.Callback を実装)内で、事前定義済みのアクションを次のようにしてオーバーライドできます。

Kotlin

    class GesturesActivity : Activity() {

        /* KeyEvent.Callback */
        override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
            return when (keyCode) {
                KeyEvent.KEYCODE_NAVIGATE_NEXT ->
                    // Do something that advances a user View to the next item in an ordered list.
                    moveToNextItem()
                KeyEvent.KEYCODE_NAVIGATE_PREVIOUS ->
                    // Do something that advances a user View to the previous item in an ordered list.
                    moveToPreviousItem()
                else -> {
                    // If you did not handle it, let it be handled by the next possible element as deemed
                    // by the Activity.
                    super.onKeyDown(keyCode, event)
                }
            }
        }

        /** Shows the next item in the custom list.  */
        private fun moveToNextItem(): Boolean {
            …
            // Return true if handled successfully, otherwise return false.
            return false
        }

        /** Shows the previous item in the custom list.  */
        private fun moveToPreviousItem(): Boolean {
            …
            // Return true if handled successfully, otherwise return false.
            return false
        }
    }
    

Java

    public final class GesturesActivity extends Activity {

     @Override /* KeyEvent.Callback */
     public boolean onKeyDown(int keyCode, KeyEvent event) {
      switch (keyCode) {
       case KeyEvent.KEYCODE_NAVIGATE_NEXT:
        // Do something that advances a user View to the next item in an ordered list.
        return moveToNextItem();
       case KeyEvent.KEYCODE_NAVIGATE_PREVIOUS:
        // Do something that advances a user View to the previous item in an ordered list.
        return moveToPreviousItem();
      }
      // If you did not handle it, let it be handled by the next possible element as deemed by the Activity.
      return super.onKeyDown(keyCode, event);
     }

     /** Shows the next item in the custom list. */
     private boolean moveToNextItem() {
      boolean handled = false;
      …
      // Return true if handled successfully, otherwise return false.
      return handled;
     }

     /** Shows the previous item in the custom list. */
     private boolean moveToPreviousItem() {
      boolean handled = false;
      …
      // Return true if handled successfully, otherwise return false.
      return handled;
     }
    }
    

おすすめの方法

  • キーイベントのビューとアクティビティへの配信については、KeyEvent および KeyEvent.Callback のページを確認してください。
  • 以下の方法で、一貫した指向性アフォーダンスを維持します。
    • 次に進むには「手首を外側にすばやく振る」を使用し、前に戻るには「手首を内側にすばやく振る」を使用します。
  • 手首の操作に対応するタップ操作を使用できるようにします。
  • 視覚的なフィードバックを提供します。
  • 直感に反する機能をシステムの残りの部分に実装するためにキーコードを使用しないでください。たとえば、アクションをキャンセルしたり、手首をすばやく振る動作で左右の軸を移動したりするために KEYCODE_NAVIGATE_NEXT を使用しないでください。
  • ユーザー インターフェースに含まれていない要素(画面の表示領域外のビューや部分的に覆われているビューなど)でキーイベントをインターセプトしないでください。これは、他のキーイベントでも同じです。
  • 手首を繰り返し振るジェスチャーを独自の新しい操作として解釈し直さないでください。 システムの「手首を振る」操作と競合する可能性があります。
  • ビューが手首の操作のキーイベントを受け取るには、フォーカスを保持する必要があります。詳しくは、View::setFocusable() をご覧ください。手首の操作はキーイベントとして扱われるため、「タップモード」からの遷移をトリガーし、それによって予期しない事態が発生することがあります。したがって、ユーザーがタップと手首の操作を併用する可能性があるため、View::setFocusableInTouchmode() メソッドが必要になることがあります。場合によっては、「タップモード」との間でモードの遷移が行われた後でフォーカスが変更された際に、目的のビューがフォーカスを取得できるように、setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS) を使用しなければならないこともあります。
  • requestFocus()clearFocus() は慎重に使用してください。
    • requestFocus() を呼び出すときは、ビューが実際にフォーカスを取得していることを確認してください。ビューが画面の表示領域外にある場合や、別のビューで覆われている場合、手首の操作によってコールバックがトリガーされると、予期しない事態が発生することがあります。
    • clearFocus() は、別の適切なビューを見つけるためにフォーカスの検索を開始します。ビュー階層によっては、この検索でかなりの量の計算が必要になることがあります。最終的に、フォーカスを受け取るとは予想しなかったビューにフォーカスが割り当てられることもあります。
  • キーイベントはまず、ビュー階層内のフォーカスを保持するビューに配信されます。フォーカスを保持するビューがイベントを処理できない場合(つまり、false を返す場合)、親ビューがフォーカスを受け取ることができ、なおかつ KeyListener を持っていたとしても、イベントは親ビューには配信されません。代わりに、フォーカスを保持するビュー階層を持つ現在のアクティビティに配信されます。そのため、より上位のレベルですべてのイベントをキャッチしてから、関連するコードに渡す必要があります。または、Activity をサブクラス化して dispatchKeyEvent(KeyEvent event) メソッドをオーバーライドすることもできます。これにより、必要に応じてキーがインターセプトされるようにします。また、より下位のレイヤでキーが処理されなかった場合に処理されるようにします。