๐ŸชJetpack Compose Pager ๋งŒ๋“ค๊ธฐ

: ) YOUNGยท2023๋…„ 9์›” 14์ผ
1

์•ˆ๋“œ๋กœ์ด๋“œ

๋ชฉ๋ก ๋ณด๊ธฐ
15/17

Jetpack Compose์—์„œ pager๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž

์ฝ”์ผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.


    // coil
    implementation("io.coil-kt:coil-compose:2.4.0")

๊ฐ„๋‹จํ•˜๊ฒŒ ํŽ˜์ด์ €์— ๋“ค์–ด๊ฐˆ ์ด๋ฏธ์ง€๋ฅผ list๋กœ ๋งŒ๋“ค์–ด๋†“์ž.


    val images = remember {
        mutableStateListOf(
            "https://images.unsplash.com/photo-1694481348806-0b6de4934812?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1471&q=80",
            "https://images.unsplash.com/photo-1694057442309-bfe467bff9a9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1287&q=80",
            "https://images.unsplash.com/photo-1559803509-40f78353d413?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2564&q=80",
            "https://plus.unsplash.com/premium_photo-1668633086435-a16be494a922?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1287&q=80"
        )
    }

๋‚˜๋Š” unsplash์—์„œ ์ด๋ฏธ์ง€ ์ฃผ์†Œ ๋ณต์‚ฌ๋ฅผ ํ•ด์„œ ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ ๋ฟŒ๋ ค์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฑ„ํƒํ–ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๋ณด์—ฌ์งˆ ์ด๋ฏธ์ง€๋“ค์„ ๋จผ์ € ๋ฆฌ์ŠคํŠธ๋กœ ๋งŒ๋“ค์–ด์ฃผ๊ณ 



๋‹ค์Œ์€ ํŽ˜์ด์ €์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” pagerState๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.
์ด์ œ Pager๊ฐ€ ๋ณด์—ฌ์งˆ ๋ทฐ๋ฅผ Scaffold์— ๊ตฌํ˜„๋งŒ ํ•ด์ฃผ๋ฉด ๋์ด๋‹ค.

์ฝ”๋“œ


@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MainScreen() {
    val images = remember {
        mutableStateListOf(
            "https://images.unsplash.com/photo-1694481348806-0b6de4934812?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1471&q=80",
            "https://images.unsplash.com/photo-1694057442309-bfe467bff9a9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1287&q=80",
            "https://images.unsplash.com/photo-1559803509-40f78353d413?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2564&q=80",
            "https://plus.unsplash.com/premium_photo-1668633086435-a16be494a922?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1287&q=80"
        )
    }

    val pagerState = rememberPagerState()

    Scaffold(modifier = Modifier.padding(vertical = 48.dp)) {
        HorizontalPager(pageCount = images.size, state = pagerState) { idx ->
            AsyncImage(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(16.dp)
                    .clip(RoundedCornerShape(16.dp)),
                model = ImageRequest.Builder(LocalContext.current).data(images[idx])
                    .build(),
                contentDescription = "imagePager",
                contentScale = ContentScale.Crop
            )
        }
    }
} // End of MainScreen()



๊ฐ„๋‹จํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ถ”๊ฐ€

์—ฌ๊ธฐ์„œ ํŽ˜์ด์ €์— ๊ฐ„๋‹จํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž


            val pageOffset = (pagerState.currentPage - idx) + pagerState.currentPageOffsetFraction
            val imageSize by animateFloatAsState(
                targetValue = if (pageOffset != 0.0f) 0.75f else 1f,
                animationSpec = tween(durationMillis = 300)
            )

...


                modifier = Modifier
                    .fillMaxSize()
                    .padding(16.dp)
                    .clip(RoundedCornerShape(16.dp))
                    .graphicsLayer {
                        scaleX = imageSize
                        scaleY = imageSize
                    },

ํ•ด๋‹น ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ํŽ˜์ด์ €์•ˆ์˜ ์ด๋ฏธ์ง€ x๊ฐ’์ด 0.0์ด ์•„๋‹๊ฒฝ์šฐ์— 0.75๋กœ ์ž‘์•„์ง€๋ฉด์„œ, 0.0f ์ผ๋•Œ๋งŒ ์ปค์ง€๊ฒŒ ๋œ๋‹ค.



์ „์ฒด์ฝ”๋“œ


import android.annotation.SuppressLint
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Scaffold
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MainScreen() {
    val images = remember {
        mutableStateListOf(
            "https://images.unsplash.com/photo-1694481348806-0b6de4934812?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1471&q=80",
            "https://images.unsplash.com/photo-1694057442309-bfe467bff9a9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1287&q=80",
            "https://images.unsplash.com/photo-1559803509-40f78353d413?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2564&q=80",
            "https://plus.unsplash.com/premium_photo-1668633086435-a16be494a922?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1287&q=80"
        )
    }

    val pagerState = rememberPagerState()
    val matrix = remember {
        ColorMatrix()
    }

    Scaffold(modifier = Modifier.padding(vertical = 48.dp)) {
        HorizontalPager(
            pageCount = images.size, state = pagerState
        ) { idx ->
            val pageOffset = (pagerState.currentPage - idx) + pagerState.currentPageOffsetFraction
            val imageSize by animateFloatAsState(
                targetValue = if (pageOffset != 0.0f) 0.75f else 1f,
                animationSpec = tween(durationMillis = 300)
            )

            LaunchedEffect(key1 = imageSize) {
                if (pageOffset != 0.0f) {
                    matrix.setToSaturation(0.0f)
                } else {
                    matrix.setToSaturation(1f)
                }
            }

            AsyncImage(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(16.dp)
                    .clip(RoundedCornerShape(16.dp))
                    .graphicsLayer {
                        scaleX = imageSize
                        scaleY = imageSize
                    },
                model = ImageRequest.Builder(LocalContext.current).data(images[idx]).build(),
                contentDescription = "imagePager",
                contentScale = ContentScale.Crop,
                colorFilter = ColorFilter.colorMatrix(matrix)
            )
        }
    }
} // End of MainScreen()

0๊ฐœ์˜ ๋Œ“๊ธ€