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;
}
}
Related
I am trying to achieve this loading with Maps. I have tried loading my animation after the Google Map is created and location is found. But the location loading starts after the animation is completed. How can I achieve the reveal animation after the map camera has navigated to the user's location.
I am not using Splash Screen. The animation code is within the MapsActivity.
private void setupRevealBackground(){
final int revealX = (int) (mBackgroundView.getX() + mBackgroundView.getWidth() / 2);
final int revealY = (int) (mBackgroundView.getY() + mBackgroundView.getHeight() / 2);
ViewTreeObserver viewTreeObserver = mBackgroundView.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
revealActivity(revealX, revealY);
mBackgroundView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
}
protected void revealActivity(int x, int y) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float finalRadius = (float) (Math.max(mBackgroundView.getWidth(), mBackgroundView.getHeight()) * 1.1);
// create the animator for this view (the start radius is zero)
Animator circularReveal = ViewAnimationUtils.createCircularReveal(mBackgroundView, x, y, 0, finalRadius);
circularReveal.setDuration(400);
circularReveal.setInterpolator(new AccelerateInterpolator());
// make the view visible and start the animation
circularReveal.start();
circularReveal.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
mBackgroundView.setVisibility(View.GONE);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
} else {
finish();
}
}
I am updating the location after the location is found Preference Change Listener.
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(Constants.LatitudePref)) {
Latitude = LocationResultHelper.getSavedLatitude(sharedPreferences);
Longitude = LocationResultHelper.getSavedLongitude(sharedPreferences);
if (mPickUpLatLng == null){
updateMarker(new LatLng(Latitude, Longitude));
}
} else if (key.equals(Constants.KEY_LOCATION_UPDATES_REQUESTED)) {
LocationRequestHelper.getRequesting(this);
}
}
This is my updateMarker Method after location is found
private void updateMarker(LatLng userLocation) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(userLocation, 16));
setupRevealBackground();
}
I have tried adding setupRevealBackground() method in the CameraIdleListener but the method is never called.
As indicated in this answer you can use OnMapLoadedCallback
When you have a reference to the map set the call back.
mMap.setOnMapLoadedCallback(this);
When the onMapLoaded event fires take the snapshot.
#Override
public void onMapLoaded() {
// Here you can display your map with animation
}
I'm trying to create a simple slide in animation for an existing recycler view.
Lets say the recycler holds 50 items, at some point the dataset has change and now contains only 40 items, the items have been replaced, all the previous 50 items are not relevant anymore.
So, notifyDatasetChanged() is being called after the data structure has been modified and the new items are animated in.
The problem is you can still see the previous 40 items below the new items, on the same space of each cell, you see both the previous data and the new data.
the code for ItemAnimator subclass is below, if I add a remove animation that changes the opacity of the remove cell it will be invisible but the item decoration (list lines) are not removed, I would prefer to remove the items entirely and not make it invisible.
public class RVSlideAnimation extends RecyclerView.ItemAnimator {
List<RecyclerView.ViewHolder> mViewHolders = new ArrayList<RecyclerView.ViewHolder>();
#Override
public void runPendingAnimations() {
if (!mViewHolders.isEmpty()) {
int animationDuration = 250;
AnimatorSet animator;
View target;
for (final RecyclerView.ViewHolder viewHolder : mViewHolders) {
target = viewHolder.itemView;
target.setPivotX(target.getMeasuredWidth() / 2);
target.setPivotY(target.getMeasuredHeight() / 2);
animator = new AnimatorSet();
animator.playTogether(
ObjectAnimator.ofFloat(target, "translationX", target.getMeasuredWidth(), 0.0f),
ObjectAnimator.ofFloat(target, "alpha", target.getAlpha(), 1.0f)
);
animator.setTarget(target);
animator.setDuration(animationDuration);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setStartDelay((animationDuration * viewHolder.getAdapterPosition()) / 10);
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
mViewHolders.remove(viewHolder);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
}
}
}
#Override
public boolean animateRemove(RecyclerView.ViewHolder viewHolder) {
//viewHolder.itemView.animate().alpha(0).setDuration(100);
return false;
}
#Override
public boolean animateAdd(RecyclerView.ViewHolder viewHolder) {
viewHolder.itemView.setAlpha(0.0f);
return mViewHolders.add(viewHolder);
}
#Override
public boolean animateMove(RecyclerView.ViewHolder viewHolder, int i, int i2, int i3, int i4) {
return false;
}
#Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
return false;
}
#Override
public void endAnimation(RecyclerView.ViewHolder viewHolder) {
}
#Override
public void endAnimations() {
}
#Override
public boolean isRunning() {
return !mViewHolders.isEmpty();
}
}
recyclerView.destroyDrawingCache(); before notifyDatasetChaged() solved the issue for me.
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();
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);
}
}
So what i am trying to achieve is user would open to first page of the view pager, and the view pager would bounce to half of the second page and bounce back to the fist page indicating that there are more pages to scroll to. I was wondering on how i could implement this?
You can use fakeDragBy method to achieve this effect:
viewPager.beginFakeDrag();
viewPager.fakeDragBy(offset); //offset in pixels.
viewPager.endFakeDrag();
EDIT:
I have made method for this:
private int animFactor;
private ValueAnimator animator = new ValueAnimator();
private void animateViewPager(final ViewPager pager, final int offset, final int delay) {
if (!animator.isRunning()) {
animator.removeAllUpdateListeners();
animator.removeAllListeners();
//Set animation
animator.setIntValues(0, -offset);
animator.setDuration(delay);
animator.setRepeatCount(1);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = animFactor * (Integer) animation.getAnimatedValue();
if (!pager.isFakeDragging()) {
pager.beginFakeDrag();
}
pager.fakeDragBy(value);
}
});
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
animFactor = 1;
}
#Override
public void onAnimationEnd(Animator animation) {
pager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
animFactor = -1;
}
});
animator.start();
}
}
Example of usage:
animateViewPager(pager, 10, 1000);
Edit2: ValueAnimator is class for Api level 11. Also set pager adapter before calling this method.
Adding a note to #Yuraj's answer. Call the method in onWindowFocusChanged when hasFocus==true as follows to avoid indexOutOfBoundsException:
#Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if(hasFocus)
{
Handler handler = new Handler();
final Runnable r = new Runnable()
{
public void run()
{
if(mViewPager.getCurrentItem() == 0)
{
Context context = Activity_main.this;
String filename="Init";
SharedPreferences stats;
stats = context.getSharedPreferences(filename, 0);
int appOpen = stats.getInt("appOpen", 0);
if(appOpen <= 5)
{
animateViewPager(mViewPager, 10, 300);
appOpen += 1;
SharedPreferences.Editor editor = stats.edit();
editor.putInt("appOpen", appOpen);
editor.commit();
}
}
}
};
handler.postDelayed(r, WAIT_VIEWPAGER_NUDGE);
}
}
Thank you Yuvraj! It worked with a simple modification. If anybody is getting "Invalid index 0, size is 0" error, here's a simple fix for it. If you call animateViewPager() method in onCreate() you might get this error, "Invalid index 0, size is 0". I believe viewpager.beginFakeDrag(); is being called before viewPager items / childs are initialized. So, call animateViewPager() with a delay like so:
new Handler().postDelayed(() -> animateViewPager(viewPager, 10, 1000), 500);
500 is the delay in milisecond