I am making an app that includes recyclerview. The items of recyclerview show up (slide in) with animation from the bottom on activity start. when clicking an item, it navigates to another activity. I want the items to disappear (slide out) with animation before exiting current activity. In my case I want items to drop down (slide out) and then new activity starts. is there any way to sliding out items before exiting activity?
EDIT: something like below:
You need the Slide Animation.
I have used it in one of my Android app. I will explain this with Explode Animation.
Lets say you have 2 activities A(GridList Activity) and B(DetailActivity).
A-->B
You can achieve this in these simple steps
1. Enable Content Transition
Add this code in your style.xml
<item name="android:windowContentTransitions">true</item>
2. Set Default enter and exit Animation
Write this method in Activity A which will handle animation for you.
public void setAnimation()
{
if(Build.VERSION.SDK_INT>20) {
Explode explode = new Explode();
explode.setDuration(1000);
explode.setInterpolator(new DecelerateInterpolator());
getWindow().setExitTransition(explode);
getWindow().setEnterTransition(explode);
}
}
3. Start Activity with Intent
Add this in your Activity A for starting Activity B. Note that the animation works only above SDK>20. So if your min sdk is lower then you will have to a check for SDK as well. Just use this code snippet and you will be good to go.
public void startActivity(){
Intent i = new Intent(BlankActivity.this, AllImageActivity.class);
i.putStringArrayListExtra(MOVIE_LIST, movie.getImages());
if(Build.VERSION.SDK_INT>20)
{
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(BlankActivity.this);
startActivity(i,options.toBundle());
}
else {
startActivity(i);
}
}
Very Important
You should place the setAnimation() before setContentView(R.layout.yourLayout) else the animation will not work. So the Activity A should look like this
Activity A extends .... {
#Override
protected void onCreate(Bundle savedInstaceState)
{
super.onCreate(savedInstaceState);
setAnimation();
setContentView(R.layout.image_landing_layout);
startActivity(); // Use as you wish
.......
}
public void setAnimation(){
..........
}
I am not writing this on any IDE so don't mind any syntax errors. But this will push you in the right track.
You may checkout my Repository . But you may have hard time finding it there. Hope this helps.
LayoutAnimationController:
Using the build in LayoutAnimationController you can achieve the slide out effect using a custom animation slide from top to bottom with animation order to be the LayoutAnimationController.ORDER_REVERSE to start the animation from the bottom right child item view.
1.)Declare in your res/anim/slide_from_top_to_bottom.xml the slide out animation:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="250">
<translate
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:fromYDelta="0"
android:toYDelta="100%p"/>
<alpha
android:fromAlpha="1"
android:toAlpha="0"
android:interpolator="#android:anim/accelerate_decelerate_interpolator"/>
</set>
2.)Use the below helper to start the above animation by setting the LayoutAnimationController into the RecyclerView using the setLayoutAnimation() method:
private void slideOutRecyclerViewLayoutAnimation(final RecyclerView recyclerView)
{
Animation animation = AnimationUtils.loadAnimation(this, R.anim.slide_from_top_to_bottom);
animation.setFillAfter(true); //true if the animation should apply its transformation after it ends
LayoutAnimationController lac = new LayoutAnimationController(animation);
lac.setOrder(LayoutAnimationController.ORDER_REVERSE);
lac.setDelay(0.15f);
recyclerView.setLayoutAnimation(lac);
recyclerView.getAdapter().notifyDataSetChanged();
recyclerView.scheduleLayoutAnimation();
//go to the next Activity after 1sec
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//go to the next Activity
}
}, 1000);
}
Custom ItemAnimator:
In case you want to have more customisation in the animation delay of each child with a different slide out order something like in your gif you can use a custom SimpleItemAnimator and use the public boolean animateRemove(RecyclerView.ViewHolder holder) which is called when an item in the RecyclerView has been removed. It called when you remove only one item like notifyItemRemoved() or when you remove a range of items like notifyItemRangeRemoved().
1.)Create the CustomAnimator like the below sample:
public class CustomAnimator extends SimpleItemAnimator {
private final int columns;
private final int rows;
public CustomAnimator(int columns, int itemsCount){
this.columns = columns;
this.rows = (int) Math.ceil((double) itemsCount / (double)columns);
}
#Override
public boolean animateRemove(RecyclerView.ViewHolder holder) {
//to get the item view position set in RecyclerView.Adapter#onBindViewHolder the position to the item view like: itemView.setTag(position);
int position = (int)holder.itemView.getTag();
int x = position % columns;
int y = position / columns;
int yDiff = rows - y;
long delay = 0;
switch (x)
{
//column 0
case 0:
delay = yDiff * 60L;
break;
//column 1
case 1:
delay = yDiff * 50L;
break;
//column 2
case 2:
delay = yDiff * 40L;
break;
//column 3
case 3:
delay = yDiff * 30L;
break;
}
int height = holder.itemView.getContext().getResources().getDisplayMetrics().heightPixels;
holder.itemView.setTranslationY(0);
holder.itemView.setAlpha(1);
holder.itemView.animate()
.translationY(height)
.alpha(0)
.setInterpolator(new LinearInterpolator())
.setDuration(500)
.setStartDelay(delay)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
dispatchRemoveFinished(holder);
}
})
.start();
return false;
}
#Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
return false;
}
#Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
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 runPendingAnimations() {
}
#Override
public void endAnimation(#NonNull RecyclerView.ViewHolder item) {
}
#Override
public void endAnimations() {
}
#Override
public boolean isRunning() {
return false;
}
}
2.)In your RecyclerView.Adapter in onBindViewHolder set the position of each item into the tag to be retrieved later from the CustomAnimator like below:
itemView.setTag(position);
3.)During the RecyclerView initialisation set the ItemAnimator like below:
//set GridLayoutManager with 4 columns
recyclerView.setLayoutManager(new GridLayoutManager(this, 4));
//prepare some dummy items
List<Item> itemsList = new ArrayList<>();
for(int i =0; i<26; i++) {
itemsList.add(new Item());
}
//initialize Adapter
itemsAdapter = new MyAdapter(itemsList);
//set recyclerView Adapter
recyclerView.setAdapter(itemsAdapter);
//set recyclerView CustomAnimator
recyclerView.setItemAnimator(new CustomAnimator(4, itemsAdapter.getItems().size()));
4.)Finally when you click on an item call the below helper to start the animation:
private void removeAllItems(){
int itemsCount = itemsAdapter.getItems().size();
itemsAdapter.getItems().clear();
recyclerView.getAdapter().notifyItemRangeRemoved(0, itemsCount);
//go to the next Activity after 1sec
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//go to the next Activity after 1sec
}
}, 1000);
}
Result:
Related
I have a recyclerview with a lot of elements (20+) and a lottie animation in each element. The lottie animation doesn't run until the user taps on it but the problem is that when an animation is tapped the animation happens every 5 elements.
For example if you tap on the 1st element, then element 5, 10, 15,20 all animate.
If you tap on the second then 6,11,16,21 all animate.
Here is my View Holder class:
class OutfitViewHolder extends RecyclerView.ViewHolder{
private final LottieAnimationView animationView;
private OutfitViewHolder(View view){
super(view);
animationView = view.findViewById(R.id.wearAgainAnimation);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
wearAgainAnimation();
}
});
}
private void wearAgainAnimation() {
final ValueAnimator animator = ValueAnimator.ofFloat(0f,.5f).setDuration(800);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
animationView.setProgress((Float) animator.getAnimatedValue()) ;
}
});
if (animationView.getProgress() == 0f) {
animator.start();
} else {
animationView.setProgress(0f);
}
}
}
I have checked to make sure that only the position of the tapped element is passed to the adapter so I'm not sure why the other elements are also animating
I ended up changing the cache size of the recyclerview to match my list size. I did this when initializing my recyclerview in my class
recyclerView.setItemViewCacheSize(list.size());
I am new and studying online . This is my first animation study at Android. The problem is that when I choose the answer my Card View animation is suppose to show immediately. But it didn't show as soon as I click . But I found out that after I click the button, I need to press Home button and resume it from the background apps to start the animation.The animation starts only after I do this process. Toast is showing without a problem. After I choose the answer I always need to do like that to show my animation. The app is about True or false.
Also the coding is exactly the same as my online tutorial. maybe my phone problem ?? I am using Samsung S 7 edge.
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView QuestionTextView,QcountTextView;
private Button Tbutton,Fbutton;
private ImageButton prev,next;
private int count = 0;
private CardView cardView;
private Animation shake;
public Questions Qarray[] = new Questions[]{
new Questions(R.string.Q1, true),
new Questions(R.string.Q2, false),
new Questions(R.string.Q3, true),
new Questions(R.string.Q4, true),
new Questions(R.string.Q5, false),
new Questions(R.string.Q6, true),
new Questions(R.string.Q7, false)
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Tbutton = findViewById(R.id.tb);
Fbutton = findViewById(R.id.fb);
QuestionTextView = findViewById(R.id.questionshow);
QcountTextView = findViewById(R.id.Qcount);
next = findViewById(R.id.nb);
prev = findViewById(R.id.pb);
cardView = findViewById(R.id.cardView);
shake = AnimationUtils.loadAnimation(MainActivity.this,R.anim.shakeanimation);
Tbutton.setOnClickListener(this);
Fbutton.setOnClickListener(this);
next.setOnClickListener(this);
prev.setOnClickListener(this);
QuestionTextView.setText(Qarray[count].question);
}
#Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tb:
giveanswer(true);
pagechange();
break;
case R.id.fb:
giveanswer(false);
pagechange();
break;
case R.id.pb:
if(count != 0) {
count = (count - 1);
pagechange();
}
break;
case R.id.nb:
count = (count + 1) % Qarray.length;
pagechange();
break;
}
}
private void pagechange() {
QuestionTextView.setText(Qarray[count].getQuestion());
QcountTextView.setText((count+1) + " out of " + Qarray.length);
}
private void giveanswer(boolean b) {
boolean correctanswer = Qarray[count].ans;
if(b == correctanswer){
fadeView();
Toast.makeText(this,R.string.yes,Toast.LENGTH_SHORT).show();
}
else{
ShakeAnimation();
Toast.makeText(this,R.string.no,Toast.LENGTH_SHORT).show();
}
}
private void fadeView(){
AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f,0.0f);
alphaAnimation.setDuration(350);
alphaAnimation.setRepeatCount(1);
alphaAnimation.setRepeatMode(Animation.REVERSE);
cardView.setAnimation(alphaAnimation);
alphaAnimation.setAnimationListener(new
Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
cardView.setCardBackgroundColor(Color.GREEN);
}
#Override
public void onAnimationEnd(Animation animation){
cardView.setCardBackgroundColor(Color.rgb(41,226,205));
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private void ShakeAnimation(){
cardView.setAnimation(shake);
shake.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
cardView.setCardBackgroundColor(Color.RED);
}
#Override
public void onAnimationEnd(Animation animation) {
cardView.setCardBackgroundColor(Color.rgb(41,226,205));
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
}
I expect the animation show as soon as I choose the answer.
you need to call alphaAnimation.start(); or shake.start(); just having animation listeners is not gonna start your animation when you want it to start.
try adding these lines exactly where you want the animations to run.
For Example:
AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f,0.0f);
alphaAnimation.setDuration(350);
alphaAnimation.setRepeatCount(1);
alphaAnimation.setRepeatMode(Animation.REVERSE);
cardView.setAnimation(alphaAnimation);
alphaAnimation.start();
Also setAnimation should to be used with xml Animations if I'm not mistaking
I have a horizontal recyclerView, When I first open the activity I want to make all the items in recyclerview scroll to the bottom (to the right in this case) and back to the top (to the left). Kinda like an animation. Scroll behavior should be visible to the user.
I tried to do it like:
Animation slideRight = AnimationUtils.loadAnimation(this, R.anim.slide_right);
Animation slideLeft = AnimationUtils.loadAnimation(this, R.anim.slide_left);
slideRight.setDuration(1000);
slideLeft.setDuration(1000);
slideRight.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
recyclerView.startAnimation(slideLeft);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
recyclerView.startAnimation(slideRight);
anim slide left:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" >
<translate
android:duration="200"
android:fromXDelta="-100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
anim slide right:
<translate
android:duration="200"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
it works however it just slides the recyclerview as a whole, I just want to scroll(slide) the items. How can I do this?
You can use scrollToPosition()
recyclerView.post(new Runnable() {
#Override
public void run() {
recyclerView.scrollToPosition(adapter.getItemCount() - 1);
// Here adapter.getItemCount()== child count
}
});
or smoothScrollToPosition()
recyclerView.post(new Runnable() {
#Override
public void run() {
recyclerView.smoothScrollToPosition(adapter.getItemCount()- 1);
}
});
To move up again, you need to call the above method with index 0. But first, you need to make sure that the RecyclerView is scrolled to last.
So, put a ScrollListener on RecyclerView to make sure the last item is visible.
Use this..
int top = 0;
recyclerView.smoothScrollToPosition(top); // for top
int bottom = recyclerView.getAdapter().getItemCount()-1;
recyclerView.smoothScrollToPosition(bottom);
if you need scroll item of recyclerview to CENTER of screen when for ex. expand your item - use this way:
in app/build.gradle - insert:
implementation 'com.andkulikov:transitionseverywhere:1.7.4'
when hadle click in onBindViewHolder of adapter - insert:
AutoTransition transitionPort = new AutoTransition();
transitionPort.setDuration(100);
transitionPort.addListener(new Transition.TransitionListener() {
#Override
public void onTransitionStart(#NonNull Transition transition) {
recyclerView.setEnabled(false);
recyclerView.setClickable(false);
}
#Override
public void onTransitionEnd(#NonNull Transition transition) {
recyclerView.post(() -> recyclerView.smoothScrollToPosition(position));
recyclerView.setEnabled(true);
recyclerView.setClickable(true);
}
#Override
public void onTransitionCancel(#NonNull Transition transition) {
recyclerView.setEnabled(true);
recyclerView.setClickable(true);
}
#Override
public void onTransitionPause(#NonNull Transition transition) {
}
#Override
public void onTransitionResume(#NonNull Transition transition) {
}
});
TransitionManager.beginDelayedTransition(recyclerView, transitionPort);
Not working after trying other things?
This excellent piece of code worked for me
private void scrollToBottom(final RecyclerView recyclerView) {
// scroll to last item to get the view of last item
final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
final RecyclerView.Adapter adapter = recyclerView.getAdapter();
final int lastItemPosition = adapter.getItemCount() - 1;
layoutManager.scrollToPositionWithOffset(lastItemPosition, 0);
recyclerView.post(new Runnable() {
#Override
public void run() {
// then scroll to specific offset
View target = layoutManager.findViewByPosition(lastItemPosition);
if (target != null) {
int offset = recyclerView.getMeasuredHeight() - target.getMeasuredHeight();
layoutManager.scrollToPositionWithOffset(lastItemPosition, offset);
}
}
});
}
You can modifyline, if you want to jump to some other listitem & not last
final int lastItemPosition = adapter.getItemCount() - 1;
Source
Currently, by using the default animator android.support.v7.widget.DefaultItemAnimator, here's the outcome I'm having during sorting
DefaultItemAnimator animation video : https://youtu.be/EccI7RUcdbg
public void sortAndNotifyDataSetChanged() {
int i0 = 0;
int i1 = models.size() - 1;
while (i0 < i1) {
DemoModel o0 = models.get(i0);
DemoModel o1 = models.get(i1);
models.set(i0, o1);
models.set(i1, o0);
i0++;
i1--;
//break;
}
// adapter is created via adapter = new RecyclerViewDemoAdapter(models, mRecyclerView, this);
adapter.notifyDataSetChanged();
}
However, instead of the default animation during sorting (notifyDataSetChanged), I prefer to provide custom animation as follow. Old item will slide out via right side, and new item will slide up.
Expected animation video : https://youtu.be/9aQTyM7K4B0
How I achieve such animation without RecylerView
Few years ago, I achieve this effect by using LinearLayout + View, as that time, we don't have RecyclerView yet.
This is how the animation is being setup
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f);
PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", 0f, (float) width);
ObjectAnimator animOut = ObjectAnimator.ofPropertyValuesHolder(this, alpha, translationX);
animOut.setDuration(duration);
animOut.setInterpolator(accelerateInterpolator);
animOut.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
final View view = (View) ((ObjectAnimator) anim).getTarget();
Message message = (Message)view.getTag(R.id.TAG_MESSAGE_ID);
if (message == null) {
return;
}
view.setAlpha(0f);
view.setTranslationX(0);
NewsListFragment.this.refreshUI(view, message);
final Animation animation = AnimationUtils.loadAnimation(NewsListFragment.this.getActivity(),
R.anim.slide_up);
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.VISIBLE);
view.setTag(R.id.TAG_MESSAGE_ID, null);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(animation);
}
});
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animOut);
this.nowLinearLayout.setLayoutTransition(layoutTransition);
and, this is how the animation is being triggered.
// messageView is view being added earlier in nowLinearLayout
for (int i = 0, ei = messageViews.size(); i < ei; i++) {
View messageView = messageViews.get(i);
messageView.setTag(R.id.TAG_MESSAGE_ID, messages.get(i));
messageView.setVisibility(View.INVISIBLE);
}
I was wondering, how I can achieve the same effect in RecylerView?
Here is one more direction you can look at, if you don't want your scroll to reset on each sort (GITHUB demo project):
Use some kind of RecyclerView.ItemAnimator, but instead of rewriting animateAdd() and animateRemove() functions, you can implement animateChange() and animateChangeImpl(). After sort you can call adapter.notifyItemRangeChanged(0, mItems.size()); to triger animation.
So code to trigger animation will look pretty simple:
for (int i = 0, j = mItems.size() - 1; i < j; i++, j--)
Collections.swap(mItems, i, j);
adapter.notifyItemRangeChanged(0, mItems.size());
For animation code you can use android.support.v7.widget.DefaultItemAnimator, but this class has private animateChangeImpl() so you will have to copy-pasted code and changed this method or use reflection. Or you can create your own ItemAnimator class like #Andreas Wenger did in his example of SlidingAnimator. The point here is to implement animateChangeImpl Simmilar to your code there are 2 animations:
1) Slide old view to the right
private void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder oldHolder = changeInfo.oldHolder;
final View view = oldHolder == null ? null : oldHolder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view == null) return;
mChangeAnimations.add(oldHolder);
final ViewPropertyAnimatorCompat animOut = ViewCompat.animate(view)
.setDuration(getChangeDuration())
.setInterpolator(interpolator)
.translationX(view.getRootView().getWidth())
.alpha(0);
animOut.setListener(new VpaListenerAdapter() {
#Override
public void onAnimationStart(View view) {
dispatchChangeStarting(oldHolder, true);
}
#Override
public void onAnimationEnd(View view) {
animOut.setListener(null);
ViewCompat.setAlpha(view, 1);
ViewCompat.setTranslationX(view, 0);
dispatchChangeFinished(oldHolder, true);
mChangeAnimations.remove(oldHolder);
dispatchFinishedWhenDone();
// starting 2-nd (Slide Up) animation
if (newView != null)
animateChangeInImpl(newHolder, newView);
}
}).start();
}
2) Slide up new view
private void animateChangeInImpl(final RecyclerView.ViewHolder newHolder,
final View newView) {
// setting starting pre-animation params for view
ViewCompat.setTranslationY(newView, newView.getHeight());
ViewCompat.setAlpha(newView, 0);
mChangeAnimations.add(newHolder);
final ViewPropertyAnimatorCompat animIn = ViewCompat.animate(newView)
.setDuration(getChangeDuration())
.translationY(0)
.alpha(1);
animIn.setListener(new VpaListenerAdapter() {
#Override
public void onAnimationStart(View view) {
dispatchChangeStarting(newHolder, false);
}
#Override
public void onAnimationEnd(View view) {
animIn.setListener(null);
ViewCompat.setAlpha(newView, 1);
ViewCompat.setTranslationY(newView, 0);
dispatchChangeFinished(newHolder, false);
mChangeAnimations.remove(newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
Here is demo image with working scroll and kinda similar animation
https://i.gyazo.com/04f4b767ea61569c00d3b4a4a86795ce.gif
https://i.gyazo.com/57a52b8477a361c383d44664392db0be.gif
Edit:
To speed up RecyclerView preformance, instead of adapter.notifyItemRangeChanged(0, mItems.size()); you probably would want to use something like:
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisible = layoutManager.findFirstVisibleItemPosition();
int lastVisible = layoutManager.findLastVisibleItemPosition();
int itemsChanged = lastVisible - firstVisible + 1;
// + 1 because we start count items from 0
adapter.notifyItemRangeChanged(firstVisible, itemsChanged);
First of all:
This solution assumes that items that are still visible, after the dataset changed, also slide out to the right and later slide in from the bottom again (This is at least what I understood you are asking for)
Because of this requirement I couldn't find an easy and nice solution for this problem (At least during the first iteration). The only way I found was to trick the adapter - and fight the framework to do something that it was not intended for. This is why the first part (How it normally works) describes how to achieve nice animations with the RecyclerView the default way. The second part describes the solution how to enforce the slide out/slide in animation for all items after the dataset changed.
Later on I found a better solution that doesn't require to trick the adapter with random ids (jump to the bottom for the updated version).
How it normally works
To enable animations you need to tell the RecyclerView how the dataset changed (So that it knows what kind of animations should be run). This can be done in two ways:
1) Simple Version:
We need to set adapter.setHasStableIds(true); and providing the ids of your items via public long getItemId(int position) in your Adapter to the RecyclerView. The RecyclerView utilizes these ids to figure out which items were removed/added/moved during the call to adapter.notifyDataSetChanged();
2) Advanced Version: Instead of calling adapter.notifyDataSetChanged(); you can also explicitly state how the dataset changed. The Adapter provides several methods, like adapter.notifyItemChanged(int position),adapter.notifyItemInserted(int position),... to describe the changes in the dataset
The animations that are triggered to reflect the changes in the dataset are managed by the ItemAnimator. The RecyclerView is already equipped with a nice default DefaultItemAnimator. Furthermore it is possible to define custom animation behavior with a custom ItemAnimator.
Strategy to implement the slide out (right), slide in (bottom)
The slide to the right is the animation that should be played if items are removed from the dataset. The slide from bottom animation should be played for items that were added to the dataset. As mentioned at the beginning I assume that it is desired that all elements slide out to the right and slide in from the bottom. Even if they are visible before and after the dataset change. Normally RecyclerView would play to change/move animation for such items that stay visible. However, because we want to utilize the remove/add animation for all items we need to trick the adapter into thinking that there are only new elements after the change and all previously available items were removed. This can be achieved by providing a random id for each item in the adapter:
#Override
public long getItemId(int position) {
return Math.round(Math.random() * Long.MAX_VALUE);
}
Now we need to provide a custom ItemAnimator that manages the animations for the added/removed items. The structure of the presented SlidingAnimator is very similar to theandroid.support.v7.widget.DefaultItemAnimator that is provided with the RecyclerView. Also Notice this is a prove of concept and should be adjusted before used in any app:
public class SlidingAnimator extends SimpleItemAnimator {
List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();
#Override
public void runPendingAnimations() {
final List<RecyclerView.ViewHolder> additionsTmp = pendingAdditions;
List<RecyclerView.ViewHolder> removalsTmp = pendingRemovals;
pendingAdditions = new ArrayList<>();
pendingRemovals = new ArrayList<>();
for (RecyclerView.ViewHolder removal : removalsTmp) {
// run the pending remove animation
animateRemoveImpl(removal);
}
removalsTmp.clear();
if (!additionsTmp.isEmpty()) {
Runnable adder = new Runnable() {
public void run() {
for (RecyclerView.ViewHolder addition : additionsTmp) {
// run the pending add animation
animateAddImpl(addition);
}
additionsTmp.clear();
}
};
// play the add animation after the remove animation finished
ViewCompat.postOnAnimationDelayed(additionsTmp.get(0).itemView, adder, getRemoveDuration());
}
}
#Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
pendingAdditions.add(holder);
// translate the new items vertically so that they later slide in from the bottom
holder.itemView.setTranslationY(300);
// also make them invisible
holder.itemView.setAlpha(0);
// this requests the execution of runPendingAnimations()
return true;
}
#Override
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
pendingRemovals.add(holder);
// this requests the execution of runPendingAnimations()
return true;
}
private void animateAddImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view);
anim
// undo the translation we applied in animateAdd
.translationY(0)
// undo the alpha we applied in animateAdd
.alpha(1)
.setDuration(getAddDuration())
.setInterpolator(new DecelerateInterpolator())
.setListener(new ViewPropertyAnimatorListener() {
#Override
public void onAnimationStart(View view) {
dispatchAddStarting(holder);
}
#Override
public void onAnimationEnd(View view) {
anim.setListener(null);
dispatchAddFinished(holder);
// cleanup
view.setTranslationY(0);
view.setAlpha(1);
}
#Override
public void onAnimationCancel(View view) {
}
}).start();
}
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view);
anim
// translate horizontally to provide slide out to right
.translationX(view.getWidth())
// fade out
.alpha(0)
.setDuration(getRemoveDuration())
.setInterpolator(new AccelerateInterpolator())
.setListener(new ViewPropertyAnimatorListener() {
#Override
public void onAnimationStart(View view) {
dispatchRemoveStarting(holder);
}
#Override
public void onAnimationEnd(View view) {
anim.setListener(null);
dispatchRemoveFinished(holder);
// cleanup
view.setTranslationX(0);
view.setAlpha(1);
}
#Override
public void onAnimationCancel(View view) {
}
}).start();
}
#Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
// don't handle animateMove because there should only be add/remove animations
dispatchMoveFinished(holder);
return false;
}
#Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
// don't handle animateChange because there should only be add/remove animations
if (newHolder != null) {
dispatchChangeFinished(newHolder, false);
}
dispatchChangeFinished(oldHolder, true);
return false;
}
#Override
public void endAnimation(RecyclerView.ViewHolder item) { }
#Override
public void endAnimations() { }
#Override
public boolean isRunning() { return false; }
}
This is the final result:
Update: While Reading the post again I figured out a better solution
This updated solution doesn't require to trick the adapter with random ids into thinking all items were removed and only new items were added. If we apply the 2) Advanced Version - how to notify the adapter about dataset changes, we can just tell the adapter that all previous items were removed and all the new items were added:
int oldSize = oldItems.size();
oldItems.clear();
// Notify the adapter all previous items were removed
notifyItemRangeRemoved(0, oldSize);
oldItems.addAll(items);
// Notify the adapter all the new items were added
notifyItemRangeInserted(0, items.size());
// don't call notifyDataSetChanged
//notifyDataSetChanged();
The previously presented SlidingAnimator is still necessary to animate the changes.
I'm trying to create an animation buttons. I have 3 buttons. I created an animation as I need to. and applied it to the buttons. but it is performed on all buttons simultaneously. I want her to be followed sequentially. first there is the first button, then the second, then a third.
my animation:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" >
<alpha
android:duration="1000"
android:fromAlpha="0.0"
android:startOffset="0"
android:toAlpha="1.0" >
</alpha>
<translate
android:fromXDelta="-100%"
android:duration="1000"
android:toXDelta="0" />
</set>
in my class onCreateView:
Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.falling);
Button kitchenBtn = (Button) v.findViewById(R.id.buttonKitchen);
Button hotelBtn = (Button) v.findViewById(R.id.buttonHotel);
Button engenerBtn = (Button) v.findViewById(R.id.buttonEngener);
kitchenBtn.setAnimation(animation);
hotelBtn.setAnimation(animation);
engenerBtn.setAnimation(animation);
buttons go from the left edge. I want to start with the first left, then the second and third behind her. Now they do it at the same time.
FULL CODE:
public class PurchaseMenu extends Fragment implements View.OnClickListener {
private int mAnimationsFinished = 0;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.purchase_menu, null);
final Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.falling);
final Button kitchenBtn = (Button) v.findViewById(R.id.buttonKitchen);
final Button hotelBtn = (Button) v.findViewById(R.id.buttonHotel);
final Button engenerBtn = (Button) v.findViewById(R.id.buttonEngener);
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
Log.d("mylognah","start"+" "+mAnimationsFinished);
}
#Override
public void onAnimationEnd(Animation animation) {
if (mAnimationsFinished == 0) { //kitchenBtn animation ended
hotelBtn.setAnimation(animation);
} else if (mAnimationsFinished == 1) { //hotelBtn animation ended
engenerBtn.setAnimation(animation);
}
mAnimationsFinished++; //This would be a member variable
Log.d("mylognah","finish"+" "+mAnimationsFinished);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
kitchenBtn.setAnimation(animation);
kitchenBtn.setOnClickListener(this);
hotelBtn.setOnClickListener(this);
engenerBtn.setOnClickListener(this);
return v;
}
You have to set an AnimationListener on the Animation and keep track of the number of times it finished and start the appropriate next Animation like so:
final Animation fallingAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.falling);
final Button kitchenBtn = (Button) v.findViewById(R.id.buttonKitchen);
final Button hotelBtn = (Button) v.findViewById(R.id.buttonHotel);
final Button engenerBtn = (Button) v.findViewById(R.id.buttonEngener);
fallingAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
if (mAnimationsFinished == 0) { //Kitchen animation ended
kitchenBtn.setAnimation(null); //Set the animation on the other button to null
engenerBtn.setAnimation(null); //Otherwise they animate too
hotelBtn.setVisibility(View.VISIBLE); //Make the view visible
hotelBtn.setAnimation(fallingAnimation);
fallingAnimation.start(); //Restart animation. DO NOT FORGET THIS ONE
} else if (mAnimationsFinished == 2) { //Hotel animation ended. I couldn't figure out why, but this won't work when you try == 1.
kitchenBtn.setAnimation(null); //Set the animation on the other button to null
hotelBtn.setAnimation(null); //Otherwise they animate too
engenerBtn.setVisibility(View.VISIBLE); //Make the view visible
engenerBtn.setAnimation(fallingAnimation);
fallingAnimation.start(); //Restart animation. DO NOT FORGET THIS ONE
}
mAnimationsFinished++;
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
kitchenBtn.setAnimation(fallingAnimation);
hotelBtn.setVisibility(View.INVISIBLE); //Make view invisible. Otherwise they're visible before animated
engenerBtn.setVisibility(View.INVISIBLE); //Make view invisible. Otherwise they're visible before animated
This way, the kitchedBtn will be animated first and when the Animation ended, the onAnimationEnd method will be called, starting the next Animation.
using startOffSet you can avoid the issue. like this:
Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.falling);
Button kitchenBtn = (Button) v.findViewById(R.id.buttonKitchen);
Button hotelBtn = (Button) v.findViewById(R.id.buttonHotel);
Button engenerBtn = (Button) v.findViewById(R.id.buttonEngener);
kitchenBtn.setAnimation(animation);
animation.setStartOffset(animation.getDuration());
hotelBtn.setAnimation(animation);
animation.setStartOffset(animation.getDuration()*2);
engenerBtn.setAnimation(animation);
Lets say you have a LinearLayout(or any viewgroup) that you want to add these buttons dynamically. You can do like this:
for (int i = 0; i < buttons.size(); i++) {
Button button = new Button(context);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
linearLayout.addView(button);
animateButton(button);
}
}, BUTTON_DELAY_DURATION * i);
}
and dont forget to call handler.removeCallBacks() to remove runnable instance in onPause();