ViewPager进化版:ViewPager2
大名鼎鼎的翻页视图ViewPager和RecyclerView一样被众多广大的应用使用,抖音上下滑动观看视频用的主控件就是ViewPager。继ViewPager翻页控件,Google又推出了它的升级版本ViewPager2。
1、ViewPager2
“上一任”ViewPager有一些瑕疵,于是Google就推陈出新搞出了个ViewPager 2.0。
1.1、ViewPager2简介
ViewPager2是基于RecyclerView实现的,比之前的ViewPager 1.0更强大,至于如何从原来的ViewPager迁移升级到ViewPager2详见官方指导Migrate from ViewPager to ViewPager2。使用ViewPager2需要在build.gradle中添加如下的依赖:
implementation "androidx.viewpager2:viewpager2:1.0.0"1.2、对比ViewPager
ViewPager2不仅修复了之前ViewPager中的issue,而且还改进了很多:
- RTL(从右到左)布局支持
- 支持垂直方向
- 可靠的Fragment支持(包括处理对基础Fragment集合的更改)
- 数据集更改动画(包括DiffUtil支持)
2、API
ViewPager2相比于ViewPager接口做了一些修改,先来了解一下ViewPager2的API,并将这些方法按照功能分类。完整的API文档参考 Docs > Reference > ViewPager2">Android Developers > Docs > ViewPager2。
2.1、适配器Adapter
①setAdapter(Adapter adapter)
②getAdapter()
2.2、方向(水平|垂直)
①setOrientation(int orientation):设置ViewPager2翻译滚动方向,默认水平方向
②getOrientation()
//定义两个静态变量代表水平、垂直。从RecyclerView引用,而RecyclerView又使用了LinearLayout中定义的
public static final int ORIENTATION_HORIZONTAL = RecyclerView.HORIZONTAL;
public static final int ORIENTATION_VERTICAL = RecyclerView.VERTICAL;2.3、Item Direction装饰
①setLayoutDirection(int layoutDirection)
②addItemDecoration(ItemDecoration decor)
③addItemDecoration(ItemDecoration decor, int index)
④removeItemDecoration(@NonNull ItemDecoration decor)
⑤removeItemDecorationAt(int index)
⑥getItemDecorationAt(int index)
⑦getItemDecorationCount()
⑧invalidateItemDecorations()
2.4、Item位置
①setCurrentItem(int item)
②setCurrentItem(int item, boolean smoothScroll)
③getCurrentItem()
2.5、翻页监听
①registerOnPageChangeCallback(@NonNull OnPageChangeCallback callback)
②unregisterOnPageChangeCallback(OnPageChangeCallback callback)
/**
* 回调,用于响应所选页面的更改状态。
*/
public abstract static class OnPageChangeCallback {
/**
* 当滚动当前页面时(作为程序启动的平滑滚动的一部分或由用户启动的触摸滚动的一部分)
* 将调用此方法。
*
* @param position 当前显示的第一页的位置索引。
* 如果positionOffset不为零,则页面position + 1将可见。
* @param positionOffset [0,1)中的值表示与位置页面的偏移量。
* @param positionOffsetPixels 以像素为单位的值,指示与位置的偏移量。
*/
public void onPageScrolled(int position, float positionOffset,@Px int positionOffsetPixels) {
}
/**
* 当选择新页面时,将调用此方法。动画不一定是完整的。
*
* @param position 新选定页面的位置索引。
*/
public void onPageSelected(int position) {
}
/**
* 滚动状态更改时调用。
* 对于发现用户何时开始拖动,何时开始伪拖动,何时自动将寻呼机稳定到当前页面或何时完全停止/空闲,很有用。
* 传入值state可以是SCROLL_STATE_IDLE/SCROLL_STATE_DRAGGING/SCROLL_STATE_SETTLING之一。
*/
public void onPageScrollStateChanged(@ScrollState int state) {
}
}2.6、翻页动画PageTransformer
PageTransformer接口用于设置翻页动画,需要高超的算法设计,详见Slide between fragments using ViewPager2
①setPageTransformer(PageTransformer transformer):设置翻页动画
②requestTransform()
/**
* 每当滚动可见/附加页面时,就会调用PageTransformer
* 将自定义转换动画应用于页面视图
*/
public interface PageTransformer {
/**
* 将属性转换应用于给定页面.
*
* @param page 将转换应用到此页面
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -2 is two pages to the left.
* Minimum / maximum observed values depend on how many pages we keep
* attached, which depends on offscreenPageLimit.
*
* @see #setOffscreenPageLimit(int)
*/
void transformPage(@NonNull View page, float position);
}2.7、滚动
①canScrollHorizontally(int direction):是否可以沿水平方向滚动
②canScrollVertically(int direction):是否可以沿垂直方向滚动
③getScrollState():返回ViewPager2的当前滚动状态。
/**
* 指示ViewPager2处于空闲,稳定状态。 当前页面已完全显示,没有动画正在进行。
*/
public static final int SCROLL_STATE_IDLE = 0;
/**
* 指示用户当前正在拖动ViewPager2,或者通过伪造的拖动功能以编程方式拖动ViewPager2。
*/
public static final int SCROLL_STATE_DRAGGING = 1;
/**
* 指示ViewPager2正在稳定到最终位置。
*/
public static final int SCROLL_STATE_SETTLING = 2;2.8、用户输入拦截
①setUserInputEnabled(boolean enabled):启用或禁用用户滚动ViewPager2
②isUserInputEnabled()
2.9、模拟拖动Drag
①beginFakeDrag():开始模拟拖动
②endFakeDrag():结束模拟拖动
③fakeDragBy(float offsetPxFloat)
④isFakeDragging():当前是否是模拟拖动
2.10、偏移
①setOffscreenPageLimit(int limit):设置应保留在当前可见页面两侧的页面数
②getOffscreenPageLimit()
2、适配器Adapter
ViewPager2支持两种数据适配器,一种是RecyclerView.Adapter,毫无疑问,因为ViewPager2就是借助RecyclerView实现的;另外一个是Fragment适配器FragmentStateAdapter。
2.1、RecyclerView.Adapter<VH extends ViewHolder>
RecyclerView.Adapter<VH extends ViewHolder>再熟悉不过了,经常使用RecyclerView,比传统的ListView更受欢迎。关于RecyclerView.Adapter及RecyclerView另见《RecyclerView的基本使用》。
public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(@NonNull View itemView) {
super(itemView);
}
}
}一个标准的适配器如上继承自RecyclerView.Adapter,再实现RecyclerView.Adapter的三个抽象方法onCreateViewHolder、onBindViewHolder、getItemCount;内部自定义ViewHolder继承自RecyclerView.ViewHolder,里面可以添加View成员变量用来绑定,减少findViewById()的调用次数;通常还会添加Context和一些其它数据作为成员变量,通过构造函数传递初始化。
2.2、FragmentStateAdapter
继承FragmentStateAdapter类实现另一种Adapter,FragmentStateAdapter在ViewPager2包中androidx.viewpager2.adapter
public class DemoAdapter extends FragmentStateAdapter {
public DemoAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
public DemoAdapter(@NonNull Fragment fragment) {
super(fragment);
}
public DemoAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
}
@Override
public Fragment createFragment(int position) {
return null;
}
@Override
public int getItemCount() {
return 0;
}
}只需要实现createFragment、getItemCount两个抽象方法即可;关于Fragment详见《Fragment碎片用法》。
3、实例
Android控件这么多,经常使用才能熟练。ViewPager、RecyclerView、Fragment这些不经常用很容易忘掉使用细节(但是看一下demo或者以前写过的代码很快又能记起来)。官方的demo在GitHub上有,可以下载下来学习学习:views-widgets-samples/ViewPager2
3.1、简单使用ViewPager2
①创建ViewPager2的Adapter:实现onCreateViewHolder、onBindViewHolder、getItemCount三个抽象方法,并且一个ViewHolder静态内部类;再提供一些测试数据集(每次个人Demo数据构造比较麻烦,或者需要像抖音/头条那么多的数据来测试就好了)
public class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2Adapter.ViewHolder> {
private Context mContext;
private List<Integer> mList = new ArrayList<Integer>();
public ViewPager2Adapter(Context mContext) {
this.mContext = mContext;
mList.add(Color.parseColor("#181d4b"));
mList.add(Color.parseColor("#78a355"));
mList.add(Color.parseColor("#845538"));
mList.add(Color.parseColor("#b76f40"));
mList.add(Color.parseColor("#e0861a"));
}
public ViewPager2Adapter(Context mContext, List<Integer> mList) {
this.mContext = mContext;
this.mList = mList;
}
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.view_pager_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.mContainer.setBackgroundColor(mList.get(position));
holder.mTextView.setText("ViewPager2Adapter: " + mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private View mContainer;
private TextView mTextView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
mContainer = (View) itemView.findViewById(R.id.container);
mTextView = (TextView) itemView.findViewById(R.id.text);
}
}
}②布局中方式ViewPager2
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_page2"
android:layout_width="match_parent"
android:layout_height="match_parent" />③Activity中初始化ViewPager2,并进设置Adapter还有其它一些设置
ViewPager2 mViewPager2 = findViewById(R.id.view_page2);
RecyclerView.Adapter<ViewPager2Adapter.ViewHolder> adapter = new ViewPager2Adapter(this);
mViewPager2.setAdapter(adapter);
//mViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);设置翻页滚动方向,默认水平

3.2、简单的翻页动画
上面的翻页动画很枯燥,别玩了前面介绍的ViewPager2.PageTransformer,专门用来设置ViewPager2翻页时候的动画。详细设计说明见官方文档Slide between fragments using ViewPager2
mViewPager2.setPageTransformer(new ViewPager2.PageTransformer() {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
@Override
public void transformPage(@NonNull View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0f);
} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA +
(scaleFactor - MIN_SCALE) /
(1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0f);
}
}
});
参考资料:
Docs > Reference > ViewPager2">Android Developers > Docs > Reference > ViewPager2
Slide between fragments using ViewPager2
Migrate from ViewPager to ViewPager2
ViewPager2 AndroidX Dependencies
Androidx 中的 ViewPager 与 ViewPager2

