alexvasilkov / GestureViews

ImageView and FrameLayout with gestures control and position animation
Apache License 2.0
2.37k stars 384 forks source link

How to prevent final image size blinking briefly on enter animation #164

Closed ParticleCore closed 3 years ago

ParticleCore commented 3 years ago

I'm trying to prevent the image from blinking briefly in its final size and position right before the enter a animation runs in the Image Animation (cross-activity) example.

For this scenario I need the image to not be invisible, so it starts visible, but when I do that this happens every single time:

I click on the image image

It shows like this instantly for what feels like 1 animation frame image

And quickly switches to the actual animation/transition (imagine this growing from the original image to the final size in the new activity) image

So for a very fast 1 frame kinda moment the image is shown in its final size and position right before it starts animating from the clicked area.

I tried setting the setState manually into the gesture view even before the image is set in it, also after. I also tried to set the enter with the initial x, y and zoom and false for animation, just to make sure the image is starting from the expected position and size. I even confirmed the states and fromPos were being stored correctly in the getPositionAnimator(), but no difference.

Isn't there any way to ensure that the very first time the GestureView display the image is at the specified position + zoom we expect or define?

alexvasilkov commented 3 years ago

For this scenario I need the image to not be invisible, so it starts visible

What does it mean? Can you share how it looks in code?

If the final view is not invisible it will be shown before the animation starts, so it's kinda expected. You may try to use somkind of a hack, like preventing it from dwrawing manually instead of using standard view visiblity.

Overall I cannot say much without reproducing the issue.

ParticleCore commented 3 years ago

For this scenario I need the image to not be invisible, so it starts visible

What does it mean? Can you share how it looks in code?

There is nothing special, it's just in your example the image stars invisible first right before the animation begins which hides that first frame from showing the image in full size shortly before it is resized correctly to the from packed position information.

Basically removing this step right here causes the issue: https://github.com/alexvasilkov/GestureViews/blob/552383ee0605205c2dcfac009722cefff2726ced/sample/src/main/java/com/alexvasilkov/gestures/sample/ex/image/animation/cross/FullImageActivity.java#L48

alexvasilkov commented 3 years ago

Yes, it was done exactly to hide this first frame. So just don't remove this line. What's the issue then? :)

ParticleCore commented 3 years ago

I need it to not be hidden because occasionally it still blinks (the previous activity's image is hidden faster than the FullImageActivity can be shown) either because of a combination of Glide or other stuff I have no control over.

I noticed your implementation depends on some calculation of draw + frame count to assume when the origin image can be hidden and the full image can be displayed + zoomed into, but this is not working all the time, I noticed about every 40% or more times it still blinks, but if I remove that line (and manually set the image enter destination size to be the same as the original one) then it never blinks again.

The problem with just removing the line I mentioned is the full image shows briefly right before the enter animation starts, which in turn resizes the animation to the expected initial position and size before starting the animation and I have been trying to work around that because everything else I tried (timings between hiding and showing images) did not have a successful result as much as just removing that line - in regards to never blinking again.

ParticleCore commented 3 years ago

There is a runOnNextFrame responsible for this logic and I decided to try not using it and instead to post the action right away, and the blinking has been severely reduced.

I'm thinking this will be the best that can be done given how this is a very specific coordination between multiple factors that can be tricky to gather feedback from in order to chain a stable chain of events.

@alexvasilkov if you don't have any suggestion on how this can be achieved in a different way you can close this issue, I don't see any point in keeping it open for what appears to be a lost cause in my very specific case.

alexvasilkov commented 3 years ago

Cross activity animation is tricky and generally not recommended. To avoid blinking you have to implement quite a few hacks and it still does not 100% guarantee the result (who knows how Android will decide to draw old/new activity).

If you cannot hide the image on start (I'm still not sure why) then I don't really know what to recommend. I cannot completely recall why I added runOnNextFrame hack, but I would assume that it may not work as expected if you won't use that.

Overall, without any extra code from your side I'm not able to help, so let's close the issue now.

ParticleCore commented 3 years ago

I have discovered why the image is briefly showing in full right before the animation is started and it is due to an incorrect(?) sequence of calls.

The very first time the view is about to be displayed right after updating its state values it reaches this point here: https://github.com/alexvasilkov/GestureViews/blob/f7a80dd1d5fe0a1302b62b073e22b6bf04389986/library/src/main/java/com/alexvasilkov/gestures/animation/ViewPositionAnimator.java#L697-L701

It enters here because fromPos is still null, thus causing the initial position to be the same as the final position which results in the view showing in full shortly before the animation resumes.

This is because the calls made here are maybe out of order for this case?

https://github.com/alexvasilkov/GestureViews/blob/f7a80dd1d5fe0a1302b62b073e22b6bf04389986/library/src/main/java/com/alexvasilkov/gestures/animation/ViewPositionAnimator.java#L227-L234

I say this because this block is the very first one to run, then it enters enterInternal(withAnimation); which goes here https://github.com/alexvasilkov/GestureViews/blob/f7a80dd1d5fe0a1302b62b073e22b6bf04389986/library/src/main/java/com/alexvasilkov/gestures/animation/ViewPositionAnimator.java#L298-L306

Then setState(withAnimation ? 0f : 1f, false, withAnimation); goes here: https://github.com/alexvasilkov/GestureViews/blob/f7a80dd1d5fe0a1302b62b073e22b6bf04389986/library/src/main/java/com/alexvasilkov/gestures/animation/ViewPositionAnimator.java#L463-L480

And finally we reach the previously mentioned point when it calls applyCurrentPosition();, pointing to here https://github.com/alexvasilkov/GestureViews/blob/f7a80dd1d5fe0a1302b62b073e22b6bf04389986/library/src/main/java/com/alexvasilkov/gestures/animation/ViewPositionAnimator.java#L503-L505

Because at no point in this chain of events the fromPos was ever set, the very first frame will animate from the default position, which is the same as the final position, leading to that brief blink of the image in full I mentioned in this issue.

Is there a way to work around this problem? It feels like updateInternal(fromPos); should be called before enterInternal(withAnimation);

ParticleCore commented 3 years ago

Note: I did try using .update(position) before .enter(...), but of course it throws the You should call enter(...) before calling update(...) error, so I cannot "preset" the from position before the enter animation is run.

alexvasilkov commented 3 years ago

The order of calls is correct and there is no need to call update before enter. Both calls are made one by one, so if we know both from and to positions then the system will draw it correctly on the next drawing event.

To position is not available right away, instead we have to wait when the view is properly measured and laid out. If the view was already laid out then we'd get its position immediately and you won't see any lags. But since we don't know the to view position then there is no way to draw the image in the correct place.

It is especially a problem for activity start.

There could probably be a way to get the to view position faster, but I didn't dig into it. You can try to play with ViewPositionHolder.

alexvasilkov commented 3 years ago

I updated cross-acitvity example, removed setting image invisibility on start and it does not blink to me: https://github.com/alexvasilkov/GestureViews/commit/cab84484f825f16d0a14241aeb8ca26d0077a494

ParticleCore commented 1 year ago

It's been 3 years, but I still end up coming here, so I thought I should post a final update on this. It is exactly as you said, we have to fine tune the moment the image can be displayed and the enter animation begins. I found it enough to wait for the first image draw (while that imageview is invisible) before entering the animation. So far I've only had issues when I forgot to set the initial visibility of the imageview to invisible, which caused the issue initially described here.