-
-
Save sinasamaki/a07304c1081bf615d2496945acc934cd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.graphics.RenderEffect | |
import android.graphics.Shader | |
import androidx.compose.animation.core.Spring | |
import androidx.compose.animation.core.animateFloatAsState | |
import androidx.compose.animation.core.spring | |
import androidx.compose.foundation.ExperimentalFoundationApi | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.layout.Arrangement | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.Row | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.layout.fillMaxWidth | |
import androidx.compose.foundation.layout.height | |
import androidx.compose.foundation.layout.padding | |
import androidx.compose.foundation.pager.HorizontalPager | |
import androidx.compose.foundation.pager.VerticalPager | |
import androidx.compose.foundation.pager.rememberPagerState | |
import androidx.compose.foundation.shape.RoundedCornerShape | |
import androidx.compose.material.Icon | |
import androidx.compose.material.MaterialTheme | |
import androidx.compose.material.Text | |
import androidx.compose.material.icons.Icons | |
import androidx.compose.material.icons.rounded.Star | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.derivedStateOf | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.snapshotFlow | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.alpha | |
import androidx.compose.ui.draw.clip | |
import androidx.compose.ui.draw.scale | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.graphics.asComposeRenderEffect | |
import androidx.compose.ui.graphics.graphicsLayer | |
import androidx.compose.ui.graphics.vector.rememberVectorPainter | |
import androidx.compose.ui.layout.ContentScale | |
import androidx.compose.ui.text.font.FontWeight | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | |
import androidx.compose.ui.zIndex | |
import coil.compose.AsyncImage | |
import kotlin.math.ceil | |
import kotlin.math.floor | |
import kotlin.math.roundToInt | |
@Composable | |
fun MoviePager() { | |
val horizontalState = rememberPagerState(initialPage = 0) | |
Column { | |
HorizontalPager( | |
pageCount = movies.size, | |
modifier = Modifier | |
.weight(.7f) | |
.padding( | |
top = 32.dp | |
), | |
state = horizontalState, | |
pageSpacing = 1.dp, | |
beyondBoundsPageCount = 9, | |
) { page -> | |
Box( | |
modifier = Modifier | |
.zIndex(page * 10f) | |
.padding( | |
start = 64.dp, | |
end = 32.dp, | |
) | |
.graphicsLayer { | |
val startOffset = horizontalState.startOffsetForPage(page) | |
translationX = size.width * (startOffset * .99f) | |
alpha = (2f - startOffset) / 2f | |
val blur = (startOffset * 20f).coerceAtLeast(0.1f) | |
renderEffect = RenderEffect | |
.createBlurEffect( | |
blur, blur, Shader.TileMode.DECAL | |
) | |
.asComposeRenderEffect() | |
val scale = 1f - (startOffset * .1f) | |
scaleX = scale | |
scaleY = scale | |
} | |
.clip(RoundedCornerShape(20.dp)) | |
.background( | |
color = Color(0xFFF58133), | |
shape = RoundedCornerShape(20.dp) | |
), | |
contentAlignment = Alignment.Center, | |
) { | |
AsyncImage( | |
model = movies[page].img, | |
contentDescription = null, | |
contentScale = ContentScale.Crop, | |
modifier = Modifier.fillMaxSize() | |
) | |
} | |
} | |
Row( | |
modifier = Modifier | |
.padding(horizontal = 16.dp) | |
.fillMaxWidth() | |
.weight(.3f), | |
horizontalArrangement = Arrangement.SpaceBetween, | |
verticalAlignment = Alignment.CenterVertically, | |
) { | |
val verticalState = rememberPagerState() | |
VerticalPager( | |
pageCount = movies.size, | |
state = verticalState, | |
modifier = Modifier | |
.weight(1f) | |
.height(72.dp), | |
userScrollEnabled = false, | |
horizontalAlignment = Alignment.Start, | |
) { page -> | |
Column( | |
verticalArrangement = Arrangement.Center, | |
) { | |
Text( | |
text = movies[page].title, | |
style = MaterialTheme.typography.h1.copy( | |
fontWeight = FontWeight.Thin, | |
fontSize = 28.sp, | |
) | |
) | |
Text( | |
text = movies[page].subtitle, | |
style = MaterialTheme.typography.body1.copy( | |
fontWeight = FontWeight.Bold, | |
fontSize = 14.sp, | |
color = Color.Black.copy(alpha = .56f), | |
) | |
) | |
} | |
} | |
LaunchedEffect(Unit) { | |
snapshotFlow { | |
Pair( | |
horizontalState.currentPage, | |
horizontalState.currentPageOffsetFraction | |
) | |
}.collect { (page, offset) -> | |
verticalState.scrollToPage(page, offset) | |
} | |
} | |
val interpolatedRating by remember { | |
derivedStateOf { | |
val position = horizontalState.offsetForPage(0) | |
val from = floor(position).roundToInt() | |
val to = ceil(position).roundToInt() | |
val fromRating = movies[from].rating.toFloat() | |
val toRating = movies[to].rating.toFloat() | |
val fraction = position - position.toInt() | |
fromRating + ((toRating - fromRating) * fraction) | |
} | |
} | |
RatingStars(rating = interpolatedRating) | |
} | |
} | |
} | |
@Composable | |
fun RatingStars( | |
modifier: Modifier = Modifier, | |
rating: Float, | |
) { | |
Row( | |
modifier = modifier | |
) { | |
for (i in 1..5) { | |
val animatedScale by animateFloatAsState( | |
targetValue = if (floor(rating) >= i) { | |
1f | |
} else if (ceil(rating) < i) { | |
0f | |
} else { | |
rating - rating.toInt() | |
}, | |
animationSpec = spring( | |
stiffness = Spring.StiffnessMedium | |
), | |
label = "" | |
) | |
Box( | |
contentAlignment = Alignment.Center, | |
) { | |
Icon( | |
painter = rememberVectorPainter(image = Icons.Rounded.Star), | |
contentDescription = null, | |
modifier = Modifier.alpha(.1f), | |
) | |
Icon( | |
painter = rememberVectorPainter(image = Icons.Rounded.Star), | |
contentDescription = null, | |
modifier = Modifier.scale(animatedScale), | |
tint = Color(0xFFD59411) | |
) | |
} | |
} | |
} | |
} | |
data class Movie( | |
val title: String = "Avengers", | |
val subtitle: String = "", | |
val rating: Int = 4, | |
val img: String = "", | |
) | |
val movies = listOf( | |
Movie( | |
title = "Moonlight", | |
subtitle = "Barry Jenkins • 2016", | |
rating = 4, | |
img = "https://www.themoviedb.org/t/p/w1280/4911T5FbJ9eD2Faz5Z8cT3SUhU3.jpg", | |
), | |
Movie( | |
title = "Little Miss Sunshine", | |
subtitle = "Dayton & Faris • 2006", | |
rating = 5, | |
img = "https://www.themoviedb.org/t/p/w1280/tFnTds88mCuLcLPBseK1kF2E3qv.jpg", | |
), | |
Movie( | |
title = "The Lobster", | |
subtitle = "Yorgos Lanthimos • 2015", | |
rating = 2, | |
img = "https://www.themoviedb.org/t/p/w1280/7Y9ILV1unpW9mLpGcqyGQU72LUy.jpg", | |
), | |
Movie( | |
title = "Her", | |
subtitle = "Spike Jonze • 2013", | |
rating = 4, | |
img = "https://www.themoviedb.org/t/p/w1280/eCOtqtfvn7mxGl6nfmq4b1exJRc.jpg", | |
), | |
Movie( | |
title = "Memento", | |
subtitle = "Christopher Nolan • 2000", | |
rating = 3, | |
img = "https://www.themoviedb.org/t/p/w1280/yuNs09hvpHVU1cBTCAk9zxsL2oW.jpg", | |
), | |
Movie( | |
title = "The Room", | |
subtitle = "Tommy Wiseau • 2003", | |
rating = 1, | |
img = "https://www.themoviedb.org/t/p/w1280/9QscHN4pXj6Ja1k7e1ZT4vWDGnr.jpg", | |
), | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment