Android circular reveal animation too fast - android

I am using the circular reveal animation in my project, but it is now working correctly. The problem is the reveal happens way to fast and you almost can't see the reveal because it expands like instantly. I tried setting anim.setDuration() but that did not change anything. I used the code from the google examples.
Here my code:
View myView = getActivity().findViewById(R.id.view_to_expand);
int cx = myView.getRight();
int cy = myView.getBottom();
int finalRadius = Math.max(myView.getWidth(), myView.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
myView.setVisibility(View.VISIBLE);
anim.start();
view_to_expand is a simple relativelayout, don't think the problem is there. Also how can I apply the circular reveal to animation transitions?

The instantaneous expansion is because there is some other heavy work on main thread aside from your animation (data update, other views/fragments hiding, render refresh and so on).
Best bet is to wrap it into a runnable and add it to the stack. It will be called when there is available CPU cycle and will show your animation.
Below is the right way to create and use your runnable. Try not to post an anonymous one since there is possibility user to return and your runnable to hang and expose a memory leak and/or to throw a runtime exception.
I presume here your class where your view stays is an activity and your myView is an instance reference in your activity
public final ApplicationActivity extends Activity {
private View myView;
private final Runnable revealAnimationRunnable = new Runnable() {
#Override
public void run() {
int cx = myView.getRight();
int cy = myView.getBottom();
int finalRadius = Math.max(myView.getWidth(), myView.getHeight());
Animator animator = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
animator.start();
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
...
myView = findViewById(R.id.view_to_expand);
myView.setVisibility(View.VISIBLE);
myView.post(revealAnimationRunnable);
// alternatively, in case load is way too big, you can post with delay
// i.e. comment above line and uncomment the one below
// myView.postDelayed(revealAnimationRunnable, 200);
}
#Override
protected void onDestroy() {
...
myView.removeCallbacks(revealAnimationRunnable);
}
}

Related

CircularReveal animation doesn't work on first attempt

In android 5.0 i am trying to work with circular reveal animation
Problem
When i click on button to start reveal animation, on first click animation doesn't start
Second Click onwards it works normally
My Code
public class MainActivity extends ActionBarActivity {
Animator a;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View cardType = findViewById(R.id.cardtype);
cardType.setVisibility(View.GONE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
a = ViewAnimationUtils.createCircularReveal(cardType,
cardType.getWidth(),
cardType.getHeight(),
0,
cardType.getHeight() * 2)
.setDuration(2500);
a.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
cardType.setVisibility(View.VISIBLE);
}
});
a.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
cardType.setVisibility(View.GONE);
}
});
findViewById(R.id.icon_first_activity).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
a.start();
}
});
}
}}
I haven't tried your code, but I think you have a small ordering problem. I think you just need to set the cardType visible before you start the animation.
Edited to add:
... and you should be setting your button View.INVISIBLE, not View.GONE.
Here: This code works.
Edited once more to add:
Yes. Your problem is that you set the view GONE initially. That means it has 0 size. Then you use cardType.getHeight and cardType.getWidth as reveal coordinates. They are 0. You are going to want to set the view INVISIBLE, initially, and then use width/2 and height/2 as the center of the reveal.
Basically what others answers say, it's correct, but the problem is if you want visibility GONE (because your layout requires it GONE!) you have to set visibility INVISIBLE in the xml with height 0dp (and/or width 0dp as well) and programmatically set the correct LayoutParams even inside the click event it will work. For example my code:
...
expandButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//To not have empty scroll, the container is INVISIBLE with 0dp height.
//Otherwise the Reveal effect will not work at the first click.
//Here I set the parameters programmatically.
viewContainer.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
if (viewContainer.getVisibility() == View.VISIBLE) {
expandButton.animate().rotation(0f).setDuration(duration).start();
Utils.unReveal(viewContainer, 0, 0);
} else {
expandButton.animate().rotation(180f).setDuration(duration).start();
Utils.reveal(viewContainer, viewContainer.getWidth(), 0);
}
}
});
...
#TargetApi(VERSION_CODES.LOLLIPOP)
public static void reveal(final View view, int cx, int cy) {
if (!hasLollipop()) {
view.setVisibility(View.VISIBLE);
return;
}
//Get the final radius for the clipping circle
int finalRadius = Math.max(view.getWidth(), view.getHeight());
//Create the animator for this view (the start radius is zero)
Animator animator =
ViewAnimationUtils.createCircularReveal(view, cx, cy, 0, finalRadius);
//Make the view VISIBLE and start the animation
view.setVisibility(View.VISIBLE);
animator.start();
}
#TargetApi(VERSION_CODES.LOLLIPOP)
public static void unReveal(final View view, int cx, int cy) {
if (!hasLollipop()) {
view.setVisibility(View.GONE);
return;
}
//Get the initial radius for the clipping circle
int initialRadius = view.getWidth();
//Create the animation (the final radius is zero)
Animator animator =
ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius, 0);
//Make the view GONE when the animation is done
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.GONE);
}
});
//Start the animation
animator.start();
}
If you set only GONE in the xml, the first time will never work because height/width/x/y/etc.. are 0. Also, if you just set INVISIBLE before the call to the animation it will not work as well, but if you start with visibility INVISIBLE it will initialize the layout params.
what i did is, Like i have two view with same height,As we now visibility gone returns 0 {height and width} than i am giving visible view height every time and its work for me.
The solution is don't get values directly into code
Either put the animation code on click and the values outside onclick
or get the values from other activity
By values i mean cardType.getWidth() and cardType.getHeight()

Animation - why is applyTransformation only called when animating in one direction?

Here's the animation:
public class WidthAnimation extends Animation {
protected final int originalWidth;
protected final View view;
protected float perValue;
public WidthAnimation(View view, int fromWidth, int toWidth) {
this.view = view;
this.originalWidth = fromWidth;
this.perValue = (toWidth - fromWidth);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().width = (int) (originalWidth + perValue * interpolatedTime);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
When called by this (animating the View to be zero width), it works fine:
WidthAnimation widthAnim = new WidthAnimation(dashboardContainerView, getWindowWidthInPixels(), 0);
widthAnim.setDuration(500);
dashboardContainerView.startAnimation(widthAnim);
But when called by this (animating the View to being displayed), applyTransform is not called, and the animation is not shown:
WidthAnimation widthAnim = new WidthAnimation(dashboardContainerView, 0, getWindowWidthInPixels());
widthAnim.setDuration(500);
dashboardContainerView.startAnimation(widthAnim);
Both animations are being triggered by screen clicks. The getWindowWidthInPixels() method works correctly. I've seen several other questions on SO that suggest calling invalidate() or requestLayout() on the View, or its parent, can resolve this, but for me those solutions do not work.
Wow, that is weird, that it would work to shrink but not grow. Maybe Android is discarding the startAnimation call because the view is width 0. I suppose one quick fix would be to set the width to 1 before starting the grow animation.
dashboardContainerView.getLayoutParams().width = 1;
WidthAnimation widthAnim = new WidthAnimation(dashboardContainerView, 0, getWindowWidthInPixels());
widthAnim.setDuration(500);
dashboardContainerView.startAnimation(widthAnim);
Another option would be to switch to ObjectAnimator. My animations got a lot more straightforward when I did that. Yours would look something like this:
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dashboardWidth", fromWidth, toWidth);
anim.setDuration(500);
dashboardContainerView.startAnimation(anim);
public void setDashboardWidth(int width) {
ViewGroup.LayoutParams params = dashboardContainerView.getLayoutParams();
params.width = width;
dashboardContainerView.setLayoutParams(params);
}
That is the pattern I usually use with success. I don't have to call requestLayout in that case, but maybe setLayoutParams is doing that. Maybe just the getLayoutParams and requestLayout that you are using would be fine too.
Anyway, I really like the ObjectAnimator because it is so simple. One thing you do have to watch out for is if you are using ProGuard, you have to make sure it doesn't clean out the setDashboardWidth method, because it is not directly called in your code. It is called from within ObjectAnimator, which finds the method using reflection. Thus the exact name and signature of the method has to match the "dashboardWidth" property name in the call to ofInt.

Custom circular reveal transition results in "java.lang.UnsupportedOperationException" when paused?

I created a custom circular reveal transition to use as part of an Activity's enter transition (specifically, I am setting the transition as the window's enter transition by calling Window#setEnterTransition()):
public class CircularRevealTransition extends Visibility {
private final Rect mStartBounds = new Rect();
/**
* Use the view's location as the circular reveal's starting position.
*/
public CircularRevealTransition(View v) {
int[] loc = new int[2];
v.getLocationInWindow(loc);
mStartBounds.set(loc[0], loc[1], loc[0] + v.getWidth(), loc[1] + v.getHeight());
}
#Override
public Animator onAppear(ViewGroup sceneRoot, final View v, TransitionValues startValues, TransitionValues endValues) {
if (endValues == null) {
return null;
}
int halfWidth = v.getWidth() / 2;
int halfHeight = v.getHeight() / 2;
float startX = mStartBounds.left + mStartBounds.width() / 2 - halfWidth;
float startY = mStartBounds.top + mStartBounds.height() / 2 - halfHeight;
float endX = v.getTranslationX();
float endY = v.getTranslationY();
v.setTranslationX(startX);
v.setTranslationY(startY);
// Create a circular reveal animator to play behind a shared
// element during the Activity Transition.
Animator revealAnimator = ViewAnimationUtils.createCircularReveal(v, halfWidth, halfHeight, 0f,
FloatMath.sqrt(halfWidth * halfHeight + halfHeight * halfHeight));
revealAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
// Set the view's visibility to VISIBLE to prevent the
// reveal from "blinking" at the end of the animation.
v.setVisibility(View.VISIBLE);
}
});
// Translate the circular reveal into place as it animates.
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("translationX", startX, endX);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("translationY", startY, endY);
Animator translationAnimator = ObjectAnimator.ofPropertyValuesHolder(v, pvhX, pvhY);
AnimatorSet anim = new AnimatorSet();
anim.setInterpolator(getInterpolator());
anim.playTogether(revealAnimator, translationAnimator);
return anim;
}
}
This works OK normally. However, when I click the "back button" in the middle of the transition, I get the following exception:
Process: com.adp.activity.transitions, PID: 13800
java.lang.UnsupportedOperationException
at android.view.RenderNodeAnimator.pause(RenderNodeAnimator.java:251)
at android.animation.AnimatorSet.pause(AnimatorSet.java:472)
at android.transition.Transition.pause(Transition.java:1671)
at android.transition.TransitionSet.pause(TransitionSet.java:483)
at android.app.ActivityTransitionState.startExitBackTransition(ActivityTransitionState.java:269)
at android.app.Activity.finishAfterTransition(Activity.java:4672)
at com.adp.activity.transitions.DetailsActivity.finishAfterTransition(DetailsActivity.java:167)
at android.app.Activity.onBackPressed(Activity.java:2480)
Is there any specific reason why I am getting this error? How should it be avoided?
You will need to create a subclass of Animator that ignores calls to pause() and resume() in order to avoid this exception.
For more details, I just finished a post about this topic below:
Part 1: http://halfthought.wordpress.com/2014/11/07/reveal-transition/
Part 2: https://halfthought.wordpress.com/2014/12/02/reveal-activity-transitions/
Is there any specific reason why I am getting this error?
ViewAnimationUtils.createCircularReveal is a shortcut for creating a new RevealAnimator, which is a subclass of RenderNodeAnimator. By default, RenderNodeAnimator.pause throws an UnsupportedOperationException. You see this occur here in your stack trace:
java.lang.UnsupportedOperationException
at android.view.RenderNodeAnimator.pause(RenderNodeAnimator.java:251)
When Activity.onBackPressed is called in Lollipop, it makes a new call to Activity.finishAfterTransition, which eventually makes a call back to Animator.pause in Transition.pause(android.view.View), which is when your UnsupportedOperationException is finally thrown.
The reason it isn't thrown when using the "back" button after the transition is complete, is due to how the EnterTransitionCoordinator handles the entering Transition once it's completed.
How should it be avoided?
I suppose you have a couple of options, but neither are really ideal:
Option 1
Attach a TransitionListener when you call Window.setEnterTransition so you can monitor when to invoke the "back" button. So, something like:
public class YourActivity extends Activity {
/** True if the current window transition is animating, false otherwise */
private boolean mIsAnimating = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Get the Window and enable Activity transitions
final Window window = getWindow();
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// Call through to super
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_child);
// Set the window transition and attach our listener
final Transition circularReveal = new CircularRevealTransition(yourView);
window.setEnterTransition(circularReveal.addListener(new TransitionListenerAdapter() {
#Override
public void onTransitionEnd(Transition transition) {
super.onTransitionEnd(transition);
mIsAnimating = false;
}
}));
// Restore the transition state if available
if (savedInstanceState != null) {
mIsAnimating = savedInstanceState.getBoolean("key");
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the current transition state
outState.putBoolean("key", mIsAnimating);
}
#Override
public void onBackPressed() {
if (!mIsAnimating) {
super.onBackPressed();
}
}
}
Option 2
Use reflection to call ActivityTransitionState.clear, which will stop Transition.pause(android.view.View) from being called in ActivityTransitionState.startExitBackTransition.
#Override
public void onBackPressed() {
if (!mIsAnimating) {
super.onBackPressed();
} else {
clearTransitionState();
super.onBackPressed();
}
}
private void clearTransitionState() {
try {
// Get the ActivityTransitionState Field
final Field atsf = Activity.class.getDeclaredField("mActivityTransitionState");
atsf.setAccessible(true);
// Get the ActivityTransitionState
final Object ats = atsf.get(this);
// Invoke the ActivityTransitionState.clear Method
final Method clear = ats.getClass().getDeclaredMethod("clear", (Class[]) null);
clear.invoke(ats);
} catch (final Exception ignored) {
// Nothing to do
}
}
Obviously each has drawbacks. Option 1 basically disables the "back" button until the transition is complete. Option 2 allows you to interrupt using the "back" button, but clears the transition state and uses reflection.
Here's a gfy of the results. You can see how it completely transitions from "A" to "M" and back again, then the "back" button interrupts the transition and goes back to "A". That'll make more sense if you watch it.
At any rate, I hope that helps you out some.
You can add listener to enter transition that sets flag transitionInProgress in methods onTransitionStart() / onTransitionEnd(). Then, you can override method finishAfterTransition() and then check transitionInProgress flag, and call super only if transition finished. Otherwise you can just finish() your Activity or do nothing.
override fun finishAfterTransition() {
if (!transitionInProgress){
super.finishAfterTransition()
} else {
finish()
}
}

How to generate looping animation with ViewPropertyAnimator?

I want to build an animation of TextViews, which repeats itself just after completion.
For each View I want to animate, I use the following piece of code
final float oldX = v.getX();
final float newX = v.getX() - (float)totalWidth;
final AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
v.setX(oldX);
animFinished = true;
//This line won't compile
//v.animate().setDuration(animDuration).setInterpolator(newsInterpolator)
// .setListener(listener).x(newX);
}
};
v.animate().setDuration(animDuration).setInterpolator(newsInterpolator)
.setListener(listener).x(newX);
I tried to place the last piece of code into the onAnimationEnd, but Java will not compile since it considers the object listener as not initialized. Moreover, I don't think that this "recursive" animation invocation is a good solution, it was the first thing which came to my mind. I am suspicious that there is a simple and sound way to implement looping property animation, but I failed to locate it, so I turned here for help.
Thanks in advance
Well, I am going to answer myself again.
TranslateAnimation class has methods about repeating the animation, so I used it instead of ViewPropertyAnimator.
The following code seems to work:
long duration = 1000* ((long)totalWidth / newsScrollSpeed);
System.out.println("totalWidth="+totalWidth);
TranslateAnimation anim = new TranslateAnimation(0,-totalWidth,0,0);
anim.setInterpolator(linearInterpolator);
anim.setDuration(duration);
anim.setRepeatCount(TranslateAnimation.INFINITE);
anim.setRepeatMode(TranslateAnimation.RESTART);
for(i=0;i<this.getChildCount();i++)
{
View v = this.getChildAt(i);
if(v.getId() == R.id.yuruyen_yazi)
{
continue;
}
v.startAnimation(anim);
}
Not elegant way, but it works:
Runnable runnable = new Runnable() {
#Override
public void run() {
// update newX
v.animate().setDuration(animDuration).setInterpolator(newsInterpolator).x(newX).withEndAction(this).start();
}
};
v.animate().setDuration(animDuration).setInterpolator(newsInterpolator).x(newX).withEndAction(runnable).start();

Android: Is there any way to get the latest position of View after TranslateAnimation?

I know multiple ways to get location values of a View.
getLocationOnScreen()
getLocationInWindow()
getLeft()
However, none of them actually returns the current location of the View I moved by startAnimation() method, but only the original location.
So, now let's make a View that moves to the right by 10 pixels on each Click (I'm omitting the layout, since you can just place whatever view in your main XML and give it onClickListener).
public class AndroidTestActivity extends Activity implements OnClickListener {
LinearLayout testView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
testView = (LinearLayout) this.findViewById(R.id.test);
testView.setOnClickListener(this);
}
public void onClick(View v) {
int[] pLoS = new int[2];
testView.getLocationOnScreen(pLoS);
TranslateAnimation move = new TranslateAnimation(pLoS[0], pLoS[0] + 10, 0f, 0f);
move.setFillAfter(true);
move.setFillEnabled(true);
testView.startAnimation(move);
}
}
As you see, this doesn't work as I intended, since getLocationOnScreen() always returns the same value (in my case, 0), and doen't reflect the value I used in TranslateAnimation...
Any idea?
Assuming you're using Android < 3.0 then your question may be in a similar vein to mine I asked here. Basically Animations are separate from the View itself i.e. Android animates a copy of your View. That is why getLocationOnScreen() always returns 0. It's not the view that has moved (animated) it was the copy that moved (animated). If you see the answers to my question this issue has been addressed in later versions of Android.
Well, if you are trying to see how many pixels the view has shifted during/after its animation, then you can open up the Transformation object on the animation.
Here's an example using the AnimationListener:
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
Transformation trans = new Transformation();
// startTime + duration = end of animation
int endTime = animation.getStartTime()+animation.getDuration();
animation.getTransformation(endTime, trans);
Matrix transformationMatrix = trans.getMatrix();
float[] matrixVals = new float[9];
transformationMatrix.getValues(matrixVals);
float xTraveled = matrixVals[2];
float yTraveled = matrixVals[5];
// do something with them here...
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
I may not be exactly correct in the array indices for these, definitely use Matrix.toString() and look at the values yourself. xTraveled and yTraveled will give you the amount of distance traveled by the TranslateAnimation at the indicated time (in this case, at the end of the animation).
animations on android gingerbread and below do not really change the view in any way , only change the way it is shown.
the only way to get the new position is by calculating it.

Categories

Resources