Android的ViewPager2实战详解-入门到精通

ViewPager2正式版的发布已经一年多了,目前ViewPager早已停止更新,官方鼓励使用ViewPager2替代。ViewPager2底层基于RecyclerView实现,因此可以获得RecyclerView带来的诸多收益:

ViewPager2历史版本更新记录
图片

ViewPager2目前的特性:
抛弃传统的PagerAdapter,统一了Adapter的API;
横向、竖向布局都可以实现自由滑动;
支持DiffUitl,可以实现局部刷新;
支持RTL(right-to-left),对于一些有出海需求的APP非常有用;
支持ItemDecorator,搭配PageTransformer实现炫酷的跳转动画;

  1. 应用场景

上滑吸顶

CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout

左右滑动

ViewPager2+TabLayout+Fragment

横滑和竖滑列表

RecycleView+NestedScrollableHost
例图如下

图片

2.RecycleView和Viewpage2的滑动冲突

class RecyclerViewAtViewPager2 : RecyclerView {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

var x1 = 0f
var x2 = 0f
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
    if(event!!.action == MotionEvent.ACTION_DOWN) {
        x1 = event.x
    } else if(event.action == MotionEvent.ACTION_MOVE) {
        x2 = event.x
    } else if (event.action == MotionEvent.ACTION_CANCEL
        || event.action == MotionEvent.ACTION_UP) {
        x2 = 0f
        x1 = 0f
    }
    val xOffset= x2-x1
    if (layoutManager is LinearLayoutManager) {
        val linearLayoutManager = layoutManager as LinearLayoutManager
        if (linearLayoutManager.orientation == HORIZONTAL) {
            if ((xOffset <= 0 && canScrollHorizontally(1))
                || (xOffset >= 0 && canScrollHorizontally(-1))) {
                this.parent?.requestDisallowInterceptTouchEvent(true)
            } else {
                this.parent?.requestDisallowInterceptTouchEvent(false)
            }
        } else {
            // TODO: 2021/4/8 目前没有实现上下滑动和 [androidx.viewpager2.widget.ViewPager2]上下滑动的冲突
        }
    } else {
        handleDefaultScroll()
    }

    return super.dispatchTouchEvent(event)
}

fun handleDefaultScroll() {
    val canScrollHorizontally = canScrollHorizontally(-1) || canScrollHorizontally(1)
    val canScrollVertically = canScrollVertically(-1) || canScrollVertically(1)
    if (canScrollHorizontally || canScrollVertically) {
        this.parent?.requestDisallowInterceptTouchEvent(true)
    } else {
        this.parent?.requestDisallowInterceptTouchEvent(false)
    }
}

}

3.ViewPager2中Fragment的懒加载
懒加载

一般我们使用Fragment对页面进行数据懒加载的时候都是通过onHiddenChanged方法判断显示和隐藏,在第一次展现出来的时候再进行接口调用。

@Override
public final void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
onUserVisible();
} else {
onUserGone();
}
}
但在ViewPager2中,Fragment的setUserVisibleHint和onHiddenChanged方法都是不执行的。
ViewPager展现第一个页面,然后切后台的日志:
04-17 16:45:10.992 D/tanzhenxing:11(22006): onCreate
04-17 16:45:10.992 D/tanzhenxing:11(22006): onCreateView:
04-17 16:45:11.004 D/tanzhenxing:11(22006): onActivityCreated
04-17 16:45:11.004 D/tanzhenxing:11(22006): onViewStateRestored: 184310198
04-17 16:45:11.004 D/tanzhenxing:11(22006): onStart
04-17 16:45:11.004 D/tanzhenxing:11(22006): onResume
04-17 16:45:18.739 D/tanzhenxing:11(22006): onPause
04-17 16:45:18.779 D/tanzhenxing:11(22006): onStop
然后在切回前台的日志:
04-17 16:53:40.749 D/tanzhenxing:11(22006): onStart
04-17 16:53:40.752 D/tanzhenxing:11(22006): onResume
ViewPager展现第一个页面,然后手动滑动到第二个页面日志:
04-17 16:54:44.168 D/tanzhenxing:12(22006): onCreate
04-17 16:54:44.168 D/tanzhenxing:12(22006): onCreateView:
04-17 16:54:44.178 D/tanzhenxing:12(22006): onActivityCreated
04-17 16:54:44.178 D/tanzhenxing:12(22006): onViewStateRestored: 47009644
04-17 16:54:44.178 D/tanzhenxing:12(22006): onStart
04-17 16:54:44.553 D/tanzhenxing:11(22006): onPause
04-17 16:54:44.554 D/tanzhenxing:12(22006): onResume

这样看起来我们是可以利用Fragment声明周期中的onStart和onResume方法做懒加载。
预加载
只要讲数据请求写在 onCreateView 或者onStart中就能进行接口的离屏请求。

4.FragmentStateAdapter

ViewPager2继承自RecyclerView,大概率FragmentStateAdapter继承自RecyclerView.Adapter:

public abstract class FragmentStateAdapter extends
RecyclerView.Adapter implements StatefulAdapter {
}
4.1 onCreateViewHolder

onCreateViewHolder是RecycleVeiw用于创建ViewHolder的方法:

@NonNull
@Override
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return FragmentViewHolder.create(parent);
}

FragmentViewHolder的主要作用是通过FrameLayout为Fragment提供用作容器的container:
@NonNull
static FragmentViewHolder create(@NonNull
ViewGroup parent) {
FrameLayout container = new FrameLayout(parent.getContext());
container.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
container.setId(ViewCompat.generateViewId());
container.setSaveEnabled(false);
return new FragmentViewHolder(container);
}
onBindViewHolder
onBindViewHolder是RecycleVeiw用于数据绑定的方法:
@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
/部分代码省略 */ ensureFragment(position); /部分代码省略 */
gcFragments();
}

ensureFragment(position),其内部会最终回调用createFragment创建当前Fragment。
private void ensureFragment(int position) {
long itemId = getItemId(position);
if (!mFragments.containsKey(itemId)) {
// TODO(133419201): check if a Fragment provided here is a new Fragment
Fragment newFragment = createFragment(position);
newFragment.setInitialSavedState(mSavedStates.get(itemId));
mFragments.put(itemId, newFragment);
}
}

mFragments缓存创建的Fragment,供后面placeFramentInViewholder使用; gcFragments回收已经不再使用的的Fragment(对应的item已经删除),节省内存开销。

onViewAttachedToWindow
onViewAttachedToWindow是ViewHolder出现在页面中回调。

@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
//将FragmentViewHolder的container与当前Fragment绑定
placeFragmentInViewHolder(holder);
gcFragments();
}

4.2 FragmentStateAdapter使用

Fragment对象容器;
生产fragment识别的id;

class MyFragmentStateAdapter(val data: List, fragment: Fragment) : FragmentStateAdapter(fragment){
private val fragments = mutableMapOf()
override fun createFragment(position: Int): Fragment {
val value = data[position]
val fragment = fragments[value]
if (fragment != null) {
return fragment
}
val cardFragment =
NestedAllRecyclerViewFragment.create(value)
fragments[value] = cardFragment
return cardFragment
}

/**
 * 根据数据生成唯一id
 *
 * 如果不重写,那么在调用[notifyDataSetChanged]更新的时候
 *
 * 会抛出```new IllegalStateException("Fragment already added")```异常
 */
override fun getItemId(position: Int): Long {
    return data[position].toLong()
}

/**
 * 用来判断当前id对应的fragment是否添加过
 */
override fun containsItem(itemId: Long): Boolean {
    data.forEach {
        if (it.toLong() == itemId) {
            return true
        }
    }
    return false
}
override fun getItemCount(): Int {
    return data.size
}

}

获取Fragment实例

fun getCurrentFragment(position: Int): Fragment? =
fragment.childFragmentManager.findFragmentByTag(“f$position”)

5.ViewPager2滑动监听

public abstract static class OnPageChangeCallback { //当前页面开始滑动时
public void onPageScrolled(int position, float positionOffset,@Px int positionOffsetPixels) {
} //当页面被选中时
public void onPageSelected(int position) {
} //当前页面滑动状态变动时
public void onPageScrollStateChanged(@ScrollState int state) {
}
}
var pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
//需要注意的是postion需要做大于0的判断
}
}


简介:一个有10多年经验开发的android、java、前端等语言的老程序员,在这里一起聊聊技术,一起聊聊生活、一起聊聊中年危机的生存之道,一起进步一起加油,感兴趣的欢迎订阅;不定时的更新。欢迎关注微信公众号:Android开发编程
(0)
打赏 喜欢就点个赞支持下吧 喜欢就点个赞支持下吧

声明:本文来自“Android开发编程”,分享链接:https://www.zyxiao.com/p/290206    侵权投诉

网站客服
网站客服
内容投稿 侵权处理
分享本页
返回顶部