Android CircularReveal library View visibility bug - android

I use this libary to create CircularReveal animation on pre-Lollipop devices. The problem is in hiding View with animation. Animation is performed as well, but after animation end, View become visible for a second and then disappears. How can I prevent blinking of View?
Here is my method for hiding View with CircularReveal animation:
public static void revealCloseTopRight(final View view) {
int cx = view.getRight();
int cy = view.getTop();
// get the final radius for the clipping circle
int dx = Math.max(cx, view.getWidth() - cx);
int dy = Math.max(cy, view.getHeight() - cy);
float finalRadius = (float) Math.hypot(dx, dy);
SupportAnimator animator = ViewAnimationUtils.createCircularReveal(view, cx, cy, 0, finalRadius);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(animDuration);
animator = animator.reverse();
try {
animator.start();
} catch (Exception ex) {
ex.printStackTrace();
}
view.postDelayed(new Runnable() {
#Override
public void run() {
view.setVisibility(View.INVISIBLE);
}
}, animDuration);
}
UPDATE
I also tried to add SupportAnimator.AnimatorListener() like this:
animator.addListener(new SupportAnimator.AnimatorListener() {
#Override
public void onAnimationStart() {
Log.d(AnimationSupport.TAG, TAG + " -> 1onAnimationStart()");
}
#Override
public void onAnimationEnd() {
Log.d(AnimationSupport.TAG, TAG + " -> 1onAnimationEnd()");
view.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationCancel() {
Log.d(AnimationSupport.TAG, TAG + " -> 1onAnimationCancel()");
}
#Override
public void onAnimationRepeat() {
Log.d(AnimationSupport.TAG, TAG + " -> 1onAnimationRepeat()");
}
});
And Animator.AnimatorListener() like this:
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
Log.d(AnimationSupport.TAG, TAG + " -> onAnimationStart()");
}
#Override
public void onAnimationEnd(Animator animation) {
Log.d(AnimationSupport.TAG, TAG + " -> onAnimationEnd()");
view.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationCancel(Animator animation) {
Log.d(AnimationSupport.TAG, TAG + " -> onAnimationCancel()");
}
#Override
public void onAnimationRepeat(Animator animation) {
Log.d(AnimationSupport.TAG, TAG + " -> onAnimationRepeat()");
}
});
In both cases none on this callbacks are never called. I have no idea why.

Make sure that in your layout where you're trying to animate the view, there is no
android:animateLayoutChanges="true"
property in the root viewGroup.
Removing that will help you to overcome blinking view after animation end and setting visibility to GONE (or INVISIBLE).

The view is being visible because there is a very small delay between the animation completing and your handler's execution.
You can solve this problem by adding an animation listener to your circular reveal animation and setting the view to invisible in the onAnimationEnd() callback.

Related

Android circle reveal animation not smooth

am creating login and signup in same page with circle reveal animation ,switch the view using a floating action button, it very lag when switching one view to another view,
Login page Contain two relative layout and a floating action button
one view for login and other for signup
when floating action button click view will switch to another ie login to signup and vise versa.. i achieved but it is too lag how can i make it smooth
in tab(big screen) it works very smoothly(am creating two layout one for mobile and other for tab)
can anyone help me.. this is my code..
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void viewMenu() {
if (!isOpen) {
// int x = layoutContent.getRight();
// int y = layoutContent.getBottom();
int x = Math.round(fab.getX() + fab.getWidth() / 2);
int y = Math.round(fab.getY() - fab.getHeight());
int startRadius = 0;
int endRadius = (int) Math.hypot(layoutMain.getWidth(), layoutMain.getHeight());
fab.setBackgroundTintList(ColorStateList.valueOf(ResourcesCompat.getColor(getResources(),android.R.color.white,null)));
fab.setImageResource(R.drawable.ic_cross);
Animator anim = ViewAnimationUtils.createCircularReveal(layoutButtons, x, y, startRadius, endRadius);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
layoutButtons.setVisibility(View.VISIBLE);
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
// fst_view.setVisibility(View.GONE);
}
#Override
public void onAnimationEnd(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
anim.start();
isOpen = true;
} else {
// int x = layoutButtons.getRight();
// int y = layoutButtons.getBottom();
int x = Math.round(fab.getX() + fab.getWidth() / 2);
int y = Math.round(fab.getY() + fab.getHeight()/2) - toolbar.getHeight();
int startRadius = Math.max(layoutContent.getWidth(), layoutContent.getHeight());
int endRadius = 0;
fab.setBackgroundTintList(ColorStateList.valueOf(ResourcesCompat.getColor(getResources(),R.color.colorAccent,null)));
fab.setImageResource(R.drawable.ic_menu_camera);
// fst_view.setVisibility(View.VISIBLE);
Animator anim = ViewAnimationUtils.createCircularReveal(layoutButtons, x, y, startRadius, endRadius);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationEnd(Animator animator) {
layoutButtons.setVisibility(View.GONE);
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
anim.start();
isOpen = false;
}
}
As far as I can see, you could use FastOutLinearInInterpolator() instead of AccelerateDecelerateInterpolator(), and add anim.setDuration() with animation duration you want.
And set layoutButtons.setVisibility() to View.INVISIBLE like described here in the bottom of the article.
Furthermore, you can rid of from Math.round() when you calculate view x, y coordinates.
It worked great for my case.
Here is my example code

ValueAnimator has an infinite call of cancel () if it is called from startListener

I have two instances of ValueAnimator that are started inside onClickListener - one moves the view up, another moves the same view down. Each animation has a startListener, so that when one starts, the second one is called cancel(). Ie if the animation, say up, interrupt in the middle, - cause the animation to go down, then cancel() should work, and it works, but if i set startOffset on both animations, cancel() in startListener works many times (as in a loop), and then i have a stackOverFlowError.
I noticed that the same error will be without startOffset, but if i call both animation starts at the same view without calling cancel() at the first animation. But cancel() is called inside startListener(!). And startListener does not works when the animation starts native with anim.start(), only after the end of the startOffset's delay.
Code:
Float firstLevelUp = 200f;
Float firstLevelDown = 0f;
long duration = 1500l;
ValueAnimator valueAnimatorView1Down = ValueAnimator.ofFloat(firstLevelUp,firstLevelDown);
ValueAnimator valueAnimatorView1Up = ValueAnimator.ofFloat(firstLevelUp,firstLevelDown);
public void initAnim(final View view){
valueAnimatorView1Down.setDuration(duration);
valueAnimatorView1Up.setDuration(duration);
valueAnimatorView1Down.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
if (valueAnimatorView1Up.isStarted()) {
valueAnimatorView1Up.cancel();
valueAnimatorView1Down.setCurrentPlayTime(duration - valueAnimatorView1Up.getCurrentPlayTime());
Log.i("Down View1 animation " , "STARTED WITH CANCEL");
}else {
Log.i("Down View1 animation " , "STARTED WITHOUT CANCEL");
}
}
#Override
public void onAnimationCancel(Animator animation) {
Log.i("DownView1 animation " , "CANCELED");
}
});
valueAnimatorView1Up.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
if (valueAnimatorView1Down.isStarted()) {
valueAnimatorView1Down.cancel();
valueAnimatorView1Up.setCurrentPlayTime(duration - valueAnimatorView1Down.getCurrentPlayTime());
Log.i("Up View1 animation " , "STARTED WITH CANCEL");
}else {
Log.i("Up View1 animation " , "STARTED WITHOUT CANCEL");
}
}
#Override
public void onAnimationCancel(Animator animation) {
Log.i("Up View1 animation " , "CANCELED");
}
});
valueAnimatorView1Down.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationY((Float) valueAnimatorView1Down.getAnimatedValue());
}
});
valueAnimatorView1Up.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationY((Float) valueAnimatorView1Up.getAnimatedValue());
}
});
}
public void startViewAnimation(boolean side) {
if (side) {
Log.i("Down animations ", "start()");
valueAnimatorView1Down.setStartDelay(800);
valueAnimatorView1Down.start();
} else {
Log.i("Up animations ", "start()");
valueAnimatorView1Up.setStartDelay(800);
valueAnimatorView1Up.start();
}
}
}
Questions: why is the cancel() on the onStartListener invoked in loop + stackOverFlowError, if i change the animation to the second one, before the startOffset is over in the first (although if i call a new animation after the start delay - everything works correctly) and how to fix it?

Android ObjectAnimator inside custom view

I have a custom view kind of like a progress bar. inside this view i have an ObjectAnimator.AnimatorUpdateListener which i'm trying to use to call invalidate on the view. However, my view is not updating! I tried to add a button which simply changed the value to something else and called invalidate once and it worked, my view updated to reflect the value change.
Am I missing something here? Am I calling invalidate too many times or something?
My "progressbar" starts with a float at 0, and the animation is supposed to animate it to 100. Calling a method to update it to 50 and call invalidate works, but the ObjectAnimator doesnt seem to be calling invalidate.
Everything is being called on the UI Thread
ObjectAnimator does not call invalidate() - your method should do it when needed
ObjectAnimator oAnimator = ObjectAnimator.ofInt(view,"someproperty",0,100)
void setSomeProperty(value) {
mValue = value
invalidate()
}
I have used this and it worked.
public interface ProgressAnimationListener {
public void onAnimationStart();
public void onAnimationFinish();
public void onAnimationProgress(int progress);
}
private ObjectAnimator progressBarAnimator;
public synchronized void animateProgressTo(final int start, final int end, final int duration, final ProgressAnimationListener listener) {
stopAnimation();
setProgress(start);
progressBarAnimator = ObjectAnimator.ofFloat(this, "animateProgress", start, end);
progressBarAnimator.setDuration(duration);
progressBarAnimator.setInterpolator(new LinearInterpolator());
progressBarAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationCancel(final Animator animation) {
}
#Override
public void onAnimationEnd(final Animator animation) {
setProgress(end);
if (listener != null) {
listener.onAnimationFinish();
}
}
#Override
public void onAnimationRepeat(final Animator animation) {
}
#Override
public void onAnimationStart(final Animator animation) {
if (listener != null) {
listener.onAnimationStart();
}
}
});
progressBarAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(final ValueAnimator animation) {
int progress = ((Float) animation.getAnimatedValue()).intValue();
if (progress != getProgress()) {
//Log.d(TAG, progress + "");
setProgress(progress);
if (listener != null) {
listener.onAnimationProgress(progress);
}
}
}
});
progressBarAnimator.start();
}
public synchronized boolean isAnimationRunning() {
return progressBarAnimator != null && progressBarAnimator.isRunning();
}
public synchronized void stopAnimation() {
if (isAnimationRunning()) {
progressBarAnimator.cancel();
progressBarAnimator = null;
}
}

Api 21 Circular Reveal Animation not working

I can't get the circular reveal animation to work.
I think I checked the most obvious things:
It starts, width and height are > 0 and it is visible, no Exception..
I load some data from the internet and display it in the view(fab)
The animation should only play after the download finishes.
TmdbHelper helper = new TmdbHelper();
helper.getMovieById(id, "en", new TmdbHelper.ResultListener() {
#Override
public void onResultReceived(JSONObject result) {
// called when finished downloading
try {
String rating = result.getString("vote_average");
AnimationHelper.circularReveal(fab, 500, 0);
fab.setText(rating);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
AnimationHelper:
public static void circularReveal(final View view, final long duration, long startDelay) {
// get the center for the clipping circle
int cx = (view.getLeft() + view.getRight()) / 2;
int cy = (view.getTop() + view.getBottom()) / 2;
// 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 anim =
ViewAnimationUtils.createCircularReveal(view, cx, cy, 0, finalRadius);
anim.setDuration(duration);
anim.setStartDelay(startDelay);
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
view.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {}
});
// make the view visible and start the animation
anim.start();
}
I use the circular reveal animation in other parts like this to make sure the view is attached, and it works:
headerContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
headerContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
AnimationHelper.circularReveal(headerContainer, 500, 200);
}
});
Perhaps you should erase this line inside your onResultRecieved():
fab.setVisibility(View.VISIBLE);
My assumption is that your circular reveal method is working just fine. It is because of you have made the FAB visible before the animation even begin, you can't see it in action.
As an addition, those lines you've shown which is working doesn't have fab.setVisibility(View.VISIBLE) called anywhere in it.
1st Approach:
Try Transition Listener.
getWindow().getSharedElementExitTransition().addListener(new Transition.TransitionListener() {
#Override
public void onTransitionStart(Transition transition) {
}
#Override
public void onTransitionEnd(Transition transition) {
}
#Override
public void onTransitionCancel(Transition transition) {
}
#Override
public void onTransitionPause(Transition transition) {
}
#Override
public void onTransitionResume(Transition transition) {
}
});
2nd Approach: Try setting start delay and listener to the reveal animation and when animation starts then set the view visible
if (Build.VERSION.SDK_INT >= 21) {
Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);
anim.setStartDelay(300);
anim.setDuration(1000);
anim.setInterpolator(new DecelerateInterpolator());
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
viewRoot.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.start();
}
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.start();

Android: Is there a .TranslationX() change listener for a view

I have a button in my layout. And I am animating the position of that button using ObjectAnimator with translationX animation.
ObjectAnimator btnAnimator = ObjectAnimator.ofFloat(myBtn, "translationX",
ViewHelper.getTranslationX(myBtn), 0);
btnAnimator.addListener(new AnimatorListener() {
#Override
public void onAnimationCancel(Animator arg0) {}
#Override
public void onAnimationEnd(Animator arg0) {
Log.i("TAG","Animation Finished");
}
#Override
public void onAnimationRepeat(Animator arg0) {}
#Override
public void onAnimationStart(Animator arg0) {}
});
btnAnimator.setDuration(animationSpeed).start();
Now I would like to have a listener for the TranslationX of that button to notify whenever the TranslationX position of the button changes.
Here's an easy way I found to do what you're after:
btnAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.e("TAG", "translateX: "+animation.getAnimatedValue("translationX"));
}
});
btnAnimator.setDuration(animationSpeed).start();
Two possible approaches:
1) Override onLayout() in your view to manually compare and detect position changes.
2) Use onLayoutChangeListener on your View:
button.addOnLayoutChangeListener(new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
// Check your new position vs the old one
}
});
I used this and it worked as a decent way of listening for changes of the view translation.
var previousTranslationX = view.translationX
var previousTranslationY = view.translationY
view.viewTreeObserver.addOnDrawListener {
if (previousTranslationX != view.translationX ||
previousTranslationY != view.translationY) {
previousTranslationX = view.translationX
previousTranslationY = view.translationY
dispatchViewTranslationUpdated(view)
}
}
Simply register a callback to be invoked when the view tree is about to be drawn.
Note: This listener almost called every time the view is drawn!
public class MyView extends View {
private float oldScaleX;
public MyView(Context context) {
super(context);
getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
#Override
public void onDraw() {
// Many things can invoke this method! We don't know why view going
// to be redrawn, So we must determine the cause ourselves.
float newScaleX=getScaleX();
if (oldScaleX!=newScaleX) {
scaleXUpdated();
oldScaleX=newScaleX;
}
}
});
}
private void scaleXUpdated() {
Log.e(TAG,"scaleX updated "+getScaleX);
}
}

Categories

Resources