[Android] フリック入力機能を実装してみた

#完成形！

![flickMovie.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306161/a1968ecf-4f6a-51a4-1e4d-b05e2cb4460b.gif)

#はじめに

フリック機能を搭載したアプリを作りたかったのですが、[公式](https://developer.android.com/training/gestures/detector)を見ると
フリックされたことを検知する処理しか掲載されていなかったので、
[こちらの記事](https://www.ohmyenter.com/flick-listener-in-android-dev/)を参考にさせていただき、自作でリスナーを作成しました。

これからフリック入力機能を実装したいと思う方の一助になればと思います。

#実装

##FlickListener

```FlickListener.kt
class FlickListener(
    private val listener: Listener
) : View.OnTouchListener {

    // フリックイベントのリスナー
    interface Listener {
        fun onButtonPressed()
        fun onButtonReleased()
        fun onFlickToLeft()
        fun onFlickToRight()
        fun onFlickToUp()
        fun onFlickToDown()
        fun onFlickOutside()
        fun onSwipingOnLeft()
        fun onSwipingOnRight()
        fun onSwipingOnUp()
        fun onSwipingOnDown()
        fun onSwipingOnCenter()
        fun onSwipingOutside()
    }

    companion object {
        // フリック判定時の遊び。小さいほど判定が敏感になる
        const val DEFAULT_PLAY = 100f
    }

    private val play = DEFAULT_PLAY
    private var startX: Float = 0f
    private var startY: Float = 0f
    private var endX: Float = 0f
    private var endY: Float = 0f

    override fun onTouch(v: View, event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> touchDown(event)
            MotionEvent.ACTION_MOVE -> swipe(event)
            MotionEvent.ACTION_UP -> touchOff(event)
        }
        return true
    }

    private fun touchDown(event: MotionEvent) {
        startX = event.x
        startY = event.y
        listener.onButtonPressed()
    }

    private fun swipe(event: MotionEvent) {
        endX = event.x
        endY = event.y
        when {
            outScope()   -> listener.onSwipingOutside()
            leftScope()  -> listener.onSwipingOnLeft()
            rightScope() -> listener.onSwipingOnRight()
            upScope()    -> listener.onSwipingOnUp()
            downScope()  -> listener.onSwipingOnDown()
            else         -> listener.onSwipingOnCenter()
        }
    }

    private fun touchOff(event: MotionEvent) {
        endX = event.x
        endY = event.y
        when {
            outScope()   -> listener.onFlickOutside()
            leftScope()  -> listener.onFlickToLeft()
            rightScope() -> listener.onFlickToRight()
            upScope()    -> listener.onFlickToUp()
            downScope()  -> listener.onFlickToDown()
            else         -> listener.onButtonReleased()
        }
    }

    private fun leftScope(): Boolean = endX < startX - play
    private fun rightScope(): Boolean = startX + play < endX
    private fun upScope(): Boolean = endY < startY - play
    private fun downScope(): Boolean = startY + play < endY
    private fun outScope(): Boolean =
        (leftScope() && upScope()) || (rightScope() && upScope()) || 
                (leftScope() && downScope()) || (rightScope() && downScope())
}
```

`startX`, `startY`, `endX`, `endY` と `play` との関係ついては、次の図を見てもらえればわかると思います！
ボタンのサイズや感度に合うように、`play` を変更して調整しましょう。

<img width="1048" alt="FlickScope.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306161/1b792f46-97ed-5039-acf7-c708eaa2934a.png">


##MainActivity

centerボタンにFlickListernerを設定し、そのプロパティをオブジェクト式で実装します。

```MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        center.setOnTouchListener(FlickListener(flickListener))
    }

    private val flickListener = object : FlickListener.Listener {

        override fun onButtonPressed() {
            center.setBackgroundButtonColor(R.color.pressedButtonColor)
            toggleVisible()
        }

        override fun onSwipingOnCenter() = center.settingSwipe()
        override fun onSwipingOnLeft() = left.settingSwipe()
        override fun onSwipingOnRight() = right.settingSwipe()
        override fun onSwipingOnUp() = top.settingSwipe()
        override fun onSwipingOnDown() = bottom.settingSwipe()

        override fun onButtonReleased() = settingFlick("中")
        override fun onFlickToLeft() = settingFlick("左")
        override fun onFlickToRight() = settingFlick("右")
        override fun onFlickToUp() = settingFlick("上")
        override fun onFlickToDown() = settingFlick("下")

        private fun settingFlick(label: String) {
            showToast(label)
            Thread.sleep(100)
            toggleInvisible()
            clearAll()
        }

        private fun toggleVisible() {
            top.visibility = View.VISIBLE
            left.visibility = View.VISIBLE
            right.visibility = View.VISIBLE
            bottom.visibility = View.VISIBLE
        }

        private fun toggleInvisible() {
            top.visibility = View.INVISIBLE
            left.visibility = View.INVISIBLE
            right.visibility = View.INVISIBLE
            bottom.visibility = View.INVISIBLE
        }

        private fun clearAll() {
            center.setBackgroundButtonColor(R.color.baseButtonColor)
            left.setBackgroundButtonColor(R.color.baseButtonColor)
            right.setBackgroundButtonColor(R.color.baseButtonColor)
            top.setBackgroundButtonColor(R.color.baseButtonColor)
            bottom.setBackgroundButtonColor(R.color.baseButtonColor)
        }

        private fun View.settingSwipe() {
            clearAll()
            setBackgroundButtonColor(R.color.pressedButtonColor)
        }

        private fun View.setBackgroundButtonColor(@ColorRes resId: Int) =
            setBackgroundColor(ContextCompat.getColor(applicationContext, resId))

        private fun showToast(msg: String) = Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
    }
}
```

#おわりに
今回、フリックする速度は考慮していません！
それでも機能としては十分だったので、よければ使ってみてください＼(^o^)／

また、この機能の実装自体は難しいものではなかったので、
Android初心者の方の勉強用としても最適だと思いました！

もっとこうすれば良くなる！などの改善点やアドバイスがあれば教えていただけると嬉しいです！