How can I call this only once?
#OptIn(ExperimentalPagerApi::class)
#Composable
fun TutorialPager(
tutorials: List<TutorialViewModel.TutorialUiResources>,
state: PagerState,
modifier: Modifier = Modifier
) {
HorizontalPager(
count = tutorials.size,
state = state,
modifier = modifier.fillMaxSize(),
userScrollEnabled = false
) {
TutorialPage(state.currentPage, pagerScope = this, tutorial = tutorials[currentPage])
}
}
And also TutorialPage is called many times. I just want to call it just once when it's visible.
#OptIn(ExperimentalPagerApi::class)
#Composable
fun TutorialPage(
currentPage: Int,
pagerScope: PagerScope,
tutorial: TutorialViewModel.TutorialUiResources,
modifier: Modifier = Modifier
) {
val isShow = currentPage == pagerScope.currentPage
val pxValue = LocalDensity.current.run { 60.dp.toPx() }
if(isShow){
Box(modifier = modifier.fillMaxSize()) {
AnimatedVisibility(
visibleState = MutableTransitionState(false).apply { targetState = isShow },
enter = pageItemSlideInTransition(pxValue.toInt(), 100)
) {
Log.d("aos", "World: page:${pagerScope.currentPage}, ${tutorial.lottie}")
// Lottie(
// currentPage == pagerScope.currentPage,
// tutorial.lottie,
// modifier = Modifier
// .align(Alignment.TopStart)
// .size(220.dp),
// speed = 0.3f
// )
}
Column(
modifier = Modifier
.align(Alignment.BottomStart)
) {
AnimatedVisibility(
visibleState = MutableTransitionState(false).apply { targetState = isShow },
enter = pageItemSlideInTransition(pxValue.toInt(), 250)
) {
Text(
text = stringResource(id = tutorial.titleRes),
fontFamily = HanSansFamily,
style = MaterialTheme.typography.headlineLarge,
color = Color.Black
)
}
AnimatedVisibility(
visibleState = MutableTransitionState(false).apply { targetState = isShow },
enter = pageItemSlideInTransition(pxValue.toInt(), 400)
) {
Spacer(
modifier = Modifier
.padding(vertical = 20.dp)
.size(width = 28.dp, height = 4.dp)
.background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(100))
)
}
AnimatedVisibility(
visibleState = MutableTransitionState(false).apply { targetState = isShow },
enter = pageItemSlideInTransition(pxValue.toInt(), 550)
) {
Text(
text = stringResource(id = tutorial.subTitleRes),
style = MaterialTheme.typography.bodyLarge,
lineHeight = 22.4.sp,
color = Color.Gray05
)
}
}
}
}
}
Those composable functions are called many times, and it has Lottie files. So, I guess they are loaded many times. And it cause 'OutOfMemory' Erorr.
Here's my log when the app is launched.
17:30:56.215 aos D Hello!
17:30:56.263 aos D ===================
17:30:56.263 aos D Hello: page:0, 2131886082
17:30:56.268 aos D World: page:0, 2131886082
17:30:56.303 aos D ===================
17:30:56.303 aos D Hello: page:0, 2131886082
17:30:56.305 aos D World: page:0, 2131886082
17:30:56.368 aos D Hello!
17:30:56.392 aos D World: page:0, 2131886082
17:30:56.394 aos D World: page:0, 2131886082
17:30:56.771 aos D World: page:0, 2131886082
17:30:56.773 aos D World: page:0, 2131886082
17:30:56.789 aos D World: page:0, 2131886082
17:30:56.791 aos D World: page:0, 2131886082
Hello! >> TutorialPager()
===== >> TutorialPage()
And this is when the next page is visible after swiping.
17:36:43.077 aos D ===================
17:36:43.077 aos D Hello: page:1, 2131886083
17:36:43.079 aos D World: page:1, 2131886083
17:36:43.085 aos D ===================
17:36:43.086 aos D Hello: page:1, 2131886083
17:36:43.088 aos D World: page:1, 2131886083
17:36:43.093 aos D ===================
17:36:43.093 aos D Hello: page:1, 2131886083
17:36:43.095 aos D World: page:1, 2131886083
17:36:43.140 aos D World: page:1, 2131886083
17:36:43.143 aos D World: page:1, 2131886083
17:36:43.150 aos D World: page:1, 2131886083
17:36:43.283 aos D ===================
17:36:43.283 aos D Hello: page:1, 2131886083
17:36:43.284 aos D World: page:1, 2131886083
17:36:43.305 aos D World: page:1, 2131886083
17:36:43.554 aos D World: page:1, 2131886083
17:36:43.568 aos D World: page:1, 2131886083
17:36:43.669 aos D World: page:1, 2131886083
17:36:43.684 aos D World: page:1, 2131886083
17:36:43.704 aos D World: page:1, 2131886083
17:36:43.718 aos D World: page:1, 2131886083
This is Lottie function.
#Composable
fun Lottie(play: Boolean, #RawRes lottie: Int, modifier: Modifier = Modifier, speed: Float = 1f, iterations: Int = LottieConstants.IterateForever) {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(lottie))
val lottieAnimatable = rememberLottieAnimatable()
LaunchedEffect(composition) {
lottieAnimatable.animate(
composition = composition,
clipSpec = LottieClipSpec.Frame(0, 1200),
initialProgress = 0f
)
}
val progress by animateLottieCompositionAsState(
composition,
isPlaying = play,
speed = speed,
iterations = iterations
)
LottieAnimation(
composition = composition,
progress = { progress },
modifier = modifier
)
}
Use launch effect block. That should do the trick
Related
I'm new to Jetpack Compose, and I'm trying to rotate the home screen with animation when the menu button is tapped. It works fine 3-5 times, but suddenly it lags like crazy and I don't know why? Is this a bug, or am I doing something wrong?
var isOpen by remember { mutableStateOf(false) }
val transition = updateTransition(targetState = isOpen, "Menu")
val rotation by transition.animateFloat(
transitionSpec = { spring(0.4f, Spring.StiffnessLow) },
label = "MenuRotation",
targetValueByState = { if (it) -30f else 0f }
)
val scale by transition.animateFloat(
label = "MenuScale",
targetValueByState = { if (it) 0.9f else 1f }
)
val translateX by transition.animateFloat(
transitionSpec = { tween(400) },
label = "MenuTranslation",
targetValueByState = { if (it) 536f else 0f }
)
Box(
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
cameraDistance = density * 10f
rotationY = rotation
scaleX = scale
translationX = translateX
}
) {
HomeScreen()
}
Box {
DefaultButton(
onClick = { isOpen = ! isOpen },
modifier = Modifier
.padding(16.dp)
.padding(top = 32.dp)
.shadow(blur = 8.dp, radius = 16.dp)
.size(32.dp, 32.dp),
shape = Shapes.large,
) {
Icon(
imageVector = if (isOpen) Icons.Filled.Close else Icons.Filled.Menu,
contentDescription = "Menu",
tint = Color.Black,
)
}
}
Update #1
I found this in the logcat
Skipped 52 frames! The application may be doing too much work on its main thread.
Davey! duration=1067ms; Flags=0, FrameTimelineVsyncId=2511155, IntendedVsync=4294436921331, Vsync=4294870254647, InputEventId=0, HandleInputStart=4294871069349, AnimationStart=4294871070287, PerformTraversalsStart=4294871939089, DrawStart=4294872039558, FrameDeadline=4294486921330, FrameInterval=4294870971954, FrameStartTime=41666666, SyncQueued=4294872645860, SyncStart=4295312217578, IssueDrawCommandsStart=4295312304089, SwapBuffers=4295937520703, FrameCompleted=4295944298047, DequeueBufferDuration=5729, QueueBufferDuration=166719, GpuCompleted=4295944298047, SwapBuffersCompleted=4295937862943, DisplayPresentTime=4237530536663, CommandSubmissionCompleted=4295937520703,
Update #2
The animation works flawlessly when I comment out all text components and vice versa. So what's wrong with the text components?
Please check the HomeScreen composable, component recomposition counts。
I suspect that HomeScreen reorganizes too many times。
You can just replace #Composable HomeScreen with an #Composeable Image verify。
I need set Diagonal gradient on a rectangle as background.
I have two colors (Yellow and Green) which needs to be painted as:
Green from top left to bottom right and Yellow from bottom right to topleft.
I see only linearGradient, horizontalGradient and verticalGradient on Brush in Modifier.
But I'm not able to generate the required angle.
Brush.linearGradient() is diagonal by default with default angle 45 degrees. You can change angle of gradient by changing start and values. Rotating by 45 is easy.
I add a demonstration. You can pick start and end values for any angle from GradientOffset function and paste it to Brush.linearGradient
/**
* Offset for [Brush.linearGradient] to rotate gradient depending on [start] and [end] offsets.
*/
data class GradientOffset(val start: Offset, val end: Offset)
enum class GradientAngle {
CW0, CW45, CW90, CW135, CW180, CW225, CW270, CW315
}
fun GradientOffset(angle: GradientAngle = GradientAngle.CW0): GradientOffset {
return when (angle) {
GradientAngle.CW45 -> GradientOffset(
start = Offset.Zero,
end = Offset.Infinite
)
GradientAngle.CW90 -> GradientOffset(
start = Offset.Zero,
end = Offset(0f, Float.POSITIVE_INFINITY)
)
GradientAngle.CW135 -> GradientOffset(
start = Offset(Float.POSITIVE_INFINITY, 0f),
end = Offset(0f, Float.POSITIVE_INFINITY)
)
GradientAngle.CW180 -> GradientOffset(
start = Offset(Float.POSITIVE_INFINITY, 0f),
end = Offset.Zero,
)
GradientAngle.CW225 -> GradientOffset(
start = Offset.Infinite,
end = Offset.Zero
)
GradientAngle.CW270 -> GradientOffset(
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset.Zero
)
GradientAngle.CW315 -> GradientOffset(
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset(Float.POSITIVE_INFINITY, 0f)
)
else -> GradientOffset(
start = Offset.Zero,
end = Offset(Float.POSITIVE_INFINITY, 0f)
)
}
}
Demonstration of how gradients are formed based on start and end values
#Composable
private fun RotatableGradientSample() {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Offsets for gradients based on selected angle
var gradientOffset by remember {
mutableStateOf(GradientOffset(GradientAngle.CW45))
}
var angleSelection by remember { mutableStateOf(0f) }
var angleText by remember { mutableStateOf("0 Degrees") }
val brush = Brush.linearGradient(
listOf(Color.Green, Color.Yellow),
start = gradientOffset.start,
end = gradientOffset.end
)
Text(
text = angleText,
color = Color.Red,
modifier = Modifier
.padding(8.dp),
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Slider(
modifier = Modifier.height(50.dp),
value = angleSelection,
onValueChange = {
angleSelection = it
gradientOffset = when (angleSelection.roundToInt()) {
0 -> {
angleText = "0 Degrees"
GradientOffset(GradientAngle.CW0)
}
1 -> {
angleText = "45 Degrees"
GradientOffset(GradientAngle.CW45)
}
2 -> {
angleText = "90 Degrees"
GradientOffset(GradientAngle.CW90)
}
3 -> {
angleText = "135 Degrees"
GradientOffset(GradientAngle.CW135)
}
4 -> {
angleText = "180 Degrees"
GradientOffset(GradientAngle.CW180)
}
5 -> {
angleText = "225 Degrees"
GradientOffset(GradientAngle.CW225)
}
6 -> {
angleText = "270 Degrees"
GradientOffset(GradientAngle.CW270)
}
else -> {
angleText = "315 Degrees"
GradientOffset(GradientAngle.CW315)
}
}
},
steps = 6,
valueRange = 0f..7f
)
Spacer(modifier = Modifier.height(10.dp))
Box(
Modifier
.fillMaxWidth(.4f)
.aspectRatio(5 / 3f)
.background(brush)
)
Spacer(modifier = Modifier.height(10.dp))
Box(
Modifier
.fillMaxWidth(.4f)
.aspectRatio(1f)
.background(brush)
)
Spacer(modifier = Modifier.height(10.dp))
Box(
Modifier
.fillMaxWidth(.4f)
.aspectRatio(1f)
.background(brush, CircleShape)
)
}
}
You can try:
Box(
modifier = Modifier
.size(100.dp, 50.dp)
.background(
brush = Brush.linearGradient(
colors = listOf(
Color.Yellow,
Color.Green
)
)
)
)
Could you tell me why it stops working when I use val animateAngle: Float by transition.animateFloat instead of val animateAngle by animateFloatAsState?
It seems that the variable rotated is not updated when the button is clicked.
var rotated by remember {
mutableStateOf(false)
}
val transition = updateTransition(
targetState = rotated,
label = "Rotate"
)
val animateAngle: Float by transition.animateFloat(
transitionSpec = {
tween(2000)
},
label = "Rotate box"
) { state ->
when(state){
rotated -> 360f
else -> 0f
}
}
Column (
) {
Image(
painter = painterResource(id = R.drawable.propeller),
contentDescription = "fan",
modifier = Modifier
.rotate(animateAngle)
.padding(30.dp)
.size(100.dp))
Button(
onClick = {
rotated = !rotated
},
) {
Text(text = "Rotate Box")
}
}
You have a boolean mutableState that you use as a targetState for your transition, true or false will be its target state.
val transition = updateTransition(
targetState = rotated,
label = "Rotate"
)
Now you are reading that boolean mutableState inside targetValueByState lambda
when(state) {
rotated -> 360f
else -> 0f
}
and because you are reading it there, the animationAngle will always get a value of 360f, regardless if its true or false.
Logging it,
Log.e("AnimatedAngle", "$animateAngle : Rotated boolean state [$rotated]")
prints:
360.0 : Rotated boolean state [false]
360.0 : Rotated boolean state [true]
Just simply remove it and slightly modify the targetValueByState block this way, and your animation will work.
val animateAngle: Float by transition.animateFloat(
transitionSpec = {
tween(2000)
},
label = "Rotate box"
) { state ->
if (state) 360f else 0f
}
I have a Box layout in which I have a canvas composable that draws a circle animation. I also have another Test Composable in which I would be drawing a few other shapes(Denoted in Grey color in the video attachment).
The problem I'm facing is every time the animation value animates the circle, the draw scope lambda inside the test composable function is also triggered continuously which I do not want. I put a print line to confirm it.
I'm unable to understand why the draw scope lambda of another composable is triggered. Any help in understanding this behavior and how not to make it trigger continuously is deeply appreciated.
Box(
modifier = Modifier.
.fillMaxWidth(1f)
.wrapContentHeight()
) {
BlinkingCircle()
val modifier = Modifier.align(Alignment.Center)
Test(modifier)
}
#Composable
fun BlinkingCircle() {
val screenWidthInDp = LocalConfiguration.current.screenWidthDp
val infiniteScale = rememberInfiniteTransition()
val animatableSize = infiniteScale.animateFloat(
initialValue = 40f,
targetValue = 6f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1500,
easing = FastOutLinearInEasing
),
repeatMode = RepeatMode.Reverse
)
)
val xStart = 57f
val yStart = 126f
Canvas(modifier = Modifier
.width(screenWidthInDp.dp)
.fillMaxHeight(1f),
onDraw = {
drawScope.drawCircle(
color = Color.White,
radius = animatableSize.value,
center = Offset(
x = xStart,
y = yStart)
)
}
)
}
#Composable
fun Test(modifier: Modifier) {
Box(
modifier = modifier
.background(Color.LightGray)
.width(150.dp)
.height(100.dp)
.offset(300.dp, 50.dp)
) {
Canvas(modifier = Modifier
.width(50.dp)
.height(50.dp),
onDraw = {
println("Testing Box Text - 2")
//drawCircle(Color.Green, radius = 30f)
})
}
}
2022-08-10 01:43:56.897 15718-15718/com.xxxx.dev I/System.out: Testing Box Text - 2
2022-08-10 01:43:56.911 15718-15718/com.xxxx.dev I/System.out: Testing Box Text - 2
2022-08-10 01:43:56.927 15718-15718/com.xxxx.dev I/System.out: Testing Box Text - 2
2022-08-10 01:43:56.946 15718-15718/com.xxxx.dev I/System.out: Testing Box Text - 2
2022-08-10 01:43:56.962 15718-15718/com.xxxx.dev I/System.out: Testing Box Text - 2
2022-08-10 01:43:56.979 15718-15718/com.xxxx.dev I/System.out: Testing Box Text - 2
....
....
Video sample
I am trying to achieve translate animation in Jetpack compose but i am not able to find Suitable Source for this.Can any one Help me to achieve translate Animation in jetpack compose in which i can set start and edning positionl Manually..
The alternative of translate animation in jetpack compose is OFFSET ANIMATION
yes, I was able to achieve this through offset animation.I am sharing the code below with comments in detail so it will be easier for the reader to understand it
// Courtine Scope to Run the animation in thread
val coroutineScope = rememberCoroutineScope()
val offsetX = remember { Animatable(0f) }
val offsetY = remember { Animatable(0f) }
Image(
painter = rememberDrawablePainter(
ContextCompat.getDrawable(
context,
R.drawable.image
)
),
contentDescription = "s", contentScale = ContentScale.Crop,
modifier = Modifier
.offset {
IntOffset(
offsetX.value.toInt(),
offsetY.value.toInt()
)
}
.width(300.dp)
.height(300.dp)
)
//Finally run the animation on the Click of your button or whenever you wants to start it...
coroutineScope.launch {
launch {
offsetXFirst.animateTo(
targetValue = targetValue,
animationSpec = tween(
durationMillis = 2000,
delayMillis = 0))}
launch {
offsetYFirst.animateTo(
targetValue = size.height.toFloat(),
animationSpec = tween(
durationMillis = 2000,
delayMillis = 0))}
}
Using the offset was my solution as well. However used animateDpAsState instead. There a tab indicator is moved on the x axis:
val offsetState = animateDpAsState(targetValue = targetPositionDp)
Box(modifier = Modifier
.offset(offsetState.value, 0.dp)
.background(color = Color.Red)
.size(tabWidth, tabHeight))