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()