カスタムビューをインタラクティブにする

Compose を試す
Jetpack Compose は Android で推奨される UI ツールキットです。Compose でレイアウトを操作する方法を学習します。

UI の描画は、カスタムビューを作成するプロセスの一部にすぎません。また、模倣している現実世界のアクションによく似た方法で、ビューがユーザー入力に応答するようにする必要があります。

アプリ内のオブジェクトを、実際のオブジェクトのように動作させます。たとえば、アプリ内の画像を、存在しているところからポップアウトさせて別の場所に再表示させないでください。現実世界のオブジェクトは、そのようなアクションは行わないからです。代わりに、画像をある場所から別の場所に移動します。

ユーザーは、インターフェースでのわずかな動作や感覚も感知し、現実世界を模倣した繊細な動作に最もよく反応します。たとえば、ユーザーが UI オブジェクトをフリングするとき、最初に動きを遅らせる慣性を感じさせます。動きが終わると、フリングの向こう側に物体を運ぶ勢いが感じられるようにします。

このページでは、Android フレームワークの機能を使用して、こうした実際の動作をカスタムビューに追加する方法を説明します。

その他の関連情報については、入力イベントの概要プロパティ アニメーションの概要をご覧ください。

入力ジェスチャーを処理する

他の多くの UI フレームワークと同様に、Android は入力イベントモデルをサポートしています。ユーザー アクションは、コールバックをトリガーするイベントに変わり、コールバックをオーバーライドして、アプリがユーザーにどのように応答するかをカスタマイズできます。Android システムで最も一般的な入力イベントはタップです。これは onTouchEvent(android.view.MotionEvent) をトリガーします。次のように、このメソッドをオーバーライドしてイベントを処理します。

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return super.onTouchEvent(event)
}

Java

@Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

タッチイベント自体は特に有用ではありません。最新のタッチ UI では、タップ、プル、プッシュ、フリング、ズームなどの操作に関するインタラクションが定義されています。未加工のタッチイベントをジェスチャーに変換するために、Android には GestureDetector が用意されています。

GestureDetector.OnGestureListener を実装するクラスのインスタンスを渡すことで、GestureDetector を構築します。少数の操作のみを処理する場合は、GestureDetector.OnGestureListener インターフェースを実装する代わりに GestureDetector.SimpleOnGestureListener を拡張できます。たとえば、次のコードは GestureDetector.SimpleOnGestureListener を拡張し、onDown(MotionEvent) をオーバーライドするクラスを作成します。

Kotlin

private val myListener =  object : GestureDetector.SimpleOnGestureListener() {
    override fun onDown(e: MotionEvent): Boolean {
        return true
    }
}

private val detector: GestureDetector = GestureDetector(context, myListener)

Java

class MyListener extends GestureDetector.SimpleOnGestureListener {
   @Override
   public boolean onDown(MotionEvent e) {
       return true;
   }
}
detector = new GestureDetector(getContext(), new MyListener());

GestureDetector.SimpleOnGestureListener を使用するかどうかにかかわらず、true を返す onDown() メソッドを必ず実装してください。これが必要なのは、すべての操作が onDown() メッセージで始まるためです。onDown() から false を返すと、GestureDetector.SimpleOnGestureListener と同様に、システムは操作の残りの部分を無視すると想定し、GestureDetector.OnGestureListener の他のメソッドは呼び出されません。操作全体を無視する場合にのみ、onDown() から false を返します。

GestureDetector.OnGestureListener を実装して GestureDetector のインスタンスを作成すると、GestureDetector を使用して、onTouchEvent() で受け取るタッチイベントを解釈できます。

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return detector.onTouchEvent(event).let { result ->
        if (!result) {
            if (event.action == MotionEvent.ACTION_UP) {
                stopScrolling()
                true
            } else false
        } else true
    }
}

Java

@Override
public boolean onTouchEvent(MotionEvent event) {
   boolean result = detector.onTouchEvent(event);
   if (!result) {
       if (event.getAction() == MotionEvent.ACTION_UP) {
           stopScrolling();
           result = true;
       }
   }
   return result;
}

操作の一部として認識されないタッチイベントを onTouchEvent() に渡すと、false が返されます。その後、独自のカスタム ジェスチャー検出コードを実行できます。

物理的にふさわしい動きを作り出す

ジェスチャーはタッチスクリーン デバイスを操作する優れた方法ですが、直感に反し、物理的に妥当な結果が得られなければ覚えるのが難しくなる場合があります。

たとえば、ビューに描画されたアイテムを縦軸の周りに回転させる水平フリング操作を実装するとします。この操作は、UI がフリングの方向にすばやく動いて応答し、ユーザーがフライホイールを押して回転したかのように速度を落とす場合に適しています。

スクロール操作をアニメーション化する方法に関するドキュメントでは、独自のスコール動作を実装する方法が詳しく説明されています。しかし、フライホイールの感触をシミュレートすることは容易ではありません。フライホイールモデルを正しく機能させるには 多くの物理学と数学が必要です幸いなことに、Android には、この動作やその他の動作をシミュレートするヘルパークラスが用意されています。Scroller クラスは、フライホイール スタイルのフリング ジェスチャーを処理する基本クラスです。

フリングを開始するには、フリングの開始速度と、フリングの x および y の最小値と最大値を指定して fling() を呼び出します。速度値には、GestureDetector によって計算された値を使用できます。

Kotlin

fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
    scroller.fling(
            currentX,
            currentY,
            (velocityX / SCALE).toInt(),
            (velocityY / SCALE).toInt(),
            minX,
            minY,
            maxX,
            maxY
    )
    postInvalidate()
    return true
}

Java

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
   scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
   postInvalidate();
    return true;
}

fling() を呼び出すと、フリング ジェスチャーの物理モデルが設定されます。その後、定期的に Scroller.computeScrollOffset() を呼び出して Scroller を更新します。computeScrollOffset() は、現在の時刻を読み取り、物理モデルを使用してその時点での xy の位置を計算することで、Scroller オブジェクトの内部状態を更新します。これらの値を取得するには、getCurrX()getCurrY() を呼び出します。

ほとんどのビューは、Scroller オブジェクトの x 位置と y 位置を scrollTo() に直接渡します。この例は少し異なります。現在のスクロールの x 位置を使用して、ビューの回転角度を設定します。

Kotlin

scroller.apply {
    if (!isFinished) {
        computeScrollOffset()
        setItemRotation(currX)
    }
}

Java

if (!scroller.isFinished()) {
    scroller.computeScrollOffset();
    setItemRotation(scroller.getCurrX());
}

Scroller クラスはスクロール位置を計算しますが、その位置をビューに自動的に適用することはありません。スクロール アニメーションが滑らかに見えるように、新しい座標を頻繁に適用します。これには次の 2 つの方法があります。

  • fling() を呼び出した後に postInvalidate() を呼び出して、強制的に再描画します。この方法では、onDraw() でスクロール オフセットを計算し、スクロール オフセットが変更されるたびに postInvalidate() を呼び出す必要があります。
  • ValueAnimator を設定して、フリングの存続期間中にアニメーション化し、addUpdateListener() を呼び出してアニメーションの更新を処理するリスナーを追加します。この手法では、View のプロパティをアニメーション化できます。

画面遷移をスムーズにする

ユーザーは最新の UI が状態間をスムーズに遷移することを期待しています。たとえば、UI 要素が表示 / 非表示になるのではなくフェードイン / フェードアウトする、モーションが突然開始 / 停止するのではなくスムーズに開始 / 終了する、などです。Android のプロパティ アニメーション フレームワークを使用すると、スムーズな遷移を簡単に行えます。

アニメーション システムを使用する場合は、ビューの外観に影響するプロパティが変更されるたびに、プロパティを直接変更しないでください。変更を行うには、代わりに ValueAnimator を使用してください。次の例では、ビュー内で選択された子コンポーネントを変更すると、選択ポインタが中央に配置されるように、レンダリングされたビュー全体が回転します。ValueAnimator は、新しい回転値を直ちに設定するのではなく、数百ミリ秒の期間にわたって回転を変更します。

Kotlin

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply {
    setIntValues(targetAngle)
    duration = AUTOCENTER_ANIM_DURATION
    start()
}

Java

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0);
autoCenterAnimator.setIntValues(targetAngle);
autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
autoCenterAnimator.start();

変更したい値がベース View プロパティの 1 つである場合、ビューには複数のプロパティを同時にアニメーション化するために最適化された組み込み ViewPropertyAnimator があるため、アニメーションの作成はさらに簡単になります。次の例をご覧ください。

Kotlin

animate()
    .rotation(targetAngle)
    .duration = ANIM_DURATION
    .start()

Java

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();