[英]Android - Horizontally (right-to-left) circular background color transition
I want to know how can I implement this animation?我想知道如何实现这个动画?
Small image frame and text movement is independent from the animation.小图像框架和文本移动独立于动画。 It's just like simple pager action with some scale and alpha transformation.
这就像简单的寻呼机动作,带有一些比例和 alpha 转换。 Only problem is the color change of background like this.
唯一的问题是这样的背景颜色变化。
I'm open to both XML and Jetpack Compose way solutions.我对 XML 和 Jetpack Compose 方式的解决方案持开放态度。 Please..
请..
My Solution我的解决方案
import android.graphics.Path
import android.view.MotionEvent
import androidx.annotation.FloatRange
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.asComposePath
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import kotlin.math.hypot
@Composable
fun <T> CircularReveal(
targetState: T,
modifier: Modifier = Modifier,
animationSpec: FiniteAnimationSpec<Float> = tween(),
content: @Composable (T) -> Unit,
) {
val transition = updateTransition(targetState, label = "Circular reveal")
transition.CircularReveal(modifier, animationSpec, content = content)
}
@Composable
fun <T> Transition<T>.CircularReveal(
modifier: Modifier = Modifier,
animationSpec: FiniteAnimationSpec<Float> = tween(),
content: @Composable (targetState: T) -> Unit,
) {
var offset: Offset? by remember { mutableStateOf(null) }
val currentlyVisible = remember { mutableStateListOf<T>().apply { add(currentState) } }
val contentMap = remember {
mutableMapOf<T, @Composable () -> Unit>()
}
if (currentState == targetState) {
// If not animating, just display the current state
if (currentlyVisible.size != 1 || currentlyVisible[0] != targetState) {
// Remove all the intermediate items from the list once the animation is finished.
currentlyVisible.removeAll { it != targetState }
contentMap.clear()
}
}
if (!contentMap.contains(targetState)) {
// Replace target with the same key if any
val replacementId = currentlyVisible.indexOfFirst {
it == targetState
}
if (replacementId == -1) {
currentlyVisible.add(targetState)
} else {
currentlyVisible[replacementId] = targetState
}
contentMap.clear()
currentlyVisible.forEach { stateForContent ->
contentMap[stateForContent] = {
val progress by animateFloat(
label = "Progress",
transitionSpec = { animationSpec }
) {
val targetedContent = stateForContent != currentlyVisible.last() || it == stateForContent
if (targetedContent) 1f else 0f
}
Box(Modifier.circularReveal(progress = progress, offset = offset)) {
content(stateForContent)
}
}
}
}
Box(
modifier = modifier.pointerInteropFilter {
if (it.action == MotionEvent.ACTION_DOWN) {
if (!started) offset = Offset(it.x, it.y)
}
started
}
) {
currentlyVisible.forEach {
key(it) {
contentMap[it]?.invoke()
}
}
}
}
private val <T> Transition<T>.started get() =
currentState != targetState || isRunning
fun Modifier.circularReveal(
@FloatRange(from = 0.0, to = 1.0) progress: Float,
offset: Offset? = null,
) = clip(CircularRevealShape(progress, offset))
class CircularRevealShape(
@FloatRange(from = 0.0, to = 1.0) private val progress: Float,
private val offset: Offset? = null,
) : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density,
): Outline {
return Outline.Generic(Path().apply {
addCircle(
offset?.x ?: (size.width / 2f),
offset?.y ?: (size.height / 2f),
longestDistanceToACorner(size, offset) * progress,
Path.Direction.CW
)
}.asComposePath())
}
private fun longestDistanceToACorner(size: Size, offset: Offset?): Float {
if (offset == null) {
return hypot(size.width / 2f, size.height / 2f)
}
val topLeft = hypot(offset.x, offset.y)
val topRight = hypot(size.width - offset.x, offset.y)
val bottomLeft = hypot(offset.x, size.height - offset.y)
val bottomRight = hypot(size.width - offset.x, size.height - offset.y)
return topLeft.coerceAtLeast(topRight).coerceAtLeast(bottomLeft).coerceAtLeast(bottomRight)
}
}
@Preview
@Composable
fun CircularRevealAnimationPreview() {
val isSystemDark = isSystemInDarkTheme()
var darkTheme by remember { mutableStateOf(isSystemDark) }
val onThemeToggle = { darkTheme = !darkTheme }
CircularReveal(
targetState = darkTheme,
animationSpec = tween(1500)
) { isDark ->
MyAppTheme(darkTheme = isDark) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background,
onClick = onThemeToggle
) {
Box(
contentAlignment = Alignment.Center
) {
Icon(
modifier = Modifier.size(120.dp),
imageVector = if (isDark) Icons.Default.DarkMode else Icons.Default.LightMode,
contentDescription = "Toggle",
)
}
}
}
}
}
The solution解决方案
After lots of hours searching, I've found the perfect one;经过数小时的搜索,我找到了完美的;
https://github.com/2307vivek/BubblePager https://github.com/2307vivek/BubblePager
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.