13

I want to animate the change of my RecyclerViews GridLayoutManager. I defaulty show a list of items in a grid with 3 columns and the user can select to show more or less columns.

I would like the views in the RecyclerView to move/scale to their new positions, but I have no idea how this could be done.

What I want in the end

  • allow to scale the grid via an expand/contract touch gesture => I know how to do that
  • animate the change of the LayoutManager

Does anyone know how I can animate the change of the LayoutManager?

prom85
  • 16,896
  • 17
  • 122
  • 242
  • you could look into a similar [question](http://stackoverflow.com/questions/26539663/how-do-i-use-a-gridlayoutanimation-in-a-recyclerview) and the related [gist](https://gist.github.com/Musenkishi/8df1ab549857756098ba) – Droidekas Mar 10 '16 at 13:48
  • Then calling `setSpanCount` followed by `notifyDatasetChanged()` ? – Droidekas Mar 10 '16 at 13:53
  • another way I can see it being done is assuming a very high span count(100 or so) and on item gesture,you can change the span size for all items – Droidekas Mar 10 '16 at 13:55

3 Answers3

18

The source of inspiration here would be the Google Photos app,the stock Sony Gallery app

There are basically 2 approaches you can go with:

  1. You modify the spancount of the GridLayoutManager using setSpanCount(int)

  2. You set a very high span count(~100) use the SpanSizeLookUp to change the per item spanSize on the fly.

    • I have used the Gist provided by Musenkishi,for this answer to provide an animator to animate the changes in grid layout changes
    • I have used this approach in a sample GitHub project implementing the same.
    • Caveats:
      • I have currently used the click listener to keep modifying the the span size look up.This could be changed to a ItemGestureListener to capture pinch zoom events and change accordingly.
      • You need to determine a way to choose a span count so that all the items in a row occupy the entire screen width (and hence you do not see any empty space)
      • You call notifyItemRangeChanged using a runnable post delayed since you cannot call the notifyChanged methods from within bindView/createView etc.
      • After changing the span size,you need to notifyItemRangeChanged with an appropriate range so that all the items currently displayed on the screen are shifted accordingly.I have used (code at the bottom)

This is not a complete solution but a 2 hour solution for the same.You can obviously improve on all the points mentioned :). I hope to keep updating the sample since this kind of views have always fascinated me. Do not view this as the final solution but just a particular way of achieving this approach. If you were to use a StaggerredLayoutManager instead,you could easily avoid blank spaces between items.

public int calculateRange() {
     int start = ((GridLayoutManager)        grv.getLayoutManager()).findFirstVisibleItemPosition();
     int end = ((GridLayoutManager) grv.getLayoutManager()).findLastVisibleItemPosition();
     if (start < 0)
         start = 0;
     if (end < 0)
         end = getItemCount();
     return end - start;
  }
Community
  • 1
  • 1
Droidekas
  • 3,464
  • 2
  • 26
  • 40
  • 1
    Thanks for that (much easier than I thought) solution. Changing the span count (I could have thought about that myself) is the way to go IMHO. It animates quite good. And after changing it just calling the adapters `notifyItemRangeChanged` will start the animation correctly... Only drawback is (but I did not ask for more in my question), you have to use the `GridLayoutManager` only and can't switch between different `LayoutManagers`... Although, if you want to, the `GridLayoutManager` supports 1 column as well and therefore can be at least be used as a `LinearLayoutManager` as well... – prom85 Mar 31 '16 at 09:10
  • 1
    If you really want to smoothen out the animations more and improve the performance for single column layouts,I would recommend working on building your own layout manager: [This](http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/) guide is pretty good. – Droidekas Mar 31 '16 at 09:14
  • @Droidekas can you please help me out creating that animation on Android 7 quick settings? (happens to the quick tiles when you expand the shade - they're in a single row of items and then they expand to a grid of 3x3.) – Alex Newman Jan 10 '17 at 16:21
  • @AlexNewman Try doing this with LayoutAnimators instead of grid RVs. Sorry for the reallly late reply :) – Droidekas Dec 14 '17 at 05:02
5

I deal with the same problem as you, and so far I have not found a good solution.

Simple change of columns number in GridLayoutManager seems weird so for now I use animation to fade out/in entire layout. Something like this:

private void animateRecyclerLayoutChange(final int layoutSpanCount) {
    Animation fadeOut = new AlphaAnimation(1, 0);
    fadeOut.setInterpolator(new DecelerateInterpolator());
    fadeOut.setDuration(400);
    fadeOut.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            productsRecyclerLayoutManager.setSpanCount(layoutSpanCount);
            productsRecyclerLayoutManager.requestLayout();
            Animation fadeIn = new AlphaAnimation(0, 1);
            fadeIn.setInterpolator(new AccelerateInterpolator());
            fadeIn.setDuration(400);
            productsRecycler.startAnimation(fadeIn);
        }
    });
    productsRecycler.startAnimation(fadeOut);
}

If you combine fade out/in animation with scaling each visible item, It will be a decent animation for GridLayoutManager changes.

ZelvaJan
  • 353
  • 2
  • 9
  • I have a similar solution currently... Removing all items, changing the layout manager and adding back all items will result in an animation as well, but is not perfect either... Thanks for this alternative though. – prom85 Mar 10 '16 at 12:06
2

You can do this with "gesture detector" see the sample tutorial here http://wiki.workassis.com/pinch-zoom-in-recycler-view/ In this tutorial we will fetch images from gallery and show them in a grid layout in recycler view. You will be able to change layout on pinch gesture. Following are the screen shots of different layouts.

mScaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
                @Override
                public boolean onScale(ScaleGestureDetector detector) {
                    if (detector.getCurrentSpan() > 200 && detector.getTimeDelta() > 200) {
                        if (detector.getCurrentSpan() - detector.getPreviousSpan() < -1) {
                            if (mCurrentLayoutManager == mGridLayoutManager1) {
                                mCurrentLayoutManager = mGridLayoutManager2;
                                mRvPhotos.setLayoutManager(mGridLayoutManager2);
                                return true;
                            } else if (mCurrentLayoutManager == mGridLayoutManager2) {
                                mCurrentLayoutManager = mGridLayoutManager3;
                                mRvPhotos.setLayoutManager(mGridLayoutManager3);
                                return true;
                            }
                        } else if(detector.getCurrentSpan() - detector.getPreviousSpan() > 1) {
                            if (mCurrentLayoutManager == mGridLayoutManager3) {
                                mCurrentLayoutManager = mGridLayoutManager2;
                                mRvPhotos.setLayoutManager(mGridLayoutManager2);
                                return true;
                            } else if (mCurrentLayoutManager == mGridLayoutManager2) {
                                mCurrentLayoutManager = mGridLayoutManager1;
                                mRvPhotos.setLayoutManager(mGridLayoutManager1);
                                return true;
                            }
                        }
                    }
                    return false;
                }
            });
Bikesh M
  • 8,163
  • 6
  • 40
  • 52