ViewPager2 based on the realization of infinite round broadcast function. Support passed RecyclerView.Adapter can realize infinite round play, in principle, support any ReyclerView.Apdater framework.

Viewpager2 has been released, and its performance is better than ViewPager. You’ve seen the source code of Viewpager2 and you know that the ReyclerView is used internally as the core implementation, and the LinearLayoutManager is used for horizontal and vertical scrolling. Yes, ViewPager2 now supports vertical scrolling.

Introduction to the use of ViewPager2

Click view ViewPager2 introduction

ViewPager2 API changes
  • Replacing the original FragmentStatePagerAdapter FragmentStateAdapter
  • Recyclerview. Adapter replaces the original PagerAdapter
  • Replacing the original addPageChangeListener registerOnPageChangeCallback
Step 1. Rely on ViewPager2
implementation "Androidx. Viewpager2: viewpager2:1.0.0."
Copy the code

Note: this is the androidx library. If you are still using the support library in your project, you will need to migrate the support library to androidx.

Step 2.xml
<androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager2"
        android:layout_width="match_parent"
        android:layout_height="150dp"/>
Copy the code
Customize RecyclerView.Adapter
// Use ReyclerView mode, custom adapter
public class ImageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> // Or use another tripartite framework, such as:BRVAH
public class ImageAdapter extends BaseQuickAdapter<String.BaseViewHolder> {
    public ImageAdapter(a) {
        super(R.layout.item_image);
    }
    @Override
    protected void convert(@NonNull BaseViewHolder helper, String item) { Glide.with(mContext) .load(item) .into((ImageView) helper.getView(R.id.img)); }}Copy the code
Step 4. Use ViewPager2 in the page
 ViewPager2 viewPager2 = findViewById(R.id.viewpager2);
 ImageAdapter pager2Adapter = new ImageAdapter();
 pager2Adapter.addData(Utils.getData(2));
 viewPager2.setAdapter(pager2Adapter);
Copy the code

Use the ViewPager2 versionBanner

  • Support automatic rotation
  • Support a screen of three pages
  • Support custom indicators
  • Support for vertical scrolling
  • Support ViewPager2 page switching speed
  • Supports any RecyclerView. Adapter
  • Same way as ViewPager2

If you feel like it, then star support

Click to go to the project address

If you feel like it, then star support

Post a wave of renderings

Click on the downloadbanner.apkexperience

Basic use of the function, please download APK experience more smooth
describe Normal style On both sides of the zoom
Three pages of one screen at a time
IndicatorView IndicatorStyle
INDICATOR_CIRCLE INDICATOR_CIRCLE_RECT
INDICATOR_BEZIER INDICATOR_DASH
INDICATOR_BIG_CIRCLE
rendering 1 2
Collect more effects
Indicator View the simple code
.

Steps to Use Banner

Step 1. Rely on the banner

Click to go to the project address

Step 2.xml
<com.to.aboomy.pager2.Banner
	android:id="@+id/banner"
	android:layout_width="match_parent"
	android:layout_height="150dp"/>
Copy the code
Customize RecyclerView.Adapter
// Customize adapter
public class ImageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> // Use other tripartite frameworks, which are supported, such as:BRVAH
public class ImageAdapter extends BaseQuickAdapter<String.BaseViewHolder> {
    public ImageAdapter(a) { super(R.layout.item_image); }
    @Override
    protected void convert(@NonNull BaseViewHolder helper, String item) { Glide.with(mContext) .load(item) .into((ImageView) helper.getView(R.id.img)); }}Copy the code
Step 4. Use the Banner on the page

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        banner = findViewById(R.id.banner);
        // Use the built-in Indicator
        IndicatorView indicator = new IndicatorView(this)
              .setIndicatorColor(Color.DKGRAY)
              .setIndicatorSelectorColor(Color.WHITE);
        
        / / create the adapter
     	 ImageAdapter adapter = new ImageAdapter();
     	 
     	 // Pass recyclerView. Adapter to realize infinite round play
         banner.setIndicator(indicator)
              .setAdapter(adapter);
    }
Copy the code
Simply set one screen to three pages
// Set the width of the left and right pages exposed and the width between items
.setPageMargin(UIUtil.dip2px(this.20), UIUtil.dip2px(this.10))
// Built-in ScaleInTransformer, set toggle zoom animation
.setPageTransformer(true.new ScaleInTransformer())
Copy the code
Description of the method provided by Banner
The method name describe
setPageTransformer(ViewPager2.PageTransformer transformer) Set viewPager2 custom animation, support multiple additions
setOuterPageChangeListener(ViewPager2.OnPageChangeCallback listener) Set the slide listener for viewPager2
setAutoTurningTime(long autoTurningTime) Set the duration of automatic rowhead
setAutoPlay(boolean autoPlay) Whether to set automatic rosette. If the page is larger than 1 page, you can rosette
setIndicator(Indicator indicator) Set the indicator
setIndicator(Indicator indicator, boolean attachToRoot) Set the indicator
setAdapter(@Nullable RecyclerView.Adapter adapter) Load data when this method starts the round cast method, please call again last
setAdapter(@Nullable RecyclerView.Adapter adapter, int startPosition) Overload method to set the starting position of the round play
isAutoPlay() Whether to play infinite rounds
getCurrentPager() Gets the current position of viewPager2
startTurning() Start by
stopTurning() Stop by
setPageMargin(int multiWidth, int pageMargin) Set one screen to multiple pages
setPageMargin(int leftWidth, int rightWidth, int pageMargin) Set a screen of multiple pages, method overload
setOffscreenPageLimit(int limit) With viewPager2 usage
setOrientation(@ViewPager2.Orientation int orientation) Set the viewPager2 slide direction
ViewPager2 getViewPager2() Get viewpager2
RecyclerView.Adapter getAdapter() Get apdater
setPagerScrollDuration(long pagerScrollDuration) Set the switching duration of viewPager2

Third, the core of the idea of rotation

Amway is a carousel control, basically the same as the ViewPager version, using the count+2 method, to achieve unlimited carousel.

NeedCount (6) = count(4) + 2. There are 6 images in the actual round play, which are stored in the banner.

We can see that the actual index=0 is the last image and index=5 is the first image. We just swipe right to index=5 and pass viewpager.setCurrentitem (1, false); Viewpager.setcurrentitem (count, false); Switch to the last picture of the actual picture, and make a transition to achieve a circular rotation effect.

  • Three pages of one screen at a time

Let’s take 4 images as an example. A screen of 3 pages requires three images to be displayed at a time, which means that one image is loaded on both the left and right sides, that is, two more images are loaded. The required number is needCount(8) = count(4) + 4.

The same control slide to the last picture and the first image corresponding to the index position, to achieve the effect of the round, here is not much to say, the specific can see the project code implementation.

How to support any ReyclerView.Adapter can realize infinite round broadcast?

1. Why not encapsulate a BaseRecyclerAdapter like this to facilitate the creation of views in the Banner?

Mainly taking into account the RecyclerView commonly used, I believe that everyone’s respective projects have similar to BaseRecyclerAdapter package, plus a variety of ReyclerAdapter framework on the market, Therefore, I personally think that it is not necessary to encapsulate a class similar to BaseRecyclerAdapter in the banner and provide the implementation, and it will not meet most of the needs.

2. Based on ReyclerView.Apdater wrapper class implementation, support any Adapter framework

The Banner internally implements BannerAdapterWrapper, reyclerView. Apdater wrapper proxy class, BannerAdapterWrapper internally returns the real index through toRealPosition(position), Apdater is called to its delegate reyclerview. Apdater to return its true position. See the source implementation of the banner, pasted with the key code below:

    public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
        setAdapter(adapter, 0);
    }
    public void setAdapter(@Nullable RecyclerView.Adapter adapter, int startPosition) {
        bannerAdapterWrapper.registerAdapter(adapter);
        initPagerCount();
        startPager(startPosition);
    }
    
 private class BannerAdapterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

        private RecyclerView.Adapter adapter;

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            return adapter.onCreateViewHolder(parent, viewType);
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            adapter.onBindViewHolder(holder, toRealPosition(position));
        }

        @Override
        public int getItemViewType(int position) {
            return adapter.getItemViewType(toRealPosition(position));
        }

        @Override
        public int getItemCount(a) {
            return needCount;
        }

        void registerAdapter(RecyclerView.Adapter adapter) {
            if (this.adapter ! =null) {
                this.adapter.unregisterAdapterDataObserver(itemDataSetChangeObserver);
            }
            this.adapter = adapter;
            if (this.adapter ! =null) {
                this.adapter.registerAdapterDataObserver(itemDataSetChangeObserver); }}}// Monitor Adapter data changes and refresh the data
private RecyclerView.AdapterDataObserver itemDataSetChangeObserver = new RecyclerView.AdapterDataObserver() {
    @Override
    public final void onItemRangeChanged(int positionStart, int itemCount) { onChanged(); }

    @Override
    public final void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { onChanged(); }

    @Override
    public final void onItemRangeInserted(int positionStart, int itemCount) { onChanged(); }

    @Override
    public final void onItemRangeRemoved(int positionStart, int itemCount) { onChanged(); }

    @Override
    public final void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { onChanged(); }

    @Override
    public void onChanged(a) {
        if(viewPager2 ! =null&& bannerAdapterWrapper ! =null) { initPagerCount(); startPager(getCurrentPager()); }}Copy the code

5.ViewPager2 page speed switch too fast, how to set the page switch speed, the default is too fast, resulting in looks like a lagIssues

  • The ViewPager implementation also has this problem. Let’s take a look at how the ViewPager implementation solves this problem:
// Customize the scroller
class ViewPagerScroller extends Scroller {
    private int scrollDuration = 800;
    ViewPagerScroller(Context context) {
        super(context);
    }
    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        super.startScroll(startX, startY, dx, dy, scrollDuration);
    }
    @Override
    public void startScroll(int startX, int startY, int dx, int dy) {
        super.startScroll(startX, startY, dx, dy, scrollDuration);
    }
    void setScrollDuration(int scrollDuration) {
        this.scrollDuration = scrollDuration; }}// Reflection replaces the mScroller member variable in Viewpager
private void initViewPagerScroll(a) {
    try {
        Field scrollerField = ViewPager.class.getDeclaredField("mScroller");
        scrollerField.setAccessible(true);
        scrollerField.set(this, scroller);
    } catch (NoSuchFieldException | IllegalArgumentException e) {
        e.printStackTrace();
    } catch(IllegalAccessException e) { e.printStackTrace(); }}Copy the code
  • ViewPager2 internal is based on ReyclerView, how to control the switching speed of the page, in fact, is to control the switching speed of RecyclerView, so search, see how to modify the ReyclerView scrolling speed, SmoothScrollToPosition is our core method, which is implemented in The LayoutManger, so we hook up to replace the LinearLayoutManager in ViewPager2. Custom LinearSmoothScroller handles the sliding time.

Hook mode to replace LinearLayoutManager in ViewPager2

Viewing the Viewpager2 source code, the LayoutManger set internally on the RcyclerView is an extension of the LinearLayoutManagerImpl based on the LinearLayoutManager.

private class LinearLayoutManagerImpl extends LinearLayoutManager {
    LinearLayoutManagerImpl(Context context) {
        super(context);
    }

    @Override
    public boolean performAccessibilityAction(@NonNull RecyclerView.Recycler recycler,
            @NonNull RecyclerView.State state, int action, @Nullable Bundle args) {
        if (mAccessibilityProvider.handlesLmPerformAccessibilityAction(action)) {
            return mAccessibilityProvider.onLmPerformAccessibilityAction(action);
        }
        return super.performAccessibilityAction(recycler, state, action, args);
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(@NonNull RecyclerView.Recycler recycler,
            @NonNull RecyclerView.State state, @NonNull AccessibilityNodeInfoCompat info) {
        super.onInitializeAccessibilityNodeInfo(recycler, state, info);
        mAccessibilityProvider.onLmInitializeAccessibilityNodeInfo(info);
    }

    @Override
    protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
            @NonNull int[] extraLayoutSpace) {
        int pageLimit = getOffscreenPageLimit();
        if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
            // Only do custom prefetching of offscreen pages if requested
            super.calculateExtraLayoutSpace(state, extraLayoutSpace);
            return;
        }
        final int offscreenSpace = getPageSize() * pageLimit;
        extraLayoutSpace[0] = offscreenSpace;
        extraLayoutSpace[1] = offscreenSpace;
    }

    @Override
    public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent,
            @NonNull View child, @NonNull Rect rect, boolean immediate,
            boolean focusedChildVisible) {
        return false; // users should use setCurrentItem instead}}Copy the code

Define a proxy class for the “object to hook” and create an object of that class

private class ProxyLayoutManger extends LinearLayoutManager {

		// This is the LinearLayoutManagerImpl object in ViewPager2
        private RecyclerView.LayoutManager linearLayoutManager;

        ProxyLayoutManger(Context context, RecyclerView.LayoutManager layoutManager) {
            super(context);
            this.linearLayoutManager = layoutManager;
        }

        @Override
        public boolean performAccessibilityAction(@NonNull RecyclerView.Recycler recycler,
                                                  @NonNull RecyclerView.State state, int action, @Nullable Bundle args) {
            return linearLayoutManager.performAccessibilityAction(recycler, state, action, args);
        }

        @Override
        public void onInitializeAccessibilityNodeInfo(@NonNull RecyclerView.Recycler recycler,
                                                      @NonNull RecyclerView.State state, @NonNull AccessibilityNodeInfoCompat info) {
            linearLayoutManager.onInitializeAccessibilityNodeInfo(recycler, state, info);
        }

        @Override
        public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent,
                                                     @NonNull View child, @NonNull Rect rect, boolean immediate,
                                                     boolean focusedChildVisible) {
            return linearLayoutManager.requestChildRectangleOnScreen(parent, child, rect, immediate);
        }
			
		 // How the core handles page switching speed
        @Override
        public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
            LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
                @Override
                protected int calculateTimeForDeceleration(int dx) {
                    return (int) (pagerScrollDuration * (1 - 3356.)); }}; linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); }@Override
        protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
                                                 @NonNull int[] extraLayoutSpace) {
            int pageLimit = viewPager2.getOffscreenPageLimit();
            if (pageLimit == ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT) {
                super.calculateExtraLayoutSpace(state, extraLayoutSpace);
                return;
            }
            final int offscreenSpace = getPageSize() * pageLimit;
            extraLayoutSpace[0] = offscreenSpace;
            extraLayoutSpace[1] = offscreenSpace;
        }

        private int getPageSize(a) {
            final RecyclerView rv = (RecyclerView) viewPager2.getChildAt(0);
            returngetOrientation() == RecyclerView.HORIZONTAL ? rv.getWidth() - rv.getPaddingLeft() - rv.getPaddingRight() : rv.getHeight() - rv.getPaddingTop() - rv.getPaddingBottom(); }}Copy the code

The member linearLayoutManager, which is actually the LinearLayoutManagerImpl object in ViewPager2, the method that it duplies, which we’ll duplicate in our proxy class, and call to its real implementation through the linearLayoutManager, It also overwrites the core of our requirement, the smoothScrollToPosition method for handling switching speeds.

Finally, replace the LinearLayoutManagerImpl in ViewPager2

Post the core code

 private void initViewPagerScrollProxy(RecyclerView recyclerView) {
        try {
            Field LayoutMangerField = ViewPager2.class.getDeclaredField("mLayoutManager");
            LayoutMangerField.setAccessible(true);
            LinearLayoutManager o = (LinearLayoutManager) LayoutMangerField.get(viewPager2);
            ProxyLayoutManger proxyLayoutManger = new ProxyLayoutManger(getContext(), o);
            recyclerView.setLayoutManager(proxyLayoutManger);
            LayoutMangerField.set(viewPager2, proxyLayoutManger);
            Field pageTransformerAdapterField = ViewPager2.class.getDeclaredField("mPageTransformerAdapter");
            pageTransformerAdapterField.setAccessible(true);
            Object mPageTransformerAdapter = pageTransformerAdapterField.get(viewPager2);
            if(mPageTransformerAdapter ! =null) { Class<? > aClass = mPageTransformerAdapter.getClass(); Field layoutManager = aClass.getDeclaredField("mLayoutManager");
                layoutManager.setAccessible(true);
                layoutManager.set(mPageTransformerAdapter, proxyLayoutManger);
            }
            Field scrollEventAdapterField = ViewPager2.class.getDeclaredField("mScrollEventAdapter");
            scrollEventAdapterField.setAccessible(true);
            Object mScrollEventAdapter = scrollEventAdapterField.get(viewPager2);
            if(mScrollEventAdapter ! =null) { Class<? > aClass = mScrollEventAdapter.getClass(); Field layoutManager = aClass.getDeclaredField("mLayoutManager");
                layoutManager.setAccessible(true); layoutManager.set(mScrollEventAdapter, proxyLayoutManger); }}catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch(IllegalAccessException e) { e.printStackTrace(); }}Copy the code

The last

If you feel like it, then star support

Click to go to the project address

If you feel like it, then star support