I am making a simple Loading spinner view component that has two image assets. The first image is a spinner that should show and rotate while the component is "loading". The second image is a checkmark that should show without any animation when the "loading" has finished. I'd like to use one ImageView and just swap out the image asset when needed.
Here's the spinner image:
And here's the checkmark image:
I wrote a function to stop the animation and replace the image but when I call it this is what I see:
It looks like the image is getting changed but then the original image is getting redrawn.
Here's my function that starts the animation:
fun show() {
rotateAnimation = RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)
rotateAnimation!!.duration = 900
rotateAnimation!!.repeatCount = Animation.INFINITE
rotateAnimation!!.fillAfter = true
mLoaderView.startAnimation(rotateAnimation)
}
And here's my function that stops the animation and changes the image:
private fun completeLoading() {
rotateAnimation!!.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(p0: Animation?) {
// not implemented
}
override fun onAnimationRepeat(p0: Animation?) {
// not implemented
}
override fun onAnimationEnd(p0: Animation?) {
mLoaderView.setBackgroundDrawable(Util.getLocalDrawable(mLoaderView.context, "icn_loading_error"))
}
})
mLoaderView.clearAnimation()
}
How can I prevent the spinner image from getting drawn over the checkmark image?
Related
I'm trying to use Lottie compose for playing animation in compose. But the animation starts from the very beginning for all recompositions. I wish to maintain the current playback and not restart the animation for each recomposition. Here is my current code
#Composable
fun Loader() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.main))
LottieAnimation(composition)
}
You need to save animation progress outside of the composable functions, that will be recomposed
#Composable
fun ParentComposable() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.main))
val animationProgress by animateLottieCompositionAsState(composition = composition)
ChildComposable(animationProgress = animationProgress, composition = composition)
}
#Composable
fun ChildComposable(animationProgress: Float, composition: LottieComposition?) {
...
Loader(progress = animationProgress, composition = composition)
...
}
#Composable
fun Loader(animationProgress: Float, composition: LottieComposition?) {
...
LottieAnimation(composition, animationProgress)
...
}
I'm currently trying to change the matchConstraintPercentWidth from 2 to 0 of a view using Animation() when starting my activity (in method onWindowFocusChanged() to make sure that all the views have been drawn correctly). The problem is the animation ends instanlty (and th view has now the new params - seems like the duration of the animation is 0 ms), no matter the duration I set...
Here is my code (in Kotlin) :
override fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) {
val gradient = findViewById<ImageView>(R.id.black_gradient)
val animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
val params = gradient.layoutParams as ConstraintLayout.LayoutParams
params.matchConstraintPercentWidth = 0f
gradient.layoutParams = params
}
}
animation.duration = 2000L // in ms
gradient.startAnimation(animation) //also tried animation.start() without effect
//animation.hasStarted() is always false here
}
}
Any help is welcome ;)
That applyTransformation method is where you're meant to calculate the current state of the animation, based on interpolatedTime (which is between 0.0 and 1.0). You're just setting your constraint value to 0, so it's not actually changing a value over time and animating anything.
Honestly you probably don't want to touch any of that if you can help it, Android has some helper classes that abstract a lot of that detail away, so you can just easily animate a thing.ValueAnimator is probably a good shout, you could just do
ValueAnimator.ofFloat(0f, 100f).apply {
addUpdateListener { anim ->
val params = (gradient.layoutParams as ConstraintLayout.LayoutParams)
params.matchConstraintPercentWidth = anim.animatedValue as Float
}
duration = 1000
start()
}
and that should be the equivalent of what you're doing. There's also ObjectAnimator at that link too, but that requires a setter method and there isn't one for that layout parameter (ConstraintProperties has some, but not for that one as far as I can see)
Code looks fine to me, try removing the condition of hasFocus, because there might some views which might be getting the focus before the this particular the thing you should do to diagnose is
try to log hasFocus if it's not getting focus then change the code like code below, also just a tip you should always initialize the views outside of callbacks.
override fun onWindowFocusChanged(hasFocus: Boolean) {
val gradient = findViewById<ImageView>(R.id.black_gradient)
val animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
val params = gradient.layoutParams as ConstraintLayout.LayoutParams
params.matchConstraintPercentWidth = 0f
gradient.layoutParams = params
}
}
animation.duration = 2000L // in ms
gradient.startAnimation(animation) //also tried animation.start() without effect
//animation.hasStarted() is always false here
}
Using navigation components, I would like apply fade-in effect to FadeInContent during transition, respecting the following order:
Text1 -> Text2 transition (Done by applying R.transition.move in sharedElement)
FadeInContent fades-in after 1. transition
I had a look to this article that does exactly what I want, but doesn't use navigation components
https://medium.com/bynder-tech/how-to-use-material-transitions-in-fragment-transactions-5a62b9d0b26b, therefore I can't apply setStartDelay. I can't also apply NavOptions.Builder().setEnterAnim(R.anim.fade_in) because it applies to all the screen, and not just the FadeInContent.
AFAIK the navigation component can only handle the motion during the transition itself so you are rightly pointing out that there is no way to delay a transition.
Nonetheless, you might want to implement your fade-in animation with a scene transition (https://developer.android.com/training/transitions).
It looks like a cleaner way to handle the situation you are exposing.
Code Solution:
val transition = TransitionInflater.from(activity)
.inflateTransition(android.R.transition.move)
sharedElementEnterTransition = transition
setEnterSharedElementCallback(object : SharedElementCallback() {
override fun onMapSharedElements(
names: MutableList<String>?,
sharedElements: MutableMap<String, View>?
) {
super.onMapSharedElements(names, sharedElements)
fadeInContainer.loadAnimation(
activity,
R.anim.fade_in
)
}
})
Code Solution:
val transition = TransitionInflater.from(activity)
.inflateTransition(android.R.transition.move)
sharedElementEnterTransition = transition
setEnterSharedElementCallback(object : SharedElementCallback() {
override fun onMapSharedElements(
names: MutableList<String>?,
sharedElements: MutableMap<String, View>?
) {
super.onMapSharedElements(names, sharedElements)
fadeInContainer.loadAnimation(
activity,
R.anim.fade_in
)
}
})
I have problem with any type of animations. I want to make material banner behavior, but with other animations. Actually I got the result, but the problem is that view is blinking after the animation. My code:
First example:
val anim = TranslateAnimation(1f, 1f, 1f, 0f)
anim.duration = 300
banner.startAnimation(anim)
banner.visibility = View.INVISIBLE
Second example
val mTransition = Slide(Gravity.END)
mTransition.setDuration(300)
mTransition.addTarget(banner)
TransitionManager.beginDelayedTransition(banner, mTransition)
banner.setVisibility(View.GONE)
Can someone explain how to avoid blinking of the view and why it is happening.
The problem is on the code banner.visibility = View.INVISIBLE and banner.setVisibility(View.GONE). Try to remove it.
If you want to the banner is gone after the animation ended. Try to add a listener on the animation and hide the banner after the animation ended:
val anim = TranslateAnimation(1f, 1f, 1f, 0f)
anim.duration = 300
anim.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
// banner.visibility = View.INVISIBLE
// or
// banner.setVisibility(View.GONE)
}
override fun onAnimationStart(animation: Animation?) {
}
})
I solved problem of blinking of the view by animating it on other way. I used following strategy. First of all I used Guideline component of the ConstraintLayout. I constraint my banner to the top of it and place parameter layout_constraintGuide_begin = "0dp". After that I used ValueAnimator in order to get animated value for my Guideline and changed the guidebegin params of it(see the code).
val params: ConstraintLayout.LayoutParams = guideline2.layoutParams as ConstraintLayout.LayoutParams
animBanner = ValueAnimator.ofInt(0, banner.height + toolbar.height)
animBanner!!.addUpdateListener {
params.guideBegin = it.getAnimatedValue() as Int
guideline2.layoutParams = params
}
This is the declaration of animation. At the end it is enough to use animBanner.start() for starting the animation and animBanner.reverse() for reverse animation (hiding banner).
I understand what was my problem with the help of #John Lee, but it solution does not was suitable for me, so I used Guideline component with AnimatedValue. My solution:
params = view.layoutParams as ConstraintLayout.LayoutParams
anim = ValueAnimator.ofInt(fromY, toY)
anim.addUpdateListener {
params.guideBegin = it.animatedValue as Int
view.layoutParams = params
}
So, here fromY value is 0, toY value is height of banner and view is Guideline, which could received by view.height. I should mention that first my banner is constrained to top of Guideline, which is placed at constraintGuide_begin=0. Then I animated this guideline with help of code above and using anim.start(), anim.reverse() methods.
I have a recycler view that contain progress bar
I am animating these progress bar with this animation
fun setProgresAnim(progress: ProgressBar, value: Int) {
val anim = ProgressBarAnimation(progress, 0, value)
anim.duration = 2000
progress.startAnimation(anim)
}
This is MyViewHolder in the adapter
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(deviceScan: DeviceScan) {
if (deviceScan.percentage > 0) {
itemView.title.text = deviceScan.name
itemView.tv_percentage.text = deviceScan.percentage.toString() + "%"
setProgresAnim(itemView.progress, deviceScan.percentage)}}}
The problem is when I scroll up and down the animation restart again.
I want to do this animation once.
You should avoid calling setProgresAnim(itemView.progress, deviceScan.percentage) within onBindViewHolder , because each time you will scroll up or down it will call your animation again . it's better to call it outside your RecyclerView Adapter .