一、ViewPager2介绍
1 简介
ViewPager2是Google 在 androidx 组件包里增加的一个组件,目前已经到了1.0.0-beta02版本。
谷歌为什么要出这个组件呢?官方是这么说的:
ViewPager2 replaces ViewPager, addressing most of its predecessor’s pain-points,
including right-to-left layout support, vertical orientation, modifiable Fragment collections, etc.
2 具体改动:
New features:
支持竖向滚动
完整支持notifyDataSetChanged
能够关闭用户输入 (setUserInputEnabled, isUserInputEnabled)
API changes:
FragmentStateAdapter 替代 FragmentStatePagerAdapter
RecyclerView.Adapter 替代 PagerAdapter
registerOnPageChangeCallback 替代 addPageChangeListener
3 附上官方链接:
官方文档
https://developer.android.google.cn/jetpack/androidx/releases/viewpager2#1.0.0-alpha01
官方Demo
https://github.com/googlesamples/android-viewpager2
二、ViewPager2的使用
1. 准备工作
- AndroidX适配参考文档:
https://developer.android.com/jetpack/androidx/migrate
https://www.jianshu.com/p/41de8689615d - 修改gradle.properties
android.useAndroidX=true
android.enableJetifier=true
android.useAndroidX=true 表示当前项目启用 AndroidX
android.enableJetifier=true 表示将依赖包也迁移到AndroidX 。如果取值为 false ,表示不迁移依赖包到AndroidX,但在使用依赖包中的内容时可能会出现问题,当然了,如果你的项目中没有使用任何三方依赖,那么,此项可以设置为 false
- 依赖库
implementation 'androidx.appcompat:appcompat:1.1.0-alpha05'
implementation 'com.android.support:design:28.0.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
2. xml文件
3. 常用Api
- void setOrientation(int orientation)
- void setUserInputEnabled(boolean enabled)
- int getCurrentItem()
- void setCurremt(int item)
- void addItemDecoration(RecyclerView.ItemDecoration decor)
- void addItemDecoration(RecyclerView.ItemDecoration decor, int index)
- void beginFakeDrag()
- endFakeDrag()
- getAdapter()
- setOffscreenPageLimit(int limit)
- setPageTransformer(ViewPager2.PageTransformer transformer)
- registerOnPageChangeCallback(OnPageChangeCallback).
- unregisterOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback)
4.ViewPager2的Demo
-
ViewPager2 with Views
viewPager2 = findViewById(R.id.viewpager2);
viewPager2.setAdapter(new ViewPagerAdapter());
public class ViewPagerAdapter extends RecyclerView.Adapter {
...
@NonNull
@Override
public ViewPagerAdapter.CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new CardViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewPagerAdapter.CardViewHolder holder, int position) {
holder.textView.setText(mDatas.get(position));
}
@Override
public int getItemCount() {
return mDatas.size();
}
public static class CardViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public CardViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_content);
}
}
}
是不是很简单?adapter和使用RecyclerView是一样的,这个大家都很熟悉了吧?
-
ViewPager2 with Fragments
viewPager.setAdapter(new ViewPagerFragmentStateAdapter(),colors);
public class ViewPagerFragmentStateAdapter extends FragmentStateAdapter {
@NonNull
@Override
public Fragment createFragment(int position) {
return PageFragment.newInstance(colors, position);
}
@Override
public int getItemCount() {
return colors.size();
}
}
ViewPager2和Fragment结合使用,需要使用FragmentStateAdapter。FragmentStateAdapter继承RecyclerView.Adapter,有兴趣的可以去看看源码。
-
ViewPager2 with TabLayout
mViewPager2.setAdapter(adapter);
new TabLayoutMediator(mTabLayout, mViewPager2, (tab, position) -> tab.setText(titles.get(position))).attach();
// 滑动监听
mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
androidx中,TabLayout没有setupWithViewPager(ViewPager2 viewPager2)方法,而是用TabLayoutMediator将TabLayout和ViewPager2结合。
- 几个api的使用示例和效果
- void setOffscreenPageLimit(boolean enable)
- void setUserInputEnabled(boolean enable)
- void beginFakeDrag()
- void notifyDataSetChanged();
Demo: ViewMutableActivity.java
public class ViewMutableActivity extends AppCompatActivity implements View.OnClickListener {
...
private void initViews() {
...
landscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
adapter = new MuTableViewPagerAdapter(this, model);
mViewPager2.setAdapter(adapter);
}
private void setListener() {
...
CheckBox checkBox = findViewById(R.id.disable_user_input_checkbox);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
mViewPager2.setUserInputEnabled(false);
} else {
mViewPager2.setUserInputEnabled(true);
}
});
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.buttonUpdate:
model.update(mViewPager2.getCurrentItem(), "update content");
adapter.notifyItemChanged(mViewPager2.getCurrentItem());
break;
case R.id.buttonAddBefore:
int oldPosition = mViewPager2.getCurrentItem();
String content = model.getData(oldPosition);
model.add(oldPosition, "is new data");
adapter.notifyDataSetChanged();
if (model.contains(content)) {
int newPositin = model.getPosition(content);
mViewPager2.setCurrentItem(newPositin,false);
}
break;
case R.id.buttonAddAfter:
int oldPosition1 = mViewPager2.getCurrentItem();
String content1 = model.getData(oldPosition1);
model.add(oldPosition1 + 1, "is new data");
adapter.notifyDataSetChanged();
if (model.contains(content1)) {
int newPositin = model.getPosition(content1);
mViewPager2.setCurrentItem(newPositin,false);
}
break;
case R.id.buttonRemove:
if(!TextUtils.isEmpty(editText.getText().toString())){
int oldPosition2 = Integer.parseInt(editText.getText().toString());
if(oldPosition2 < model.getSize()){
model.removeData(oldPosition2);
adapter.notifyDataSetChanged();
}
}
break;
case R.id.tv_vertical:
mViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
break;
case R.id.tv_horizontal:
mViewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
break;
case R.id.tv_scroll:
mViewPager2.setUserInputEnabled(true);
break;
case R.id.tv_unscroll:
mViewPager2.setUserInputEnabled(false);
break;
}
}
private final float getValue(MotionEvent event) {
return this.landscape ? event.getY() : event.getX();
}
private boolean handleOnTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastValue = getValue(event);
mViewPager2.beginFakeDrag();
break;
case MotionEvent.ACTION_MOVE:
float value = getValue(event);
float delta = value - lastValue;
mViewPager2.fakeDragBy(delta);
lastValue = value;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mViewPager2.endFakeDrag();
break;
}
return true;
}
}
三、ViewPager2到底好在哪里
1、使用更加方便
通过ViewPager2的介绍可以看出,ViewPager2实现滑动方向的切换,禁止滑动这些都有API,开发者可以很方便的根据需求进行修改。
而ViewPager则需要根据不同的情况,重写方法。
比如禁止滑动:
public class ScrollViewPager extends ViewPager {
...
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (isScroll) {
return super.onInterceptTouchEvent(event);
} else {
return false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isScroll) {
return super.onTouchEvent(event);
} else {
return true;
}
}
2、性能上提升
- ViewPager2实现了懒加载和View复用。
ViewPager2
/**
The given value must either be larger than 0, or {@code #OFFSCREEN_PAGE_LIMIT_DEFAULT(-1)}.
*/
public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1;
public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
throw new IllegalArgumentException(
"Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
}
mOffscreenPageLimit = limit;
// Trigger layout so prefetch happens through getExtraLayoutSize()
mRecyclerView.requestLayout();
}
ViewPager
public static final int DEFAULT_OFFSCREEN_PAGES = 1;
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
从源码中可以看出,ViewPager2的limit必须大于0或者是-1,而ViewPager的limit最小是1。VIewPager2可以不预加载,通过Fragment的生命周期可以验证。
- 刷新
ViewPager2支持局部刷新
notifyDataSetChanged();
notifyItemChanged(int position)
...
ViewPager 只能全局刷新
notifyDataSetChanged();
四、使用过程中的坑
- 官方ViewPager2 with TabLayout示例代码闪退,几个意思?
Caused by: java.lang.ClassCastException: Bootstrap method returned null
at com.google.android.material.tabs.TabLayout$TabView.addOnLayoutChangeListener(TabLayout.java:2592)
at com.google.android.material.tabs.TabLayout$TabView.update(TabLayout.java:2508)
at com.google.android.material.tabs.TabLayout$TabView.setTab(TabLayout.java:2437)
at com.google.android.material.tabs.TabLayout.createTabView(TabLayout.java:1501)
at com.google.android.material.tabs.TabLayout.newTab(TabLayout.java:855)
at com.google.android.material.tabs.TabLayoutMediator.populateTabsFromPagerAdapter(TabLayoutMediator.java:142)
at com.google.android.material.tabs.TabLayoutMediator.attach(TabLayoutMediator.java:118)
at com.example.myviewpager2.CardViewTabLayoutActivity.onCreate(CardViewTabLayoutActivity.kt:37)
at android.app.Activity.performCreate(Activity.java:7441)
at android.app.Activity.performCreate(Activity.java:7431)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1286)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3343)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3548)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:86)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2155)
at android.os.Handler.dispatchMessage(Handler.java:109)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:7539)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:958)
解决:
'com.google.android.material:material:1.1.0-alpha05' 替代
'com.google.android.material:material:1.1.0-alpha08'
- 如果在已创建的项目使用androidX,先看看需要注意的点
- 这里推荐一个android.support向androidX迁移的文章
https://www.jianshu.com/p/41de8689615d