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

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

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

[<img src="https://image.slidesharecdn.com/androidqgesturalnavigationanddarktheme-190518072634/95/android-q-gestural-navigation-and-dark-theme-1-638.jpg?cb=1558164424" width="400">](https://www.slideshare.net/furusin/android-q-gestural-navigation-and-dark-theme-146395117)
# 何に注意が必要なのか？
上のスライドでも触れていますが、Gestural Navigationは
> 画面の両サイドからスワイプすると戻る挙動
をします。

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

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

# どう対処したらいい？
方法は２つあります。

* 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でフリックイベントを取得する](https://qiita.com/stvjbz/items/2de62fe12c1c725ef49f)
の記事を参考にしました。

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


```Kotlin: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)
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84307/b4549239-41f2-ffc1-6b7c-ad23aacd209e.gif" width="300">


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

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

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