【Android & Kotlin】スクロールを予想させる終端の影にフェードイン・アウトのアニメーションをつけてみた

Android & Kotlin

マインスイーパーを開発していてタイル(マス)を拡大縮小かつスクロールできるようにしているので、終端に到達していない端にスクロールを予想させる影をつけたくなりました。

どうせ影をつけるならフェードイン・アウトのアニメーションをつけてみたので方法を説明します。

そそたた
そそたた

このような誰も気が付かない部分をこだわるの結構好きです。

デザインの格言に「神は細部に宿る」というのがありますしね。

動作イメージ

上下左右にスクロールを予想させる影を250ミリ秒かけてフェードイン・アウトするアニメーションで表示、非表示を切り替えます。

分かりにくいですが左端のイメージを載せておきます。

ソースコード

Viewに対してアニメーションさせるのが簡単なので、影の画像を設定したImageViewを上下左右に4つ配置してfadeAnimationメソッド内でAlphaAnimationを使ってフェードイン・アウトさせています。

開発中のマインスイーパーのソースから関連する部分を抜粋して載せておきます。

そそたた
そそたた

リソースサイズを気にするならAnimatedVectorDrawableを使う方がよさそうです。

試してないので分かりませんが多分できそうです。

開発環境は次の通りです。

PCMacBook Pro(2016年モデル)
IDEAndroid Studio 4.0.1
Android SDKminSdkVersion 21
targetSdkVersion 30
言語Kotlin 1.3.72
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <com.sosotata.minesweeper.ui.widgets.SquareTileGameView
        android:id="@+id/squareView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/topShadow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"
        android:src="@drawable/top_shadow"
        android:visibility="gone"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />
    <ImageView
        android:id="@+id/bottomShadow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"
        android:src="@drawable/bottom_shadow"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        />
    <ImageView
        android:id="@+id/leftShadow"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:src="@drawable/left_shadow"
        android:visibility="gone"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />
    <ImageView
        android:id="@+id/rightShadow"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:src="@drawable/right_shadow"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />
</androidx.constraintlayout.widget.ConstraintLayout>
/**
 * [android.view.View.onDraw]
 */
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    if (::mRenderBitmap.isInitialized) {
        canvas.drawBitmap(mRenderBitmap, mRenderMatrix, null)
    }

    //
    // スクロールを予測させる影をフェードイン・アウトのアニメーションで表示
    //
    val act: Activity? = context as? Activity
    val values = FloatArray(9)
    mRenderMatrix.getValues(values)

    // 左端の影
    if (values[Matrix.MTRANS_X] < -0.5f) {
        fadeAnimation(act?.leftShadow, VISIBLE)
    } else {
        fadeAnimation(act?.leftShadow, GONE)
    }
    // 上端の影
    if (values[Matrix.MTRANS_Y] < -0.5f) {
        fadeAnimation(act?.topShadow, VISIBLE)
    } else {
        fadeAnimation(act?.topShadow, GONE)
    }
    // 右端の影
    if (mTranslateLimit.right - values[Matrix.MTRANS_X] < -0.5f) {
        fadeAnimation(act?.rightShadow, VISIBLE)
    } else {
        fadeAnimation(act?.rightShadow, GONE)
    }
    // 下端の影
    if (mTranslateLimit.bottom - values[Matrix.MTRANS_Y] < -0.5f) {
        fadeAnimation(act?.bottomShadow, VISIBLE)
    } else {
        fadeAnimation(act?.bottomShadow, GONE)
    }
}
private fun fadeAnimation(view: View?, visibility: Int) {
    if (view?.visibility != visibility) {
        val fade: AlphaAnimation = if (visibility == VISIBLE) {
            AlphaAnimation(0.0f, 1.0f)
        } else {
            AlphaAnimation(1.0f, 0.0f)
        }
        fade.duration = 250
        fade.fillAfter = true
        view?.visibility = visibility
        view?.startAnimation(fade)
    }
}

コメント

タイトルとURLをコピーしました