I am currently working on adding a income animation for RecyclerView items. I want it just likes the Google+ Android App. Here is my code snippets:
anim/up_from_bottom.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="#android:anim/decelerate_interpolator">
<translate
android:fromXDelta="0%" android:toXDelta="0%"
android:fromYDelta="60%" android:toYDelta="0%"
android:duration="500" />
</set>
and in the Adapter class I add the function startAnimation(itemHolder.itemView, position); in the funcitononBindViewHolder
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
...
startAnimation(holder.rootView, position);
}
the startAnimation() is like this:
protected void startAnimation(View view, int position) {
if (position > lastPosition) {
Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.up_from_bottom);
view.startAnimation(animation);
lastPosition = position;
}
}
It seems looks like the Google+ Android App's list animation, but it has a bug, when I scroll fast, it animate not perfect, such as it seems that the first position and the current postion perform the animation at the same time.
I just want the animation like Google+, how can I fix it, or there has any other way.
I hava try recyclerview-animators, it's a great lib, but I still do what I want.
Any help please! Thanks!
Your "bug" occurs when you scroll fast because the animation on the viewholder is still running when it is scrolled out of the display and you are binding a new animation to the viewholder when the recyclerview recycles it.
You should override onViewDetachedFromWindow and clear the animation on the view in the method.
This will ensure that any animation that is running on the view will be cleared before it is sent to the recycler for resuse, and you can safely run a new animation on the view.
Related
I want to animate each view with delay. Curently all item are animated together on create. and only when i scroll the new added items are animated so i want to animate the initilly created items in the recycler view this is my code in my recycler view adapter
private void setAnimation(View viewToAnimate, int position)
{
// If the bound view wasn't previously displayed on screen, it's animated
if (position > lastPosition)
{
Animation animation = AnimationUtils.loadAnimation(context, R.anim.slide_left);
animation.setDuration(1000);
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
#Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
Typeface tf = Typeface.createFromAsset(context.getAssets(), "fonts/gothamlight.ttf");
NavigationData navigationData = arrayList.get(position);
holder.NavigationImage.setImageResource(navigationData.getImg_res());
holder.NavigationTitle.setText(navigationData.getNavigationTitle());
holder.NavigationTitle.setTypeface(tf);
setAnimation(holder.Container, position);
}
try increasing the duration in your animation file i.e. slide_left in your case.
slide_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="1000"
android:fromXDelta="100%"
android:toXDelta="0%" >
</translate>
</set>
The solutition i found is declare an int of duration with the default animation duration and inside set animation i have duration = duration + 300. And it works great.
recently I wrote a function.it's about a refresh button in each list item. what I want is click the button or List Item, the refresh button starts rotate. stops when the request finished.
I use animation as follows:
<?xml version="1.0" encoding="UTF-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fillAfter="true"
android:fromDegrees="0"
android:interpolator="#android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:toDegrees="358" />
and some source code are here:
public void refresh(View v) {
Animation rotation = AnimationUtils.loadAnimation(mContext,
R.anim.rotate);
rotation.setFillAfter(true);
v.startAnimation(rotation);
}
public void completeRefresh(View v) {
v.clearAnimation();
}
when the request finished, I call notifyDataSetChanged() to refresh the LiseView.
the problem is that the button was indeed rotating. but when I click it the second time. it's rotating but a little fuzzy. just like this:
any suggestions? thanks a lot.
What is most likely happening on your second (and subsequent clicks) is that the animation is running on it again.
In your current implementation, try using setTag(...) like this:
public void refresh(View v) {
if(v.getTag() != null && (boolean)v.getTag()) {
//do nothing, since we are setting the tag to be true once pressed
} else {
//this view hasn't been clicked yet, show animation and set tag
v.setTag(true);
Animation rotation = AnimationUtils.loadAnimation(mContext, R.anim.rotate);
rotation.setFillAfter(true);
v.startAnimation(rotation);
}
}
In your Adapter, you should keep track of which list items are being animated (I'm assuming multiple items can be clicked at the same time). Once you know that the 'request' is finished, you can update the adapter with the correct items and then call notifyDataSetChanged()
I'm having a recyclerView with several views and one is the animation view which has an animation applied to it. Once the view is out of the screen the animation is no longer active, even though the animation still exists.
The data:
rotate_around_center_point.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" >
<rotate
android:duration="2500"
android:interpolator="#android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:repeatMode="restart"
android:toDegrees="360" />
</set>
Applying animation:
animation = AnimationUtils.loadAnimation(this.getContext(),
R.anim.rotate_around_center_point);
loadingRotatingCircleIV.startAnimation(animation);
I could not find any way to catch an event when the animation is interrupted so I'm able to restart the animation once it was out of the screen.
RecyclerView is incorrectly stopping animation on the view when view goes out from screen only if you apply View Animation to that view. If you will use Property Animation everything works properly. So the following solution will work:
ObjectAnimator animator = ObjectAnimator.ofFloat(loadingRotatingCircleIV, "rotation", 0.0f, 360.0f);
animator.setDuration(1000L);
animator.setRepeatCount(ObjectAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.start();
If you want to use a ViewAnimation for a RecyclerView item, you have to restore animation in overriden method of RecyclerView - onViewAttachedToWindow(ItemViewHolder holder), like:
#Override
public void onViewAttachedToWindow(ItemViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (holder.animated)
{
Animation anim = new ScaleAnimation(0.8f, 1.2f,
0.8f, 1.2f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
anim.setFillAfter(true);
anim.setInterpolator(new BounceInterpolator());
anim.setDuration(1100);
anim.setRepeatCount(Animation.INFINITE);
anim.setRepeatMode(Animation.REVERSE);
holder.animatedView.startAnimation(anim);
}
}
I'm doing the exact same thing right now, and having the exact same problem. So the question is how to restart the animation once the view is back on screen.
I got the problem 99% resolved just by calling "loadingRotatingCircleIV.startAnimation(animation);" inside onBindViewHolder every time it gets called for my animated view's position.
But there's just a tiny little problem left. If you scroll down by just a little, so the view gets off screen, but it's view holder doesn't get recycled, when you scroll back up, onBindViewHolder is obviously not called again, but the animation is stopped.. So I'm still trying to fix that one..
So, my current solution:
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
...
if (position=="animated_view_position") view.startAnimation(animation);
...
}
If somebody has a better solution, please help.
create **HashMap<Integer, Boolean>** for saving each items animation loaded status
construct (){
for(int c = 0; c < adapterDataList.size(); c++){
//Here is create default status of animation loaded status used Boolean to saving
}
}
//Call Start Animation with onViewAttachedToWindow
#Override
public void onViewAttachedToWindow(ItemViewHolder holder) {
super.onViewAttachedToWindow(holder);
// get HashMap of each items Animation status like this
if(!HashMap.get(position)){ //FALSE means animation not loaded yet
//Here should call start Animation method;
} else {
//Here call no animation method, like setImageDrawable etc...
}
//create an AnimationListener for each item, when an animation is End, Listener should call a method to change HashMap above which you just created, like this **HashMap.put(position, Boolean.valueOf(true))** //TRUE means animation loaded
}
//to get position value as Integer use holder.getLayoutPosition()
Is there any way to animate the elements of a RecyclerView when I scroll it?
I took a look at DefaultItemAnimator and RecyclerView.ItemAnimator, but that animations seems to be only called if the dataset has changed, please correct me if I am wrong.
I'm a little confused about RecyclerView.ItemAnimator.animateMove() when is it called? I put some breakpoints into that class but none of them stops my app.
However back to my question how can I animate the RecyclerView? I want that some elements have another opacity, depended on some custom rules.
I did some more reaseach it seems that animation move is exactly that what I'm looking for. That methods are called from dispatchLayout(). Here is the javadoc of that method:
Wrapper around layoutChildren() that handles animating changes caused by layout.
Animations work on the assumption that there are five different kinds of items
in play:
PERSISTENT: items are visible before and after layout
REMOVED: items were visible before layout and were removed by the app
ADDED: items did not exist before layout and were added by the app
DISAPPEARING: items exist in the data set before/after, but changed from
visible to non-visible in the process of layout (they were moved off
screen as a side-effect of other changes)
APPEARING: items exist in the data set before/after, but changed from
non-visible to visible in the process of layout (they were moved on
screen as a side-effect of other changes)
The overall approach figures out what items exist before/after layout and
infers one of the five above states for each of the items. Then the animations
are set up accordingly:
PERSISTENT views are moved ({#link ItemAnimator#animateMove(ViewHolder, int, int, int, int)})
REMOVED views are removed ({#link ItemAnimator#animateRemove(ViewHolder)})
ADDED views are added ({#link ItemAnimator#animateAdd(ViewHolder)})
DISAPPEARING views are moved off screen
APPEARING views are moved on screen
So far I'm looking for PERSISTENT, DISAPPEARING and APPEARING, but that methods are never called because of this line here:
boolean animateChangesSimple = mItemAnimator != null && mItemsAddedOrRemoved
&& !mItemsChanged;
mItemsAddedOrRemoved is simply always false so none of that callback are ever reached. Any idea how to set set flag correctly?
I ended in using an OnScrollListener and animating it in a custom animate() method. In my case that code takes just 2ms so that is no problem for the 60fps.
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(int newState) {
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
// special handler to avoid displaying half elements
scrollToNext();
}
animate();
}
#Override
public void onScrolled(int dx, int dy) {
animate();
}
});
I did it this way. Might help someone. I don't know whether it's the best way to do it but works fine for me.
UPDATE:
To fix fast scrolling behaviour, override onViewDetachedFromWindow method of the adapter and call clearAnimation on the animated view (in this case, holder.itemView.clearAnimation() ).
up_from_bottom.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="#android:anim/decelerate_interpolator">
<translate
android:fromXDelta="0%" android:toXDelta="0%"
android:fromYDelta="100%" android:toYDelta="0%"
android:duration="400" />
</set>
down_from_top.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="#android:anim/decelerate_interpolator">
<translate
android:fromXDelta="0%" android:toXDelta="0%"
android:fromYDelta="-100%" android:toYDelta="0%"
android:duration="400" />
</set>
And finally put this code in onBindViewHolder of recyclerView. Create a field called lastPosition and initialize it to -1.
Animation animation = AnimationUtils.loadAnimation(context,
(position > lastPosition) ? R.anim.up_from_bottom
: R.anim.down_from_top);
holder.itemView.startAnimation(animation);
lastPosition = position;
The good solution is to animate holder in adapter in onBindViewHolder method.
Snippet taken from Material Test project Slidenerd:
#Override
public void onBindViewHolder(ViewHolderBoxOffice holder, int position) {
Movie currentMovie = mListMovies.get(position);
//one or more fields of the Movie object may be null since they are fetched from the web
holder.movieTitle.setText(currentMovie.getTitle());
//retrieved date may be null
Date movieReleaseDate = currentMovie.getReleaseDateTheater();
if (movieReleaseDate != null) {
String formattedDate = mFormatter.format(movieReleaseDate);
holder.movieReleaseDate.setText(formattedDate);
} else {
holder.movieReleaseDate.setText(Constants.NA);
}
int audienceScore = currentMovie.getAudienceScore();
if (audienceScore == -1) {
holder.movieAudienceScore.setRating(0.0F);
holder.movieAudienceScore.setAlpha(0.5F);
} else {
holder.movieAudienceScore.setRating(audienceScore / 20.0F);
holder.movieAudienceScore.setAlpha(1.0F);
}
if (position > mPreviousPosition) {
AnimationUtils.animateSunblind(holder, true);
} else {
AnimationUtils.animateSunblind(holder, false);
}
mPreviousPosition = position;
here is a link
I am trying to add Animation for ListView I am using getView()to draw some views in list. all works fine.
public View getView(int position, View convertView, ViewGroup parent) { }
I am trying to add animation when user click on list cell then all list cells should slide left and new data should come from right at same time, means cell data is moving towards left and same time new data coming from right side.
I Have implemented following code in OnItemClickListener
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
Animation slideOutAnimation=AnimationUtils.loadAnimation(this, R.anim.slide_out);
slideOutAnimation.setDuration(900);
Animation slideInAnimation=AnimationUtils.loadAnimation(this, R.anim.slide_in);
slideInAnimation.setDuration(500);
listView.startAnimation(slideOutAnimation);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
data = newData();
listView.startAnimation(slideInAnimation);
myAdapterClass.notifyDataSetChanged();
}
}, slideOutAnimation.getDuration());
}
};
above code is working but not getting desire output I am getting one empty view while changing Animation.
Left Sliding Animation Starts--- Empty View----Right Sliding Animation Starts
Not getting why Empty view (shows empty screen for while) is coming, I have played with Animation time and handler but no luck.
How to remove that empty view ? how to achieve this output ?
Left Sliding Animation Starts(Data moving)( Same time data coming from Right) Right Sliding Animation Starts
I am trying to add animation when user click on list cell then all
list cells should slide left and new data should come from right at
same time, means cell data is moving towards left and same time new
data coming from right side.
Your code works as intended but it will not do what you want simply because you use a single ListView widget which you first animate sliding left and then animate sliding right after the first animation ends.
Try to use a ViewFlipper containing two ListViews(one will be the visible one the other will be the one that comes with new data). You'll set your animations on the ViewFlipper(in and out animations) and then on a list item click you'll do:
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
data = newData();
//set the data for the second list, which currently isn't visible
secondListViewAdapter.notifyDataSetChanged();
viewFlipper().showNext(); //show the next list with animation
}
you can change animation behaviour by editing android:fromXDelta or alpha values,
slideout.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="-100%p" android:toXDelta="-100%p" android:duration="#android:integer/config_shortAnimTime"/>
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
android:duration="#android:integer/config_mediumAnimTime" />
</set>
slidein.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="#android:integer/config_shortAnimTime"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="#android:integer/config_mediumAnimTime" />
</set>