RecyclerView - SlideIn animation on activity start

2019-04-02 09:50发布

问题:

How can I add a slide in animation on my recycler view items one after the other. Like as the activity starts, the list items of the recycler view slides in one by one. I am using LinearLayoutManager

Not all at the same time should slide in. And not even while scrolling. Just at the time of activity creation.

I searched but didn't find anything.

I want to achieve something like this : https://youtu.be/Q8TXgCzxEnw?t=30s

回答1:

I put together a sample app a couple of months ago that has a sequential slide in-slide out animation during reshuffles. A demo video is available here. It should give you some ideas.

A link to the most relevant class file is here, and I'll copy the code below.

public class AllNotesFragmentRecyclerView extends RecyclerView {

    private static final int BASE_ANIMATION_TIME = 50;
    private static final int MAX_ANIMATION_TIME_INCREMENT = 100;

    private int screenWidth;
    private int startX, finalX;

    private int[] interpolatedAnimationTimes;

    public AllNotesFragmentRecyclerView(Context context) {
        super(context);
        init(context);
    }

    public AllNotesFragmentRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public AllNotesFragmentRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        calculateScreenWidth(context);

        startX = 0;
        finalX = -(screenWidth);
    }

    private void calculateScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics metrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics);
        screenWidth = metrics.widthPixels;
    }

    private int calculateInterpolatedAnimationTime(int currentIndex, int maxIndex) {
        float percentage = ((float)currentIndex/(float)maxIndex);
        float increment = (float) MAX_ANIMATION_TIME_INCREMENT * percentage;
        return (int) (BASE_ANIMATION_TIME + increment);
    }

    public void updateListOrder() {
        createAnimatorSet();
    }

    private void createAnimatorSet() {

        AnimatorSet set = new AnimatorSet();
        ArrayList<Animator> animArrayList = new ArrayList<>();

        for (int i = 0; i < getChildCount(); i++) {
            ObjectAnimator anim = ObjectAnimator
                    .ofFloat(getChildAt(i), "translationX", finalX);

            int duration = calculateInterpolatedAnimationTime(i, getChildCount());

            anim.setDuration(duration);
            anim.addListener(new RowAnimationListener(i, duration, startX));
            animArrayList.add(anim);
        }
        set.setInterpolator(new AccelerateInterpolator());
        set.playSequentially(animArrayList);
        set.start();
    }

    private void animateOn(int childPosition, int duration, int targetValue) {
        ObjectAnimator animator = ObjectAnimator
                .ofFloat(getChildAt(childPosition), "translationX", targetValue);
        animator.setInterpolator(new DecelerateInterpolator());
        animator.setDuration(duration);
        animator.start();
    }
//...

    private class RowAnimationListener implements Animator.AnimatorListener {

        private int position, duration, targetX;

        public RowAnimationListener(int position, int duration, int targetX) {
            this.position = position;
            this.duration = duration;
            this.targetX = targetX;
        }

        @Override
        public void onAnimationStart(Animator animation) {
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            int currentItem = getLinearLayoutManager().findFirstVisibleItemPosition() + position;
            getAdapter().notifyItemChanged(currentItem);
            notifyRowsPeripheralToVisibleItemsDataChanged(position);
            animateOn(position, duration, targetX);
        }

        @Override
        public void onAnimationCancel(Animator animation) { }

        @Override
        public void onAnimationRepeat(Animator animation) { }
    }
}


回答2:

Finally I found a solution. In below snippet I will explain how to implement. It is simple and can be done on any existing working RecyclerView. I have explained everything in comments.

Here is the onCreate/onCreateView method (I have used this inside Fragment, You can change accordingly if needed):

RecyclerView recList = (RecyclerView) rootView.findViewById(R.id.event_list);
recList.setHasFixedSize(true);
LinearLayoutmanager llm = new LinearLayoutManager(getActivity().getApplicationContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
recList.setLayoutManager(llm);

// This is important. Setting recyclerView's alpha to zero.
// Basically this is just to hide recyclerview at start before binding data
// As setVisibility is not working on recycler view object.
recList.setAlpha(0);

// Create the EventAdapter with the result we got
// EventAdapter is my custom adapter class.
// you should set your adapter class
EventAdapter ea = new EventAdapter(eventResultList);

// Binding the Adapter to RecyclerView list to show the data
recList.setAdapter(ea);

// ********************* Animate at start ********************************

new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {

                // This will give me the initial first and last visible element's position.
                // This is required as only this elements needs to be animated
                // Start will be always zero in this case as we are calling in onCreate
                int start = llm.findFirstVisibleItemPosition();
                int end = llm.findLastVisibleItemPosition();

                Log.i("Start: ", start + "");
                Log.i("End: ", end + "");

                // Multiplication factor
                int DELAY = 50;

                // Loop through all visible element
                for (int i = start; i <= end; i++) {
                    Log.i("Animatining: ", i + "");

                    // Get View
                    View v = recList.findViewHolderForAdapterPosition(i).itemView;

                    // Hide that view initially
                    v.setAlpha(0);

                    // Setting animations: slide and alpha 0 to 1
                    PropertyValuesHolder slide = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 150, 0);
                    PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat(View.ALPHA, 0, 1);
                    ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(v, slide, alpha);
                    a.setDuration(300);

                    // It will set delay. As loop progress it will increment
                    // And it will look like items are appearing one by one.
                    // Not all at a time
                    a.setStartDelay(i * DELAY);

                    a.setInterpolator(new DecelerateInterpolator());

                    a.start();

                }

                // Set Recycler View visible as all visible are now hidden
                // Animation will start, so set it visible
                recList.setAlpha(1);

        }
}, 50);

This is quite a small code without comments.


Some things needs an explanation:

Why hiding RecyclerView initially?

If RecyclerView is not hidden initially you will notice a blink initially before the animation starts. The reason for it is when you set a data adapter it will position it on its default positions and after the loop it starts animating. So in between while loop is running you will notice sudden blink in the RecyclerView that at first all are at its initial position and than suddenly animating.

So hiding it at first and than after loop completes and all visible positions animations are set with delays and started, we can show the RecyclerView. It makes sliding looks smooth.

The reason for hiding it with setAlpha(0) is as setVisibility() function is not working on the RecyclerView object.


How only visible elements will animate?

There are functions in the LayoutManager class to get the visible elements position. In LinearLayoutManager used findFirstVisibleItemPosition() to get the position of the first visible view from the recycler view data which is visible on screen. And the last visible view's position can be retried with findLastVisibleItemPosition(). So we can loop from the first view to last view and animate the initial views which are going to be on screen at start.


How delay Works?

As loop will progress from 0(start) to end it will set delay from 0,50,100,150,.. if DELAY variable is set to 50. So this will make first element start animating, second after 50ms delay, third after 100ms delay and so on. So it will look like they are coming in one by one. Not all together.



回答3:

Create animation in anim/slide_in.xml file like below

    <?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="100%" android:toXDelta="0%"
            android:fromYDelta="0%" android:toYDelta="0%"
            android:duration="2000"/>
    </set>

And then apply this animation on each view of RecyclerView in onBindViewHolder method.

 @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ViewHolder vh = (ViewHolder) holder;
        vh1.tv_header.setText(mList.get(position));

        Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.rec_anim);
        animation.setStartOffset(30 * position);//Provide delay here 
        holder.itemView.startAnimation(animation);
    }