[Android] 038. 画像のピンチイン/アウト

よくある画像のピンチイン/アウトの実装はJetpack Composeならそれほど頑張らなくてもデベロッパーの説明でほぼ実装できます

デベロッパーの以下の説明です
https://developer.android.com/jetpack/compose/gestures?hl=ja#multitouch

アニメーションについては以下で確認
https://developer.android.com/jetpack/compose/animation/quick-guide?authuser=1

ここでは回転の処理を省いて拡大の制限とタブルタップで拡大縮小処理をアニメーションする実装を追加してます
ドラッグ移動はscaleを配慮した値に補正してます
また画像はローカルに持ってくるの面倒だったのでCoilでWeb上のものを参照して使用してます
背景色は確認時にわかりやすくするためつけてますが実際は透明で良いと思います

動作確認をエミュレータでする場合でピンチイン/アウトはCtrlキー押しながらマウス操作です

@Composable
fun TransformableImage() {
    val scaleMax = 5.0f
    var scale by remember { mutableStateOf(1.0f) }
    var offset by remember { mutableStateOf(Offset.Zero) }
    val state = rememberTransformableState { zoomChange, offsetChange, _ ->
        val scale_ = scale * zoomChange
        if (scale_ >= 1.0f && scale_ <= scaleMax) scale = scale_
        offset += Offset(offsetChange.x * scale, offsetChange.y * scale)
    }

    var animating by remember { mutableStateOf(false) }
    val zoom by animateFloatAsState(
        label = "",
        targetValue = if (scale <= scaleMax / 2) {
            if (animating) scaleMax else scale
        } else {
            if (animating) 1.0f else scale
        },
        animationSpec = tween(durationMillis = if (animating) 300 else 0),
        finishedListener = {
            if (animating) {
                animating = false
                scale = it
            }
        }
    )

    Box(
        Modifier
            .pointerInput(Unit) {
                detectTapGestures(
                    onPress = { /* Called when the gesture starts */ },
                    onDoubleTap = {
                        animating = true
                        offset = Offset.Zero
                    },
                    onLongPress = { /* Called on Long Press */ },
                    onTap = { /* Called on Tap */ }
                )
            }
            .graphicsLayer(
                scaleX = if (animating) zoom else scale,
                scaleY = if (animating) zoom else scale,
                translationX = offset.x,
                translationY = offset.y
            )
            .transformable(state = state)
            .background(Color.Blue)
            .fillMaxSize()
    ) {
        // <uses-permission android:name="android.permission.INTERNET" />
        val url = "https://..."
        Image(
            painter = rememberAsyncImagePainter(url), // io.coil-kt:coil-compose
            contentDescription = null,
            modifier = Modifier.fillMaxSize(),
            contentScale = ContentScale.FillWidth
        )
    }
}

サムネ画像をクリックで表示する場合のアニメーションはGoogleのFileみたいにscalIn/OutよりslideIn/Outの方がいいかも
画面遷移はnavigationよりBox上に重ねて表示/非表示の方が実装が楽です

←にslideInしたい場合はwidth -> widthを指定
※ →は初期値なので未指定かwidth -> -width
slideOutの初期値は←で途中で止まってしまうのでwidth -> -widthを実装
今回は←にslideInしたので→にslideOut

var visible by remember { mutableStateOf(false) }
AnimatedVisibility(
    visible = visible,
    enter = slideInHorizontally(tween(50)) { width -> width },
    exit = slideOutHorizontally(tween(50)) { width -> width },
) {
...
}


Android Studio Giraffe 2022.3.1 built on June 29, 2023