AndroidでHaptic Feedbackを実装する

本記事では、iOSではおなじみのHaptic Feedback(触覚フィードバック)をAndroidで実装する方法について記載しています。
Android Dev Summit 2019」の動画で実装例が紹介されていたので、こちらを参考にしました。


Haptic FeedbackについてはMaterial Designの以下のページが参考になります。

material.io


AndroidでHaptic Feedbackを実装する方法

AndroidでHaptic Feedbackを実装する方法は2通りあります。

  • HapticFeedbackConstantsを使用する方法
  • VibrationEffectを使用する方法


HapticFeedbackConstantsはViewのイベントに対する応答に、VibrationEffectはフィードバックの強度が重要な応答に使用するようです。また、VibrationEffectはViewに依存せず、バックグラウンドでも実行できます。
以降はHapticFeedbackConstantsを使用するHaptic Feedbackの話になります。

Haptic Feedbackは物理ボタンなどの触覚を再現するための仕組みなので、押したとき(Press)と離したとき(Release)のフィードバックをセットで考える必要があります。
公式ドキュメントにいくつかの定数が記載されていますが、基本的にはキーボードのイベントに対してはKEYBOARD_PRESSKEYBOARD_RELEASE、その他のViewのイベントに対してはVIRTUAL_KEYVIRTUAL_KEY_RELEASEを使用すればよさそうです。
(厳密にはTimePickerなどは異なりますが、本記事では割愛します)

コードサンプル

Haptic Feedbackを実行するためには、xmlファイルまたはkt/javaファイルから対象のViewのisHapticFeedbackEnabledをtrueにする必要があります。
また、onTouchListenerの実装をsetOnTouchListener()にセットしてタッチイベントをフックし、実際のHaptic Feedbackの実行にはViewクラスのperformHapticFeedback()を使用します。HapticFeedbackConstantsperformHapticFeedback()の引数に指定します。

私の場合、上記の処理をBinding Adapterに実装したので、Haptic Feedbackを実行するかどうかのフラグをxmlファイルから受け取り、isHapticFeedbackEnabledに対してその値をコードからセットしました。

// Haptic Feedback用のListener
class HapticTouchListener : View.OnTouchListener {
  override fun onTouch(v: View, event: MotionEvent): Boolean {
    val isHandled = v.onTouchEvent(event)
    if (!v.isHapticFeedbackEnabled) return isHandled

    if (isHandled) {
      when (event.actionMasked) {
        // 押したとき
        MotionEvent.ACTION_DOWN -> {
          v.performHapticFeedback(
            HapticFeedbackConstants.VIRTUAL_KEY
          )
        }
        // 離したとき
        MotionEvent.ACTION_UP -> {
          // VIRTUAL_KEY_RELEASEはAPI Level 27以上で使用可能なので、
          // 必要に応じて条件分岐を行う
          v.performHapticFeedback(
            HapticFeedbackConstants.VIRTUAL_KEY_RELEASE
          )
        }
      }
    }

    return isHandled
  }
}

// 使用例
@BindingAdapter("hapticFeedbackApplied")
fun View.setHapticTouchListener(isApplied: Boolean) {
  isHapticFeedbackEnabled = isApplied
  setOnTouchListener(HapticTouchListener()) 
}