I Do It For the Cookies I Do It For the Cookies I Do It For the Cookies
    • No menu assigned
    svg svg
    I Do It For the Cookies

    By Eisvidas February 8, 2020 In Android

    How to populate a RecyclerView using custom animations

    Did you know that 75% of users judge the trustworthiness of an app based on its design?

    Every application uses some form of list to display data. It’s always changing as new content is loaded. It is therefore super important to make the UI as fluid and user-friendly as possible.

    Below you will find 4 main types of animations and there is an extra bonus animation provided at the end.

    This tutorial will outline the following:

    • Creating a custom java class which will house our animations.
    • Defining the animation programmatically using pure java code in our custom class.
    • Learn how to easily integrate the animation into your recycler view.
    • An extra bonus animation at the end.

    Pre-requisites: You should have a basic understanding of how a recycler view works.

    Let us start by defining a custom animation class. We shall call these method(s) from our recycler view adapter to trigger the animation.

    public class ItemAnimation {
    
        // animation type
        public static final int BOTTOM_UP = 1;
    
        public static final int FADE_IN = 2;
    
        public static final int LEFT_RIGHT = 3;
    
        public static final int RIGHT_LEFT = 4;
        
        // animation duration
        private static final long DURATION_IN_BOTTOM_UP = 150;
    
        private static final long DURATION_IN_FADE_ID = 500;
    
        private static final long DURATION_IN_LEFT_RIGHT = 150;
    
        private static final long DURATION_IN_RIGHT_LEFT = 150;
         
        // animation we want to use will by called from here
        public static void animate(View view, int position, int type) {
            switch (type) {
                case BOTTOM_UP:
                    animateBottomUp(view, position);
                    break;
    
                case FADE_IN:
                    animateFadeIn(view, position);
                    break;
    
                case LEFT_RIGHT:
                    animateLeftRight(view, position);
                    break;
    
                case RIGHT_LEFT:
                    animateRightLeft(view, position);
                    break;
    
            }
        }
    }

    Our switch statements will then direct the flow of views and their positions into the desired methods to trigger the associated animations (outlined below). You can play around with the duration of each animation and or create your own constants such as the delay, translation and delay.

    Bottom-Up

     private static void animateBottomUp(View view, int position) {
            boolean not_first_item = position == -1;
            position = position + 1;
            view.setTranslationY(not_first_item ? 800 : 500);
            view.setAlpha(0.f);
            AnimatorSet animatorSet = new AnimatorSet();
            ObjectAnimator animatorTranslateY = ObjectAnimator
                    .ofFloat(view, "translationY", not_first_item ? 800 : 500, 0);
            ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(view, "alpha", 1.f);
            animatorTranslateY.setStartDelay(not_first_item ? 0 : (position * DURATION_IN_BOTTOM_UP));
            animatorTranslateY.setDuration((not_first_item ? 3 : 1) * DURATION_IN_BOTTOM_UP);
            animatorSet.playTogether(animatorTranslateY, animatorAlpha);
            animatorSet.start();
        }

    Fade-In

     private static void animateFadeIn(View view, int position) {
            boolean not_first_item = position == -1;
            position = position + 1;
            view.setAlpha(0.f);
            AnimatorSet animatorSet = new AnimatorSet();
            ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(view, "alpha", 0.f, 0.5f, 1.f);
            ObjectAnimator.ofFloat(view, "alpha", 0.f).start();
            animatorAlpha.setStartDelay(not_first_item ? DURATION_IN_FADE_ID / 2 : (position * DURATION_IN_FADE_ID / 3));
            animatorAlpha.setDuration(DURATION_IN_FADE_ID);
            animatorSet.play(animatorAlpha);
            animatorSet.start();
        }

    Left to right

    private static void animateLeftRight(View view, int position) {
            boolean not_first_item = position == -1;
            position = position + 1;
            view.setTranslationX(-400f);
            view.setAlpha(0.f);
            AnimatorSet animatorSet = new AnimatorSet();
            ObjectAnimator animatorTranslateY = ObjectAnimator.ofFloat(view, "translationX", -400f, 0);
            ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(view, "alpha", 1.f);
            ObjectAnimator.ofFloat(view, "alpha", 0.f).start();
            animatorTranslateY
                    .setStartDelay(not_first_item ? DURATION_IN_LEFT_RIGHT : (position * DURATION_IN_LEFT_RIGHT));
            animatorTranslateY.setDuration((not_first_item ? 2 : 1) * DURATION_IN_LEFT_RIGHT);
            animatorSet.playTogether(animatorTranslateY, animatorAlpha);
            animatorSet.start();
        }

    Right to left

    private static void animateRightLeft(View view, int position) {
            boolean not_first_item = position == -1;
            position = position + 1;
            view.setTranslationX(view.getX() + 400);
            view.setAlpha(0.f);
            AnimatorSet animatorSet = new AnimatorSet();
            ObjectAnimator animatorTranslateY = ObjectAnimator.ofFloat(view, "translationX", view.getX() + 400, 0);
            ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(view, "alpha", 1.f);
            ObjectAnimator.ofFloat(view, "alpha", 0.f).start();
            animatorTranslateY
                    .setStartDelay(not_first_item ? DURATION_IN_RIGHT_LEFT : (position * DURATION_IN_RIGHT_LEFT));
            animatorTranslateY.setDuration((not_first_item ? 2 : 1) * DURATION_IN_RIGHT_LEFT);
            animatorSet.playTogether(animatorTranslateY, animatorAlpha);
            animatorSet.start();
        }

    Integration

    In order for this animation to work, we can call the methods from the adapter itself, passing the view object and position into the constructor. But first, we must pass our data and the type of animation we wish to use into the constructor of our adapter.

    public class AdapterListAnimation extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        //Context which this adapter is used in
        private Context mContext;
    
        //The animation type we want to use
        private int animation_type;
    
        //Our data to be shown
        private List<Data> mDataList;
        
        public AdapterListAnimation(Context context, List<Data> dataList, int animation_type) {
            this.mDataList = dataList;
            mContext = context;
            this.animation_type = animation_type;
        }

    Here is our onBindViewHolder where we will pass the view object from and the relative position.

    @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
    
            if (holder instanceof OriginalViewHolder) {
                OriginalViewHolder view = (OriginalViewHolder) holder;
    
                Data data = mDataList.get(position);
                view.image.setBackgroundResource(data.getImageId());
                view.description.setText(data.getLoremIpsum());
                
                //set our animation for each view and it's associated position
                setAnimation(view.itemView, position);
    
    
            }
        }

    Place the method below somewhere inside the adapter. We call this from our onBindViewHolder. We also require two extra variables:

    • lastPosition – Initially set to -1, this variable will help us compare the position returned by the onBindViewHolder and the previous or last position that was registered.
    • on_attach – Initially set to true, we use this boolean value to identify when the user has scrolled through the list. More on that later.
       
        private int lastPosition = -1;
    
        private boolean on_attach = true;
    
        private void setAnimation(View view, int position) {
            if (position > lastPosition) {
                //When we scroll down, on_attach will become false and so we return -1
                //-1 will change the animation effect, making it appear differently
                ItemAnimation.animate(view, on_attach ? position : -1, animation_type);
    
                lastPosition = position;
            }
        }

    Override onAttachedToRecyclerView add an onScrollListener as this will allow us to detect when the user has scrolled down so that every new view that is created onwards the animation effect is now different in the following ways:

    • Decrease the duration and eliminate the delay of the animation so that the views are loaded more quickly.
    • Decrease the translation values the animation will animate over time.

    Refer back to the animation class, if not_first_item is equal to true, we change the float values and milliseconds for the animation attributes.

     @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    //When the user scrolls down, we change the translation of the animation
                    //This will make it look much better.
                    on_attach = false;
                    super.onScrollStateChanged(recyclerView, newState);
                }
            });
            super.onAttachedToRecyclerView(recyclerView);
        }

    That is all we need for the above animations to work. As you can see, the code is quite easy to understand and implement. All we did was define a custom animation class, pass our view objects into the desired methods and voila! Now onto the bonus…

    BONUS : ADDING A SHIMMERING EFFECT

    dependencies {
      implementation 'com.facebook.shimmer:shimmer:0.5.0'
    }

    XML Layout – Simply wrap your view with the ShimmerFrameLayout:

    <com.facebook.shimmer.ShimmerFrameLayout
         android:id="@+id/shimmer_view_container"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
    >
         ...(Your view will go here)...
    </com.facebook.shimmer.ShimmerFrameLayout>

    AdapterListAnimation.java – Define two global variables:

    //true only when the data is still being fetched/prepared
        public boolean showShimmer = true;
    
        //The number of items we want to be shown shimmering
        private int shimmer_item_number = 7;

    Find and assign our view object…

    public ShimmerFrameLayout; 
    mShimmerFrameLayout = v.findViewById(R.id.root_layout);

    onBindViewHolder:

    @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
    
            if (holder instanceof OriginalViewHolder) {
                OriginalViewHolder view = (OriginalViewHolder) holder;
    
                //If loading data, show shimmer effect
                if (showShimmer && view.mShimmerFrameLayout != null) {
                    view.mShimmerFrameLayout.startShimmer();
    
                } else {
                 //stop and nullify the shimmer
    
                    view.mShimmerFrameLayout.stopShimmer();
                    view.mShimmerFrameLayout.setShimmer(null);
                    Data data = mDataList.get(position);
                    view.image.setBackgroundResource(data.getImageId());
                    view.description.setText(data.getLoremIpsum());
                }
            }
        }
     @Override
        public int getItemCount() {
            //if showShimmer = true, return shimmer_item_number, else, return our data size.
            return showShimmer ? shimmer_item_number : mDataList.size();
        }

    Pick & Mix

    Apply shimmering effect and another chosen animation. Now we have two, very cool animations operating at once.

    //Call this method after the shimmering has stopped and items have been loaded.
    setAnimation(view.itemView, position);

    works well for grid layouts too!

    WRAP UP

    I love animations. They give life to the elements and views which help to communicate with the audience much more effectively. The more scrupulous you are with the elements of your app, the more users will enjoy using it. The animations outlined above is one such example of creating top-notch and intuitive UX.

    Please be aware that while animations like these look very cool, they have the potential to slow down the app and especially on low powered devices. I suggest that you integrate an option to disable them in the settings menu.

    Source code can be found here.

    Leave a reply Cancel reply

    © 2020 Arrayly

    To Top