I've been trying to get a fragment to be inserted and removed from my activity with a circular reveal animation. I found this demo repo here that does exactly this. The only change I made was to have the fragment removed on a button click, instead of the fragment touched listener.
The problem is, after removal using the button, when the animation ends, I see the fragment flash for a second. The flash doesnt happen if I'm removing the fragment using the onTouch listener: YouTube link
A few observations:
- If I run the code on my mobile device, then the flash only happens if I'm removing the fragment using the button. No flash occurs when onFragmentTouch is called.
- If I'm running this on the emulator, then I can see it flash on both, the button press and the fragment touch.
To simplify things, my onFragmentTouch listener is simply calling a function remove that actually removes the fragment. This same function is also called by the button, in its XML.
I can't understand why this is happening. Code for reference.
Add fragment, within main activity:
public void addFragment(View v)
{
int randomColor =
Color.argb(255, (int)(Math.random() * 255), (int)(Math.random() * 255), (int)(Math.random() * 255));
mfragment = CircularRevealingFragment.newInstance(20, 20, randomColor);
getFragmentManager().beginTransaction().add(R.id.container, mfragment).commit();
}
Function to remove fragment, also within main activity:
public void remove(View v){
Animator unreveal = mfragment.prepareUnrevealAnimator(0, 0);
unreveal.addListener(new Animator.AnimatorListener()
{
#Override
public void onAnimationEnd(Animator animation)
{
getFragmentManager().beginTransaction().remove(mfragment).commit();
}
//hiding other overridden function.
});
unreveal.start();
}
The CircularRevealingFragment is pretty straightforward, with mostly everything being the same as in that github project. The unreveal animator:
public Animator prepareUnrevealAnimator(float cx, float cy)
{
int radius = getEnclosingCircleRadius(getView(), (int)cx, (int)cy);
Animator anim = ViewAnimationUtils.createCircularReveal(getView(), (int) cx, (int) cy, radius, 0);
anim.setInterpolator(new AccelerateInterpolator(2f));
anim.setDuration(1000);
return anim;
}
Can anyone explain the behavior shown in the video?
I think that the main problem is that commited FragmentTransaction executes asynchronously. If you want to immediately executing use getFragmentManager().executePendingTransactions().
Related
I am developing a basic app for detecting all the Gestures like (Single tap, Double Tap, Swipe, Shake). I also have implemented for wait. i,e until the animation is completed.
I am using Horizontal progress bar as timer using the ObjectAnimator.
For that i am displaying text like Single tap, and if any action other than this is done it is error.
So for the first time when WAIT text is displayed if I do not perform any action the onAnimationEnd is getting called.
Below is my code.
objectAnimator = ObjectAnimator.ofInt(timerProgress, "progress", 0, 100);
objectAnimator.setDuration(progressTime);
objectAnimator.setInterpolator(new AccelerateInterpolator());
objectAnimator.setEvaluator(new IntEvaluator());
objectAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (timerProgress.getProgress() != 100) {
return;
}
if (!isTouched) {
onTouch(Constants.WAIT);
}
}
});
If i set any text after WAIT, (say Double tap ) and if I do not perform anything, the onAnimationEnd must be called. But it is not geting called.
I have used view.clearAnimation() but didnt work.
Is there any work around.? Please help
I have the following code (Android project in Scala):
val animator = new ValueAnimator
animator.setFloatValues(0f, 100f)
animator.setDuration(20000L)
animator.addUpdateListener(this) // prints current value to console
animator.start
override def onTouch(v: View, event: MotionEvent) = {
animator.setFloatValues(100f, 0f)
animator.setCurrentPlayTime(0)
if (!animator.isRunning) animator.start
true
}
If I touch the screen while animator is running then it correctly starts working backwards (since I've swapped the values). But if I touch the screen after it is finished then nothing happens, it does not start over.
Question is can I reuse this animator somehow and make it work again for given values after it has been stopped?
You can reuse animator in the following way:
...
animator.end();
animator.setFloatValues(...);
animator.start();
...
You can also use animator.cancel() instead of animator.end() and pass the last value from the last animation to the new animation as a starting float. For instance, if the last animated value is 50, you can call animator.setFloatValues(50, 0f) so your animations will look connected.
Considering the accepted answer states it's impossible, I would like to mention that the described approach is used in Touch Circle app when users make tap with two fingers. BTW, it's a very nice effect when object trembles a little bit - use it as you wish, code exerpt is below:
void shapeTremble(long delay) {
if (null == animatorTremble) {
ValueAnimator animator = new ValueAnimator();
animator.setDuration(850).setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
setCircleScale((Float) valueAnimator.getAnimatedValue(), true);
}
});
animatorTremble = animator;
} else {
animatorTremble.cancel();
}
animatorTremble.setFloatValues(circleScale, 0.85f, 1.05f, 0.95f, 1.025f, 1.0f);
animatorTremble.setStartDelay(delay);
animatorTremble.start();
}
You cannot reuse an animation.
You need to reset() and reinitialize an animation object by calling the initialize() method before using the same object again. This is similar to instantiating a new animation object.
I'm working on a project that calls for the navigation bar to be implemented in a fashion similar to how the Zazzle application does their navigation bar. Animations are not my expertise so I would like to get some ideas from the dev community on how such an animation can be implemented.
This is an image of the navigation drawer, notice how the previous fragment stays in view on the left.
Zazzle_navigation_Drawer
This is a screen shot of the nav transition when the menu button is pressed. It turns and tucks its self away in the right hand side of the screen and when the menu is dismissed it reverses that process.
Zazzle_nav_transition
I'm looking for a suggestion on how this might be implemented and maybe some resources I should look into using or information to learn in order to have a good shot of replicating this effect.
What I've done so far.
I have watched and took notes on the Sliding Animations DevBytes video.
So my strategy thus far is to make the navigation a fragment along with all my other screens and try and implement some sliding animations between them. I think this should give me the basic foundation.
Therefore I am more asking about how the animation would be constructed. I've never done animations before so I am asking for some help in the regards of how custom animations are implemented in android generally and a suggestion on how animation would be done in this case specifically
If anyone stumbles upon this...
I figured this out by using the sample project from the Android developer training website. I created an anim folder in res and adding xml set files with the Object animator tag. I didnt understand how to manipulate the values correctly to get the desired animation but after some poking around i realized the following
<objectAnimator
android:valueFrom=
android:valueTo=
android:propertyName="
android:interpolator=
android:duration= />
The valueFrom tag and the valueTo tag define the points the animation runs between and android fills in the rest. The property name is used to describe what kind of motion is being manipulated (translationX, translationY, rotationX, translationY, scaleX and scaleY). The interpolator sets the rate of change of the animation and finally duration specifies how long the animation should take.
Once the animation files are made the following code can be used to set the animations on the fragments during a transaction.
getFragmentManager()
.beginTransaction()
.setCustomAnimations(
R.animator.exampleOut, R.animator.exampleIn,
R.animator.exampleOut, R.animator.exampleIn,
).add(R.id.container, new mFragment()).addToBackStack(null).commit();\
the first two parameters of setCustomAnimations() set the motion of the two fragments when the transaction is initiated and the last two parameters set the motion of the two fragments when the transaction is reversed, i.e., the back button is pressed.
This can be used to achieve the desired animation however I found that the outbound fragment would disappear from view for some reason. There fore I had to use AnimatorListener as follows:
Animator.AnimatorListener listener = new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator arg0) {
getFragmentManager()
.beginTransaction()
.setCustomAnimations(
R.animator.exampleOut, 0,
0, R.animator.exampleIn
)
.add(R.id.container, new mFragment())
.addToBackStack(null)
.commit();
}
};
slideback(listener);
mHandler.post(new Runnable() {
#Override
public void run() {
invalidateOptionsMenu();
}
});
}
private void slideback(Animator.AnimatorListener listener) {
View movingFragmentView = cardFrontFragment.getView();
PropertyValuesHolder rotateY = PropertyValuesHolder.ofFloat("rotationY", 15f);
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.8f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.8f);
PropertyValuesHolder translateX = PropertyValuesHolder.ofFloat("translationX", 500f);
ObjectAnimator movingFragmentAnimator = ObjectAnimator.
ofPropertyValuesHolder(movingFragmentView, rotateY, scaleX, scaleY, translateX);
ObjectAnimator movingFragmentRotator = ObjectAnimator.
ofFloat(movingFragmentView, "rotationY",15f, 0f);
movingFragmentAnimator.setDuration(DURATION_TIME);
movingFragmentRotator.setStartDelay(DELAY_TIME);
AnimatorSet s = new AnimatorSet();
s.playTogether(movingFragmentAnimator, movingFragmentRotator);
s.addListener(listener);
s.start();
}
private void slideForward (Animator.AnimatorListener listener) {
View movingFragmentView = cardFrontFragment.getView();
PropertyValuesHolder rotateY = PropertyValuesHolder.ofFloat("rotationY", 15f);
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
PropertyValuesHolder translateX = PropertyValuesHolder.ofFloat("translationX", 0f);
ObjectAnimator movingFragmentAnimator = ObjectAnimator.
ofPropertyValuesHolder(movingFragmentView, rotateY, scaleX, scaleY, translateX);
ObjectAnimator movingFragmentRotator = ObjectAnimator.
ofFloat(movingFragmentView, "rotationY",15f, 0f);
movingFragmentAnimator.setDuration(DURATION_TIME);
movingFragmentRotator.setStartDelay(DELAY_TIME);
movingFragmentRotator.setDuration(DURATION_TIME);
AnimatorSet s = new AnimatorSet();
s.playTogether(movingFragmentAnimator, movingFragmentRotator);
s.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mShowingBack = false;
}
});
s.start();
}
This allows you to set the properties for the outbound fragment dynamically during runtime and for some reason allows the fragment to remain in view.
I've a ScrollView in the PopupWindow. I'm animating ScrollView contents using TranslateAnimation.
When animation starts, the onAnimationStart listener is called but the onAnimationEnd is not getting called. Any ideas ?
Layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#drawable/popup_window_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="#dimen/toolbar_padding_left"
android:layout_height="#dimen/toolbar_height"/>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+web/toolbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none"
android:visibility="invisible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
...
</LinearLayout>
</ScrollView>
</LinearLayout>
Animation code :
mToolbar = mPopupContents.findViewById( R.web.toolbar );
TranslateAnimation anim =
new TranslateAnimation(0, 0, -60, 0);
anim.setDuration(1000);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation a) {
Log.d(LOGTAG, "---- animation start listener called" );
}
public void onAnimationRepeat(Animation a) {}
public void onAnimationEnd(Animation a) {
Log.d(LOGTAG, "---- animation end listener called" );
}
});
mToolbar.startAnimation(anim);
Update : I verified that the onAnimationEnd is called but it is called after some delay (provided you don't start the new animation during that time).
AnimationEnd is not reliable. If you don't want to rewrite your code with custom views that override OnAnimationEnd, use postDelayed.
Here's some example code:
final FadeUpAnimation anim = new FadeUpAnimation(v);
anim.setInterpolator(new AccelerateInterpolator());
anim.setDuration(1000);
anim.setFillAfter(true);
new Handler().postDelayed(new Runnable() {
public void run() {
v.clearAnimation();
//Extra work goes here
}
}, anim.getDuration());
v.startAnimation(anim);
While it MAY seem ugly, I can guarantee it's very reliable. I use it for ListViews that are inserting new rows while removing with animation to other rows. Stress testing a listener with AnimationEnd proved unreliable. Sometimes AnimationEnd was never triggered. You might want to reapply any transformation in the postDelayed function in case the animation didn't fully finish, but that really depends on what type of animation you're using.
Make sure that you are USING view.startAnimation(Animation)
AND NOT view.setAnimation(Animation). This simple confusion may be a problem.
After I don't remember how may posts read and days spent finding out a solution for this issue I discovered that if the object to move is not on the Screen region (for example is positioned out of the screen coords) OnAnimationEnd callback is not getting called. Probably the animation fails just after started (start method is called, I coded a listener) but nothing is wrote into logcat. Maybe this is not exactly your case but this finally solved my problem and hope it can help you too.
Also, when using animations, don't forget the setFillAfter() to true.
http://developer.android.com/reference/android/view/animation/Animation.html#setFillAfter(boolean)
If fillAfter is true, the transformation that this animation performed will persist when it is finished. Defaults to false if not set. Note that this applies when using an AnimationSet to chain animations. The transformation is not applied before the AnimationSet itself starts.
anim.setFillAfter(true);
mToolbar.startAnimation(anim);
For anyone stumbling upon this question: Consider switching over to using the Property Animation system instead http://developer.android.com/guide/topics/graphics/prop-animation.html
I've had several problems with the old way of animating a fade-in/out on a view (through AlphaAnimation). OnAnimationEnd was not being called etc ... With the Property Animation all those problems were resolved.
If you want to support API<11 devices, Jake Wharton's https://github.com/JakeWharton/NineOldAndroids is the way to go
Happened to me in a scenario that isn't described in other answers. Tried this and it didn't work:
ValueAnimator.ofInt(currentHeight, desiredHeight).apply {
duration = 500
addListener {
doOnEnd {
Log.d(TAG, "animate: animation ended")
}
}
start()
}
Changing to this resolved the issue. Unfortunately can't tell why.
ValueAnimator.ofInt(currentHeight, desiredHeight).apply {
duration = 500
doOnEnd {
Log.d(TAG, "animate: animation ended")
}
start()
}
The delay is probably due to the anim.setDuration(1000) or if you are doing this on a thread, then probably due to context switching. Try manipulating the delay time and see if you notice any difference.
Try to use overridePendingAnimation(int,int).
i.e.
overridePendingAnimation(0,0)
It will override your default animation and then you can define you own animation By calling the method startAnimation using the object of View.
Here is my example code. Dont know whether it will be helpful to you or not.
overridePendingTransition(0,0);
//finish();
v.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(), R.anim.fadeout));
startActivity(new Intent(ContentManagerActivity.this,Mainmenu_activity.class));
overridePendingTransition(R.anim.fadein,0);
There might be someone still having this issue and not found a solution -even though reads a lot of stackoverflow answers- like me!
So my case was: I used animatorSet and
there was not a single view that i could call clearAnimation on,
I wasn't calling my animation from backgroundThread -which you should never do that, btw-
As a solution, i did call animatorSet.end() right before animatorSet.start()
When you start an animation command in a view that is partially offscreen, the animation start and the onStartListener is called, but the animation doesn't run completely (somehow it is interrupted in the middle). My guess is that, as the view is offscreen, it is canceled and therefore it's onFinish is not called. As a workaround for that i created my own animation listener that start's an handler and uses postDelayed to notify the user of the animation end event. In Kotlin:
abstract class PartiallyOffScreenAnimationListener : Animation.AnimationListener, AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
onAnimationRepeat_(animation)
}
override fun onAnimationEnd(animation: Animation) {}
override fun onAnimationStart(animation: Animation) {
onAnimationStart_(animation)
Handler().postDelayed({
onAnimationEnd_(animation)
animation.setAnimationListener(null)
}, animation.duration + 50)
}
}
Please notice that, in the case the animation doesn't run completely, the view may be left at an inconsistent state (for example, animating layout parameters may lead to a weird middle interpolator factor being dropped. For that reason, you should verify at the end callback that the view is in the wanted state. If not, just set it manually.
Do it like belows
make animation final
invoke it inside a handler
listener should be instantiated once.
clear animation.
for example
view.clearAnimation();
new Hander().post(
run() {
final TranslateAnimation ani = new TranslateAnimation(0, 0, 0, 0);
ani.setAnimationListener(mListener);
}
);
private Animation.AnimationListener mListener = new Animation.AnimationListener() {
}
Are you not setting another animation before that one you are waiting ends?
Without context is hard to understand if it's something like that... but it is probably one of the only things that would cause it.
I guess Its gets called after sometime becasue you are using setDuration for animation and passing 1000 ms in it. Just pass 0 and it won't take a while to get called.
I tried your code it's working fine at OnAnimation start and inAmimationEnd also , after duration time means after finish animation onAnimationEnd is called , so your code working fine
TranslateAnimation anim =new TranslateAnimation(0, 0, -60, 0);
anim.setDuration(1000);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation a) {
Log.w("Start", "---- animation start listener called" );
}
public void onAnimationRepeat(Animation a) {}
public void onAnimationEnd(Animation a) {
Log.d(" end ","---- animation end listener called" );
}
});
mIv.setAnimation(anim);
mIv.startAnimation(anim);
Where do you start your animation ? If in onCreate, it is wrong. Try to do it in onResume.
I need to stop a running translate animation. The .cancel() method of Animation has no effect; the animation goes until the end anyway.
How do you cancel a running animation?
Call clearAnimation() on whichever View you called startAnimation().
On Android 4.4.4, it seems the only way I could stop an alpha fading animation on a View was calling View.animate().cancel() (i.e., calling .cancel() on the View's ViewPropertyAnimator).
Here's the code I'm using for compatibility before and after ICS:
public void stopAnimation(View v) {
v.clearAnimation();
if (canCancelAnimation()) {
v.animate().cancel();
}
}
... with the method:
/**
* Returns true if the API level supports canceling existing animations via the
* ViewPropertyAnimator, and false if it does not
* #return true if the API level supports canceling existing animations via the
* ViewPropertyAnimator, and false if it does not
*/
public static boolean canCancelAnimation() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
}
Here's the animation that I'm stopping:
v.setAlpha(0f);
v.setVisibility(View.VISIBLE);
// Animate the content view to 100% opacity, and clear any animation listener set on the view.
v.animate()
.alpha(1f)
.setDuration(animationDuration)
.setListener(null);
If you are using the animation listener, set v.setAnimationListener(null). Use the following code with all options.
v.getAnimation().cancel();
v.clearAnimation();
animation.setAnimationListener(null);
You must use .clearAnimation(); method in UI thread:
runOnUiThread(new Runnable() {
#Override
public void run() {
v.clearAnimation();
}
});
What you can try to do is get the transformation Matrix from the animation before you stop it and inspect the Matrix contents to get the position values you are looking for.
Here are the api's you should look into
public boolean getTransformation (long currentTime, Transformation outTransformation)
public Matrix getMatrix ()
public void getValues (float[] values)
So for example (some pseudo code. I have not tested this):
Transformation outTransformation = new Transformation();
myAnimation.getTransformation(currentTime, outTransformation);
Matrix transformationMatrix = outTransformation.getMatrix();
float[] matrixValues = new float[9];
transformationMatrix.getValues(matrixValues);
float transX = matrixValues[Matrix.MTRANS_X];
float transY = matrixValues[Matrix.MTRANS_Y];
Use the method setAnimation(null) to stop an animation, it exposed as public method in
View.java, it is the base class for all widgets, which are used to create interactive UI components (buttons, text fields, etc.).
/**
* Sets the next animation to play for this view.
* If you want the animation to play immediately, use
* {#link #startAnimation(android.view.animation.Animation)} instead.
* This method provides allows fine-grained
* control over the start time and invalidation, but you
* must make sure that 1) the animation has a start time set, and
* 2) the view's parent (which controls animations on its children)
* will be invalidated when the animation is supposed to
* start.
*
* #param animation The next animation, or null.
*/
public void setAnimation(Animation animation)
To stop animation you may set such objectAnimator that do nothing, e.g.
first when manual flipping there is animation left to right:
flipper.setInAnimation(leftIn);
flipper.setOutAnimation(rightOut);
then when switching to auto flipping there's no animation
flipper.setInAnimation(doNothing);
flipper.setOutAnimation(doNothing);
doNothing = ObjectAnimator.ofFloat(flipper, "x", 0f, 0f).setDuration(flipperSwipingDuration);
First of all, remove all the listeners which are related to the animatior or animator set. Then cancel the animator or animator set.
inwardAnimationSet.removeAllListeners()
inwardAnimationSet.cancel()
use this way:
// start animation
TranslateAnimation anim = new TranslateAnimation( 0, 100 , 0, 100);
anim.setDuration(1000);
anim.setFillAfter( true );
view.startAnimation(anim);
// end animation or cancel that
view.getAnimation().cancel();
view.clearAnimation();
cancel()
Cancel the animation. Canceling an animation invokes the animation
listener, if set, to notify the end of the animation.
If you cancel an animation manually, you must call reset()
before starting the animation again.
clearAnimation()
Cancels any animations for this view.
After going through all the things nothing worked. As I applied multiple animations in my views.
So below is the code that worked for me.
To start the animation that fades in and fade out continuously
val mAnimationSet = AnimatorSet()
private fun performFadeAnimation() {
val fadeOut: ObjectAnimator = ObjectAnimator.ofFloat(clScanPage, "alpha", 1f, 0f)
fadeOut.duration = 1000
val fadeIn: ObjectAnimator = ObjectAnimator.ofFloat(clScanPage, "alpha", 0f, 1f)
fadeIn.duration = 1000
mAnimationSet.play(fadeIn).after(fadeOut)
mAnimationSet.addListener(animationListener)
mAnimationSet.start()
}
The animation listener that loops in continuously
private val animationListener=object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
mAnimationSet.start()
}
}
To stop the animation that going on in a loop. I did the following.
private fun stopAnimation() {
mAnimationSet.removeAllListeners()
}
Since none of other answers mention it, you can easily stop an animation using ValueAnimator's cancel().
ValueAnimator is very powerful for making animations. Here is a sample code for creating a translation animation using ValueAnimator:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 5f);
int mDuration = 5000; //in millis
valueAnimator.setDuration(mDuration);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
// Update your view's x or y coordinate
}
});
valueAnimator.start();
You then stop the animation by calling
valueAnimator.cancel()`