Android QのGesturalNavigationとDrawerLayoutの競合に対するボクなりの最適解


Android Qでは Gestural Navigation が基本となり、アプリ側で対応が必要となりました。

Gestural Navigationについて詳しくはこちらのスライドで触れています。

何に注意が必要なのか?

上のスライドでも触れていますが、Gestural Navigationは

画面の両サイドからスワイプすると戻る挙動
をします。

ということは、これまでDrawerLayoutを画面左右からのスワイプで表示していたのが競合します。
何も対応しないと

Drawerを表示したいのに、アプリが終了してしまう

という動きになってしまい、ユーザー体験は良くないものとなってしまいます。

どう対処したらいい?

方法は2つあります。

  • DrawerLayoutをAndroidXの物を使う
  • 自分で対応する(私が出した最適解)

DrawerLayoutをAndroidXの物を使う

build.gradle
implementation 'androidx.drawerlayout:drawerlayout:1.1.0-alpha01'

このAndroidXのDrawerLayoutを使うと
- Drawerが閉じてる時:Drawerが開く
- Drawerが開いている時:アプリが終了する
という挙動になります。

果たしてこれは本当にユーザーが求めている挙動なのか・・・?
Gestural Navigationは始まったばかりの機能なので「ユーザーが求める挙動」の定義は難しいですが
なんだか違和感があります。

ということで、最適解を考えてみました。

自分で対応する(私が出した最適解)

Android版のTwitter公式アプリがやっているDrawerの出し方が、体験としては非常に良いのではないかと感じています。

  • 画面のどこでも左から右にスワイプ:Drawerが開く
  • 画面両サイドからスワイプ:Gestural Navigationでアプリが終了する

私はAndroid Q Beta3を入れて日々Gestural Navigationを使っていますが、

画面両サイドからスワイプすると「戻る」挙動

を感覚的に求めていました。なので、Twitter公式アプリの挙動が最適解なのではないかと感じました。

具体的な実装

サンプルコードは https://github.com/furusin/GesturalNavigationSample に上げています。

画面上でのスワイプ(フリック)の検知は
Androidでフリックイベントを取得する
の記事を参考にしました。

AndroidXのDrawerLayoutを使わず、GestureDetector.SimpleOnGestureListeneronFling() で画面上でのスワイプ(フリック)を検知するようにしています。

MainActivity.kt
class MainActivity : AppCompatActivity() {
    companion object {
        // X軸最低スワイプ距離
        private const val SWIPE_MIN_DISTANCE = 50
        // X軸最低スワイプスピード
        private const val SWIPE_THRESHOLD_VELOCITY = 200
    }

    lateinit var gestureDetector: GestureDetector

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 左から右へのスワイプでDrawerLayoutを開く
        gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
            override fun onFling(
                event1: MotionEvent, event2: MotionEvent, velocityX: Float, velocityY: Float
            ): Boolean {
                // 左から右へのフリックの判定
                if (event2.x - event1.x > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    findViewById<DrawerLayout>(R.id.drawerLayout).openDrawer(Gravity.LEFT)
                }
                return false
            }
        })
    }

    // ListViewの場合はonTouchEventではなく
    // dispatchTouchEventを使う
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        // GestureDetectorにイベントを流す
        gestureDetector.onTouchEvent(ev)
        return super.dispatchTouchEvent(ev)
    }
}

まとめ

いかがだったでしょうか。
まだ本記事執筆時点(2019/05/28)ではAndroid QはBeta3ですし、Gestural Navigationの挙動自体が変わる可能性はあります。

ですので「こんな考え方(回避のしかた)もあるんだな」程度で捉えていただければと思います。

また、「横向きスクロールのViewがあった場合どうするの」といった問題もあり、そこは考慮できてません。
もう少し考える必要はあるかなぁと思います。