0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Android】Jetpack Compose でスクラッチビューを作成する

Last updated at Posted at 2023-12-22

はじめに

はじめまして、Hridoy Chandra Das(リド)(@ihridoydas)です。

Jetpack Compose で、Google Pay 特典のスクラッチ カードのようなスクラッチ カードを作成します。

スクラッチカードは競技用に設計されたカードで、多くの場合、PIN を隠すために薄いカードストックまたはプラスチックで作られており、1 つまたは複数の領域に隠された情報が含まれており、不透明なカバーを剥がすことで明らかになります。

ユーザーはデバイスの画面上でカードを仮想的に「スクラッチ」「指で擦る」し、その下に隠されたコンテンツを明らかにすることができます。 このコンテンツは、クーポン コード、割引、その他の特典を紹介するためによく使用されます。

s1.jpg

設定:

プロジェクトに使うデペンデンシーライブラリー:

build.gradle.kts
//...Jetpack composeの必要なデペンデンシーライブラリー
 .
 .
implementation "androidx.compose.foundation:foundation:1.5.4"

Compose Component Canvas を使用してviewを作成したい思います。

(Dragged Path) data クラスを作成

DraggedPath.kt
import androidx.compose.ui.graphics.Path

data class DraggedPath(
    val path: Path,
    val width: Float = 50f
)

キャンバスをスクラッチする (ScratchingCanvas)

特別な PointerInput Modifier (pointerInteropFilter) を使用すると、動き (マウス、ペン、指、トラックボール) イベントの報告に使用されるオブジェクトを処理できます。

ScratchingCanvas.kt
@ExperimentalComposeUiApi
@Composable
fun ScratchingCanvas(
    overlayImage: ImageBitmap,
    baseImage: ImageBitmap,
    modifier: Modifier = Modifier,
    movedOffset: Offset?,
    onMovedOffset: (Float, Float) -> Unit,
    currentPath: Path,
    currentPathThickness: Float,
) {
    Canvas(
        modifier = modifier
            .size(220.dp)
            .clipToBounds()
            .clip(RoundedCornerShape(size = 16.dp))
            .pointerInteropFilter {
                when (it.action) {
                    MotionEvent.ACTION_DOWN -> {
                        println("CurrentPath/ACTION_DOWN: (${it.x}, ${it.y})")
                        currentPath.moveTo(it.x, it.y)
                    }

                    MotionEvent.ACTION_MOVE -> {
                        println("MovedOffset/ACTION_MOVE: (${it.x}, ${it.y})")
                        onMovedOffset(it.x, it.y)
                    }
                }
                true
            }
    ) {
        val canvasWidth = size.width.toInt()
        val canvasHeight = size.height.toInt()
        val imageSize = IntSize(width = canvasWidth, height = canvasHeight)

        // Overlay Image to be scratched
        drawImage(
            image = overlayImage,
            dstSize = imageSize
        )

        movedOffset?.let {
            currentPath.addOval(oval = Rect(it, currentPathThickness))
        }

        clipPath(path = currentPath, clipOp = ClipOp.Intersect) {
            // Base Image after scratching
            drawImage(
                image = baseImage,
                dstSize = imageSize
            )
        }
    }
}

Compose Graphics の基礎の詳細

完全な作成ビュー(ScratchCardScreen)

https://gist.github.com/ihridoydas/652fbea7eabe645c60c46844239fc2ac

ScratchCardScreen.kt
@OptIn(ExperimentalMaterial3Api::class)
@ExperimentalComposeUiApi
@Composable
fun ScratchCardScreen(onBackPress: () -> Unit) {
    val overlayImage = ImageBitmap.imageResource(id = R.drawable.bg)
    val baseImage = ImageBitmap.imageResource(id = R.drawable.won)

    val currentPathState = remember { mutableStateOf(DraggedPath(path = Path())) }
    val movedOffsetState = remember { mutableStateOf<Offset?>(null) }

    Scaffold(
        topBar = {
            TopAppBar(
                colors = topAppBarColors(
                    Color.Black
                ),
                title = {
                    Text(
                        text = "Scratch Card Effect",
                        style = TextStyle(color = Color.White)
                    )
                },
                navigationIcon = {
                    IconButton(
                        onClick = {
                            onBackPress()
                        },
                        modifier = Modifier
                    ) {
                        Icon(
                            Icons.Filled.ArrowBack,
                            contentDescription = "Back",
                            tint = Color.White
                        )
                    }
                },
                actions = {
                    IconButton(onClick = {
                        movedOffsetState.value = null
                        currentPathState.value = DraggedPath(path = Path())
                    }) {
                        Icon(
                            imageVector = Icons.Default.Clear,
                            contentDescription = null,
                            tint = Color.White
                        )
                    }
                }
            )
        },
        content = {
            Column(
                modifier = Modifier
                    .padding(it)
                    .fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .background(Color.Black)
                ) {
                    // Scratch Card Implementation
                    ScratchingCanvas(
                        overlayImage = overlayImage,
                        baseImage = baseImage,
                        modifier = Modifier.align(Alignment.Center),
                        movedOffset = movedOffsetState.value,
                        onMovedOffset = { x, y ->
                            movedOffsetState.value = Offset(x, y)
                        },
                        currentPath = currentPathState.value.path,
                        currentPathThickness = currentPathState.value.width,
                    )
                }

            }
        }
    )
}

結果:

GitHub サンプル

次、SwiftUIでスクラッチビューを作成する方法をご紹介したいと思います。

以上、最後までお読みいただきありがとうございました。

scratchCard.gif
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?