Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Kyriakos-Georgiopoulos/024fc8e281825729cee55feeb07bf774 to your computer and use it in GitHub Desktop.
Save Kyriakos-Georgiopoulos/024fc8e281825729cee55feeb07bf774 to your computer and use it in GitHub Desktop.
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
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.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.zengrip.R
import kotlin.math.hypot
import kotlin.math.max
@Composable
fun CircularImageRevealScreen() {
val imageResIds = listOf(
R.drawable.futuristic_image_1,
R.drawable.futuristic_image_2,
R.drawable.futuristic_image_3,
R.drawable.futuristic_image_4
)
var screenSize by remember { mutableStateOf(IntSize.Zero) }
var currentImage by remember { mutableIntStateOf(imageResIds.random()) }
var nextImage by remember { mutableStateOf<Int?>(null) }
val nextBitmap: ImageBitmap? = nextImage?.let { ImageBitmap.imageResource(it) }
val isRevealing = nextImage != null
val progress by animateFloatAsState(
targetValue = if (isRevealing) 1f else 0f,
animationSpec = tween(400, easing = FastOutLinearInEasing),
label = "RevealProgress",
finishedListener = {
nextImage?.let {
currentImage = it
nextImage = null
}
}
)
val density = LocalDensity.current
val fabSize = with(density) { 56.dp.toPx() }
val fabPadding = with(density) { 16.dp.toPx() }
val fabCenter = Offset(
x = screenSize.width - fabSize / 2f - fabPadding,
y = screenSize.height - fabSize / 2f - fabPadding
)
Box(
modifier = Modifier
.fillMaxSize()
.onGloballyPositioned { screenSize = it.size }
) {
// Background image
Image(
painter = painterResource(id = currentImage),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
// Reveal animation layer
if (isRevealing && nextBitmap != null) {
Canvas(modifier = Modifier.fillMaxSize()) {
val radius = progress * hypot(size.width, size.height)
val revealPath = Path().apply {
addOval(Rect(center = fabCenter, radius = radius))
}
clipPath(revealPath) {
val scale = max(
size.width / nextBitmap.width.toFloat(),
size.height / nextBitmap.height.toFloat()
)
val dstSize = IntSize(
(nextBitmap.width * scale).toInt(),
(nextBitmap.height * scale).toInt()
)
val dstOffset = IntOffset(
x = ((size.width - dstSize.width) / 2f).toInt(),
y = ((size.height - dstSize.height) / 2f).toInt()
)
drawImage(
image = nextBitmap,
dstSize = dstSize,
dstOffset = dstOffset
)
}
}
}
// Floating Action Button
FloatingActionButton(
onClick = {
if (!isRevealing) {
var newImage = imageResIds.random()
while (newImage == currentImage) {
newImage = imageResIds.random()
}
nextImage = newImage
}
},
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 22.dp, bottom = 50.dp),
shape = CircleShape,
containerColor = Color(0xFFFF2D95)
) {
Icon(Icons.Default.PlayArrow, contentDescription = "Reveal Image")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment