airbnb / epoxy

Epoxy is an Android library for building complex screens in a RecyclerView
https://goo.gl/eIK82p
Apache License 2.0
8.49k stars 733 forks source link

Carousel behavior #98

Closed marwanf1 closed 7 years ago

marwanf1 commented 7 years ago

Can you please provide a sample for working with carousels? For example, a horizontal RecyclerView inside the vertical RecyclerView. The issue I'm facing is when data is binded to the horizontal view it becomes a bit sluggish on binding.

Thank you

kevinthecity commented 7 years ago

My guess is this is probably unrelated to epoxy - but RecyclerView itself. They made recent updates to it in the support library to address an issue that sounds like what you're describing

https://developer.android.com/topic/libraries/support-library/revisions.html#25-1-0

Clients of nested RecyclerView widgets (for example, vertical scrolling list of horizontal scrolling lists) can get significant performance benefits by hinting the inner RecyclerView widgets’ layout managers how many items to prepare before being scrolled on screen. Call LinearLayoutManager.setInitialPrefetchItemCount(N), where N is the number of views visible per inner item...

elihart commented 7 years ago

This is a bit unrelated to Epoxy. I've spent a lot of time experimenting to find the best ways to deal with carousels though for our own app. The main trick is to make sure that you are properly recycling views when changing the carousel adapter. I'll try to post some examples soon.

marwanf1 commented 7 years ago

@kevinthecity Thanks that's a really helpful method.

@elihart I know it's not an issue in Epoxy but the main reason I opened this issue is to maybe add a carousel example to the sample app. Most applications are using carousels and it would be helpful to see how to apply that in Epoxy. Looking forward to the examples thanks.

elihart commented 7 years ago

@marwanf1 Here is a carousel model very similar to what we use

public class CarouselEpoxyModel<A extends EpoxyAdapter> extends AirEpoxyModel<RecyclerView> {
    @EpoxyAttribute A adapter;
    @EpoxyAttribute RecycledViewPool recycledViewPool;

    @LayoutRes
    public int getDefaultLayout() {
        return R.layout.view_holder_carousel;
    }

    @Override
    public void bind(RecyclerView carousel) {
        super.bind(carousel);
        // If there are multiple carousels showing the same item types, you can benefit by having a shared view pool between those carousels
        // so new views aren't created for each new carousel.
        if (recycledViewPool != null) {
            carousel.setRecycledViewPool(recycledViewPool);
        }

        // Carousels are generally fixed height. Using fixed size is a small optimization we can make in that case.
        carousel.setHasFixedSize(true);
        // Use swapAdapter instead of setAdapter, with 'removeAndRecycleExistingViews=false', so that the existing views in the carousel are
        // reused. This only makes sense if the old adapter and new adapter share items of the same type, which is usually the case.
        //
        // Another benefit is that this will show change animations for differences between the old adapter and new adapter.
        carousel.swapAdapter(adapter, false);
    }

    @Override
    public void unbind(RecyclerView carousel) {
        super.unbind(carousel);
        // We again use swapAdapter instead of setAdapter so that the view pool is not cleared. 'removeAndRecycleExistingViews=true' is used
        // since the carousel is going off screen and these views can now be recycled to be used in another carousel (assuming you have a shared view pool)
        carousel.swapAdapter(null, true);
    }
}

The key is using swapAdapter instead of setAdapter so that new views don't have to be created on every bind.

The shared view pool is another nice optimization if you have multiple recyclerviews in the same activity. You may want to look at https://github.com/airbnb/epoxy/issues/97 and use setRecycleChildrenOnDetach so that if you leave a fragment and come back the views are properly recycled.

I'd like to add a more complete example to the sample app later on when I have time, but I think this will help you for now. Let me know if it works and if you have any more questions.

marwanf1 commented 7 years ago

@elihart thank you so much its perfect and working smoothly.

elihart commented 7 years ago

Great! I'm also interested in experimenting with LinearLayoutManager.setInitialPrefetchItemCount(N) inside the carousel model, but haven't gotten to that yet.

I'd like to add this code to the sample app sometime in the new few weeks. Or maybe someone else can contribute a PR :)