Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save iniyanmurugavel/a794d8b2a9cf113bc6ae2091f0d6d509 to your computer and use it in GitHub Desktop.
Save iniyanmurugavel/a794d8b2a9cf113bc6ae2091f0d6d509 to your computer and use it in GitHub Desktop.
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Canvas
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.Spacer
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.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.zengrip.R
private const val DiagonalOffset = 500f
@Composable
fun DiagonalPager() {
val pageCount = 3
val pagerState = rememberPagerState(initialPage = 0) { pageCount }
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
val density = LocalDensity.current
val widthPx = with(density) { screenWidth.toPx() }
val heightPx = with(density) { screenHeight.toPx() }
val currentPage = pagerState.currentPage
val progress = pagerState.currentPageOffsetFraction.coerceIn(-1f, 1f)
Surface(modifier = Modifier.fillMaxSize(), color = Color.Black) {
Box(modifier = Modifier.fillMaxSize()) {
BackgroundGradient(currentPage, progress)
DiagonalPagerImages(pagerState, widthPx, heightPx)
PagerOverlay(currentPage, pageCount)
}
}
}
@Composable
private fun BackgroundGradient(currentPage: Int, progress: Float) {
Canvas(modifier = Modifier.fillMaxSize()) {
val (startTop, startBottom) = gradientForPage(currentPage)
val (endTop, endBottom) = gradientForPage(currentPage + 1)
val top = lerp(startTop, endTop, progress)
val mid = lerp(startTop, endBottom, progress)
val bottom = lerp(startBottom, endBottom, progress)
drawRect(
brush = Brush.verticalGradient(listOf(top, mid, bottom)),
size = size
)
drawRect(
brush = Brush.radialGradient(
colors = listOf(Color.White.copy(alpha = 0.05f), Color.Transparent),
center = Offset(size.width / 2f, size.height * 0.85f),
radius = size.minDimension * 0.8f
),
size = size
)
}
}
@Composable
private fun DiagonalPagerImages(
pagerState: androidx.compose.foundation.pager.PagerState,
width: Float,
height: Float
) {
val currentPage = pagerState.currentPage
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize(),
beyondViewportPageCount = 1,
) { page ->
val offsetX = when (page) {
currentPage -> pagerState.currentPageOffsetFraction * width
currentPage - 1 -> (pagerState.currentPageOffsetFraction + 1f) * width
currentPage + 1 -> (pagerState.currentPageOffsetFraction - 1f) * width
else -> return@HorizontalPager
}
DiagonalImage(
imageRes = imageForPage(page),
offset = Offset(offsetX, 0f),
width = width,
height = height
)
}
}
@Composable
private fun PagerOverlay(currentPage: Int, pageCount: Int) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 24.dp, vertical = 80.dp),
verticalArrangement = Arrangement.Bottom,
) {
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom
) {
PagerIndicator(
pageCount = pageCount,
currentPage = currentPage
)
Column(horizontalAlignment = Alignment.End) {
FadingText("Title for page $currentPage", 24.sp)
Spacer(modifier = Modifier.height(4.dp))
FadingText("Some dummy text here.", 16.sp)
}
}
}
}
@Composable
private fun DiagonalImage(
imageRes: Int,
offset: Offset,
width: Float,
height: Float
) {
val imageBitmap = ImageBitmap.imageResource(id = imageRes)
Canvas(
modifier = Modifier
.fillMaxSize()
.clipToBounds()
) {
val midY = height / 1.1f
val path = Path().apply {
moveTo(0f, 0f)
lineTo(width, 0f)
lineTo(width, midY - DiagonalOffset)
lineTo(0f, midY)
close()
}
val aspectRatio = imageBitmap.width.toFloat() / imageBitmap.height
val canvasAspect = size.width / size.height
val scaledWidth: Float
val scaledHeight: Float
if (aspectRatio > canvasAspect) {
scaledHeight = size.height
scaledWidth = scaledHeight * aspectRatio
} else {
scaledWidth = size.width
scaledHeight = scaledWidth / aspectRatio
}
val left = (size.width - scaledWidth) / 2f
translate(offset.x, offset.y) {
clipPath(path) {
drawImage(
image = imageBitmap,
dstSize = IntSize(scaledWidth.toInt(), scaledHeight.toInt()),
dstOffset = IntOffset(left.toInt(), 0)
)
}
}
}
}
@Composable
private fun PagerIndicator(
pageCount: Int,
currentPage: Int,
activeColor: Color = Color.White,
inactiveColor: Color = Color.White.copy(alpha = 0.3f),
activeSize: Dp = 12.dp,
inactiveSize: Dp = 8.dp,
spacing: Dp = 8.dp
) {
Row(
horizontalArrangement = Arrangement.spacedBy(spacing),
verticalAlignment = Alignment.CenterVertically
) {
repeat(pageCount) { index ->
val isSelected = index == currentPage
val dotSize by animateDpAsState(
targetValue = if (isSelected) activeSize else inactiveSize,
label = "dotSize"
)
val dotColor by animateColorAsState(
targetValue = if (isSelected) activeColor else inactiveColor,
label = "dotColor"
)
Box(
modifier = Modifier
.size(dotSize)
.clip(CircleShape)
.background(dotColor)
)
}
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun FadingText(text: String, fontSize: TextUnit) {
AnimatedContent(
targetState = text,
transitionSpec = { fadeIn() togetherWith fadeOut() },
label = "fadingText"
) {
Text(text = it, fontSize = fontSize, color = Color.White)
}
}
@Composable
private fun imageForPage(page: Int): Int = when (page % 3) {
0 -> R.drawable.lion
1 -> R.drawable.elephant
else -> R.drawable.tiger
}
private fun gradientForPage(page: Int): Pair<Color, Color> = when (page % 3) {
0 -> Color(0xFF4A4A4A) to Color(0xFF2F2F2F)
1 -> Color(0xFF5A5A5A) to Color(0xFF3A3A3A)
else -> Color(0xFF3C3C3C) to Color(0xFF252525)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment