How to make Jetpack Compose Image animating infinite - animation

I have an animated-vector drawable.
I want this animated vector to be animated in loop while this image is showing.
Cannot find a good solution for this.
val image = animatedVectorResource(R.drawable.no_devices_animated)
var atEnd by remember { mutableStateOf(false) }
Image(
painter = image.painterFor(atEnd),
"image",
Modifier.width(150.dp).clickable {
atEnd = !atEnd
},
contentScale = ContentScale.Fit)
When I tap on the image it is animating but then stops. This is kind of an infinite progress.

Leaving my solution here (using compose 1.2.0-alpha07).
Add the dependencies in your build.gradle
dependencies {
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.animation:animation-graphics:$compose_version"
}
And do the following:
#ExperimentalAnimationGraphicsApi
#Composable
fun AnimatedVectorDrawableAnim() {
val image = AnimatedImageVector.animatedVectorResource(R.drawable.avd_anim)
var atEnd by remember { mutableStateOf(false) }
// This state is necessary to control start/stop animation
var isRunning by remember { mutableStateOf(true) }
// The coroutine scope is necessary to launch the coroutine
// in response to the click event
val scope = rememberCoroutineScope()
// This function is called when the component is first launched
// and lately when the button is pressed
suspend fun runAnimation() {
while (isRunning) {
delay(1000) // set here your delay between animations
atEnd = !atEnd
}
}
// This is necessary just if you want to run the animation when the
// component is displayed. Otherwise, you can remove it.
LaunchedEffect(image) {
runAnimation()
}
Image(
painter = rememberAnimatedVectorPainter(image, atEnd),
null,
Modifier
.size(150.dp)
.clickable {
isRunning = !isRunning // start/stop animation
if (isRunning) // run the animation if isRunning is true.
scope.launch {
runAnimation()
}
},
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(Color.Red)
)
}
Case you need to repeat the animation from the start, the only way I find was create two vector drawables like ic_start and ic_end using the information declared in the animated vector drawable and do the following:
// this is the vector resource of the start point of the animation
val painter = rememberVectorPainter(
image = ImageVector.vectorResource(R.drawable.ic_start)
)
val animatedPainter = rememberAnimatedVectorPainter(
animatedImageVector = AnimatedImageVector.animatedVectorResource(R.drawable.avd_anim),
atEnd = !atEnd
)
Image(
painter = if (atEnd) painter else animatedPainter,
...
)
So, when the animated vector is at the end position, the static image is drawn. After the delay, the animation is played again. If you need a continuous repetition, set the delay as the same duration of the animation.
Here's the result:

Infinite loop without static images solution:
Best working with animated vector drawable that is a loop.
First add the dependencies in your build.gradle
dependencies {
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.animation:animation-graphics:$compose_version"
}
Animated loop loader
#OptIn(ExperimentalAnimationGraphicsApi::class)
#Composable
fun Loader() {
val image = AnimatedImageVector.animatedVectorResource(id = R.drawable.loader_cycle)
var atEnd by remember {
mutableStateOf(false)
}
val painterFirst = rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
val painterSecond = rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = !atEnd)
val isRunning by remember { mutableStateOf(true) }
suspend fun runAnimation() {
while (isRunning) {
atEnd = !atEnd
delay(image.totalDuration.toLong())
}
}
LaunchedEffect(image) {
runAnimation()
}
Image(
painter = if (atEnd) painterFirst else painterSecond,
contentDescription = null,
)
}
Right now I don't see any option to restart painter to start all over again - you need to rewind it. So in this solution we create two of those painter. One of them is starting with !atEnd. Each time atEnd is changed both of them do their work but we display only one animating from start to end. The other one is silently rewinding the animation.

According to https://developer.android.com/jetpack/compose/resources#animated-vector-drawables
val image = AnimatedImageVector.animatedVectorResource(R.drawable.animated_vector)
val atEnd by remember { mutableStateOf(false) }
Icon(
painter = rememberAnimatedVectorPainter(image, atEnd),
contentDescription = null
)
To make the animation loop infinitely:
val image = AnimatedImageVector.animatedVectorResource(id = vectorResId)
var atEnd by remember { mutableStateOf(false) }
Image(
painter = rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd),
contentDescription = null,
)
LaunchedEffect(Unit) {
while (true) {
delay(animDuration)
atEnd = !atEnd
}
}

I tried to implement a loading spinner using this approach, but encountered some issues.
I had an issue with the proposed solution, where the animation reverses back to the beginning. To prevent this, as #nglauber suggested, I tried switching to a static vector when the animation ends. Unfortunately, that wasn't smooth as the animation would have to wait for the delay before restarting.
This was the workaround that I used.
#OptIn(ExperimentalAnimationGraphicsApi::class)
#Composable
fun rememberAnimatedVectorPainterCompat(image: AnimatedImageVector, atEnd: Boolean): Painter {
val animatedPainter = rememberAnimatedVectorPainter(image, atEnd)
val animatedPainter2 = rememberAnimatedVectorPainter(image, !atEnd)
return if (atEnd) {
animatedPainter
} else {
animatedPainter2
}
}

Related

Wear OS Button Background

Button(
onClick = {
raceOn = !raceOn
if (raceOn) {
text.value = "Stop!"
color.value = Color.Red
} else {
text.value = "Go!"
color.value = Color.Green
}
},
modifier = Modifier.background(color = color.value),
content = {
Text(
text = "${text.value}",
)
}
)
Using the code above, I got the attached image. What I want is the inside of the button to be green and not the background behind it. I couldn't the right property to modify.
Has anyone here tried to modify the Button background? Or perhaps suggest another solution. I tried the OutlinedButton and wasn't successful.
Thanks!
Use the MaterialTheme ButtonColors.
val colors: ButtonColors = ButtonDefaults.primaryButtonColors(backgroundColor = Color.Red)
Button(onClick = { /*TODO*/ }, colors = ButtonDefaults.primaryButtonColors()) {
// TODO
}
You can also update via setting the MaterialTheme defaults.
val wearColorPalette: Colors = Colors(
primary = Purple200,
primaryVariant = Purple700,
secondary = Teal200,
secondaryVariant = Teal200,
error = Red400,
onPrimary = Color.Black,
onSecondary = Color.Black,
onError = Color.Black
)
...
MaterialTheme(
colors = wearColorPalette,
typography = Typography,
// For shapes, we generally recommend using the default Material Wear shapes which are
// optimized for round and non-round devices.
content = content
)

Is it possible to display animated gif in jetpack?

I'm trying to implement a gif in my splash screen using jetpack.Tried this one as suggested but no output .What am I missing?
val context = LocalContext.current
val imageLoader = ImageLoader.Builder(context)
.componentRegistry {
if (SDK_INT >= 28) {
add(ImageDecoderDecoder(context))
} else {
add(GifDecoder())
}
}
.build()
Image(
painter = rememberImagePainter(
imageLoader = imageLoader,
data = R.id.mygif,
builder = {
size(OriginalSize)
}
),
contentDescription = null,
modifier = Modifier
.padding(top = 100.dp)
)
Apparently Compose is not supporting gif out of the box, I could not find any reference to gif files in the docs. However, one of the popular library out there to deal with gifs is Coil.
-> Here is coil for Compose:
https://coil-kt.github.io/coil/compose/
-> Make sure to add the gif extension:
https://coil-kt.github.io/coil/gifs/
-> You will have to override the ImageLoader and add the gif extension:
https://coil-kt.github.io/coil/compose/#localimageloader
For those, who is still looking for answer, here is updated version (since LocalImageLoader is deprecated) in Coil.
Same steps for installation:
Add implementation for coil-compose
Add implementation for coil-gif
Usage:
#Composable
fun Content() {
val imageLoader = ImageLoader.Builder(LocalContext.current)
.components {
if (SDK_INT >= 28) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
}
.build()
Image(
painter = rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current)
.data(data = R.drawable.mettaton_battle_box)
.apply(block = fun ImageRequest.Builder.() {
size(Size.ORIGINAL)
}).build(),
imageLoader = imageLoader
),
contentDescription = null,
)
}

Transparent view

I would like to create a View (stage, window) with partially transparent background. I have an image containing alpha channel
I used this kind of scenes in JavaFx, where I had to set the scene fill to null and the root node background color to transparent. I tried the same with TornadoFX:
class NextRoundView : View("Következő kör") {
override val root = vbox {
style {
backgroundColor = multi(Color.TRANSPARENT)
backgroundImage = multi(URI.create("/common/rope-bg-500x300.png"))
backgroundRepeat = multi(BackgroundRepeat.NO_REPEAT
to BackgroundRepeat.NO_REPEAT)
}
prefWidth = 500.0
prefHeight = 300.0
spacing = 20.0
padding = insets(50, 20)
text("A text") {
font = Font.font(40.0)
alignment = Pos.CENTER
}
button("OK")
{
font = Font.font(20.0)
action {
close()
}
}
sceneProperty().addListener{ _,_,n ->
n.fill = null
}
}
}
I'm calling the view like this:
NextRoundView().apply {
openModal(stageStyle = StageStyle.TRANSPARENT, block = true)
}
However, the stage is still has background:
What have I missed?
You've made a couple of mistakes that causes this. First of all, you must never manually instantiate UICompoenents (View, Fragment). Doing so will make them miss important life cycle callbacks. One important callback is onDock, which is the perfect place to manipulate the assigned scene. Changing these two issues and also cleaning up some syntax leads to this code, which successfully makes the background transparent:
class MyApp : App(MyView::class)
class MyView : View() {
override val root = stackpane {
button("open").action {
find<NextRoundView>().openModal(stageStyle = StageStyle.TRANSPARENT, block = true)
}
}
}
class NextRoundView : View("Következő kör") {
override val root = vbox {
style {
backgroundColor += Color.TRANSPARENT
backgroundImage += URI.create("/common/rope-bg-500x300.png")
backgroundRepeat += BackgroundRepeat.NO_REPEAT to BackgroundRepeat.NO_REPEAT
}
prefWidth = 500.0
prefHeight = 300.0
spacing = 20.0
padding = insets(50, 20)
text("A text") {
font = Font.font(40.0)
alignment = Pos.CENTER
}
button("OK") {
font = Font.font(20.0)
action {
close()
}
}
}
override fun onDock() {
currentStage?.scene?.fill = null
}
}
Here is a screenshot of the app with the changes implemented:

transition scene sprite kit

Im having trouble transitioning to pre determined location on a different scene. (for example when Mario goes into the tunnel he returns to the original scene right where he left off) I was able to code a way to get to the next scene but node does not appear were I would like it to.
this is my code to transition to second Scene
func didBeginContact(contact: SKPhysicsContact) {
_ = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if (contact.bodyA.categoryBitMask == BodyType.cup.rawValue && contact.bodyB.categoryBitMask == BodyType.ball.rawValue) {
touchmain()
} else if (contact.bodyA.categoryBitMask == BodyType.ball.rawValue && contact.bodyB.categoryBitMask == BodyType.cup.rawValue) {
touchmain()
}
}
func touchmain() {
let second = GameScene(fileNamed:"GameScene")
second?.scaleMode = .AspectFill
self.view?.presentScene(second!, transition: SKTransition.fadeWithDuration(0.5))
}
I would really appreciate it if you guys can help a young developer out. much love!
There are many ways you could go about this. The general gist of it is, just remember "Mario's" location before you leave the scene and pass it along to a property in the next scene. (or you could use delegation to get the info)
If the scenario is that he starts in SceneA(main level) transfers to SceneB(small money tunnel) and then returns to SceneA(main level) you could pass the location to SceneB and have a property in SceneB that stores where Mario was. Then when you transfer back to SceneA just pass the property back to a property in SceneA, and position Mario and SceneA accordingly
func touchmain() {
let marioPos: CGPoint = Mario.position
let second = GameScene(fileNamed:"GameScene")
second.startingPos = marioPos
second?.scaleMode = .AspectFill
self.view?.presentScene(second!, transition: SKTransition.fadeWithDuration(0.5))
}
You can use the userData field on SKScene to remember the position:
Somewhere in the first scene init, do :
userData = [String : NSObject]()
Also, in your first scene, we want to override didMove(to:SKView) to set mario position based on your userdata :
override func didMove(to:SKView)
{
super.didMove(to:to)
if let position = self.userData["position"]
{
mario.position = position
}
}
We then want to actually assign the user data when transitioning away from the scene:
func touchmain() {
if let userData = self.userData
{
userData["marioPosition"] = mario.position
}
if let second = GameScene(fileNamed:"GameScene")
{
second.scaleMode = self.scaleMode //I assume we do not want to change scaleMode between scenes
second.userData = userData
self.view?.presentScene(second, transition: SKTransition.fadeWithDuration(0.5))
}
else
{
print("Error creating Second scene")
}
}
When we are coming back, we want to transfer the userData back to the first screen. This is where the didMove that I mentioned earlier comes into play, this will set the position of mario to previous location.
... I do not see this code, so try and apply it to how ever you are doing it
func touchBackToFirst() {
if let first = GameScene(fileNamed:"GameScene")
{
first.scaleMode = self.scaleMode
first.userData = userData
self.view?.presentScene(first, transition: SKTransition.fadeWithDuration(0.5))
}
else
{
print("Error creating Firstscene")
}
}

Android: How to end animation of a listitem using valueanimator?

I am using the ValueAnimator to make one row in my list pulse from dark blue to light blue finitely. However, I need to check for a boolean when the rows load and when it gets set to false I need the view to go back to its original non-pulsing state. What is the best way of doing this?
My code is as follows -
if(isHighlighted(post)) {
String titleText = title.getText().toString();
title.setText(titleText);
title.setTextColor(Color.WHITE);
timeStamp.setTextColor(Color.WHITE);
highLighterStartColor = resources.getColor( R.color.active_blue );
highLighterEndColor = resources.getColor( R.color.teal );
ValueAnimator va = ObjectAnimator.ofInt(view, "backgroundColor", highLighterStartColor, highLighterEndColor);
if(va != null) {
va.setDuration(750);
va.setEvaluator(new ArgbEvaluator());
va.setRepeatCount(ValueAnimator.INFINITE);
va.setRepeatMode(ValueAnimator.REVERSE);
va.start();
}
} else {
title.setTextAppearance(activity.getApplicationContext(), R.style.medium_text);
timeStamp.setTextAppearance(activity.getApplicationContext(), R.style.timestamp_text);
}

Resources