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