前言
ViewPager2已经出来很长一段时间了,但之前一直都是alpha版本,几次版本迭代中,内容细节变化也挺多,前阵子第一个正式版发布,不巧新冠肺炎爆发,在家里索性把之前的预研Demo整理整理,梳理下内容点写一篇博客,也算把预研工作正式收个尾。
首先先感谢一个我确实记不得的大兄嘚,预研的demo的前身,来自于GitHub,是我很久之前看alpha版本的使用时下载的,只下了一个zip,着实找不到源头了。年前开始预研的时候,我正好发现电脑里面有一个项目,就懒得新建,直接改了一通。
先贴上本文的 demo地址 (https://github.com/leobert-lan/ViewPager2-Demo/tree/master)
本文内容梗概
本文的篇幅会比较长,先给一个大致的内容梗概:
- 用于展示普通视图,横向和纵向滑动
- 结合Fragment使用以及Fragment的生命周期
- 复杂布局下,常见的结构,配合CoordinatorLayout和NestedScroll使用
- 懒、预加载和状态恢复,主要观测生命周期
- 实现原理和源码分析
最关键是我写写停停,有时候一篇文章拖个把月,不先放个梗概我自己都会忘了要写啥。
使用介绍
展示普通视图
按照以往用ViewPager的经验,我们会使用到三个东西:
- ViewPager实例
- 一个适配器实例
- 子视图
而使用ViewPager2也是类似的,我们需要一个ViewPager2实例,一个相应的适配器Adapter(RecyclerView.Adapter的子类),子视图和相应的RecyclerView.ViewHolder子类实例。
我们可能对ViewPager2有点或多或少的了解,他是通过RecyclerView做的功能实现,这里先不展开。
package com.example.viewpager2demo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
* leobert
*/
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewPagerViewHolder> {
private List<Integer> colors = new ArrayList<>();
{
colors.add(android.R.color.black);
colors.add(android.R.color.holo_purple);
colors.add(android.R.color.holo_blue_dark);
colors.add(android.R.color.holo_green_light);
}
@NonNull
@Override
public ViewPagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewPagerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_page, parent,false));
}
@Override
public void onBindViewHolder(@NonNull ViewPagerViewHolder holder, int position) {
holder.mTvTitle.setText("item " + position);
holder.mContainer.setBackgroundResource(colors.get(position));
}
@Override
public int getItemCount() {
return colors.size();
}
class ViewPagerViewHolder extends RecyclerView.ViewHolder {
TextView mTvTitle;
RelativeLayout mContainer;
public ViewPagerViewHolder(@NonNull View itemView) {
super(itemView);
mContainer = itemView.findViewById(R.id.container);
mTvTitle = itemView.findViewById(R.id.tvTitle);
}
}
}
布局都是比较简单的内容,这里不贴了。
使用的时候:
setContentView(R.layout.activity_horizontal_scrolling);
ViewPager2 viewPager2 = findViewById(R.id.viewpager2);
ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter();
viewPager2.setAdapter(viewPagerAdapter);
使用也是很简单,具体可以看demo中的HorizontalScrolling
那么纵向滚动呢?
setContentView(R.layout.activity_horizontal_scrolling);
ViewPager2 viewPager2 = findViewById(R.id.viewpager2);
ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter();
viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
viewPager2.setAdapter(viewPagerAdapter);
只需要设置下方向即可,ViewPager的默认方式是横向的,这点我们可以在源码中找到:
androidx.viewpager2.widget.ViewPager2#initialize方法中,实例化了LayoutManager,并调用了androidx.viewpager2.widget.ViewPager2#setOrientation(android.content.Context, android.util.AttributeSet)
private void setOrientation(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewPager2);
if (Build.VERSION.SDK_INT >= 29) {
saveAttributeDataForStyleable(context, R.styleable.ViewPager2, attrs, a, 0, 0);
}
try {
setOrientation(
a.getInt(R.styleable.ViewPager2_android_orientation, ORIENTATION_HORIZONTAL));
} finally {
a.recycle();
}
}
可以看到默认方向就是横向的。
这里我们简单小结一下:
ViewPager2可以像RecyclerView一样使用,并且可以配滑动方向了。我们只需要一个ViewPager2、一套ViewHolder,一个Adapter,就可以开始使用了。
结合Fragment的使用
结合我们日常工作实际,现在单独使用VP (ViewPager,如无特殊必要,下文都会使用VP来指代滑动控件,语义上如无必要区分ViewPager和ViewPager2,均以VP指代,否则以VP2指代ViewPager2)去呈现View的场景是比较少的,往往是用来显示图片,更多的业务往往会结合生命周期感知、以及需要实现业务“组件化”(这里的组件化指的是其环境相对独立,便于场景移植快速使用),而实际案例中都是使用Fragment去作为业务的承载。
我们知道,VP结合Fragment使用是一个很常见的套路,那么VP2中是否可以呢?当然是可以的,否则不支持向后兼容性迭代,这个VP2就是个笑话了。
言归正传、结合我们使用VP+Fragment的经验,我们会需要VP、FragmentPagerAdapter或者FragmentStatePagerAdapter和一系列的Fragment,限于内容主题和篇幅就放Demo代码了。ok,按照老经验,我们推断这里也会使用到VP2,一个特定的Adapter,按照兼容原则,Fragment应该没有特殊限制。
经过一番阅读,我们知道了Google的工程师们给我们提供的是:
androidx.viewpager2.adapter.FragmentStateAdapter
我们“特定的Adapter”就是这玩意了,先上代码,回头再看细节:
class ViewPagerFragmentStateAdapter extends FragmentStateAdapter {
public ViewPagerFragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
public ViewPagerFragmentStateAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
}
@NonNull
@Override
public Fragment createFragment(int position) {
if (position == 0)
return RvFragment.Companion.newInstance();
else
return PageFragment.newInstance(colors, position - 1);
}
@Override
public int getItemCount() {
return colors.size() + 1;
}
}
这里我摘了一段Demo中的内容,抛去里面的方法具体实现,使用的时候,构造器真正有用的是第二种,给FragmentActivity或者Fragment(这里没有体现),都是取出对应的FragmentManager和Lifecycle的,我们可以扫一眼源码:
/**
* @param fragmentActivity if the {@link ViewPager2} lives directly in a
* {@link FragmentActivity} subclass.
*
* @see FragmentStateAdapter#FragmentStateAdapter(Fragment)
* @see FragmentStateAdapter#FragmentStateAdapter(FragmentManager, Lifecycle)
*/
public FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());
}
/**
* @param fragment if the {@link ViewPager2} lives directly in a {@link Fragment} subclass.
*
* @see FragmentStateAdapter#FragmentStateAdapter(FragmentActivity)
* @see FragmentStateAdapter#FragmentStateAdapter(FragmentManager, Lifecycle)
*/
public FragmentStateAdapter(@NonNull Fragment fragment) {
this(fragment.getChildFragmentManager(), fragment.getLifecycle());
}
/**
* @param fragmentManager of {@link ViewPager2}'s host
* @param lifecycle of {@link ViewPager2}'s host
*
* @see FragmentStateAdapter#FragmentStateAdapter(FragmentActivity)
* @see FragmentStateAdapter#FragmentStateAdapter(Fragment)
*/
public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
@NonNull Lifecycle lifecycle) {
mFragmentManager = fragmentManager;
mLifecycle = lifecycle;
super.setHasStableIds(true);
}
必须要实现的两个抽象方法是:
androidx.viewpager2.adapter.FragmentStateAdapter#createFragment
/**
* Provide a new Fragment associated with the specified position.
* <p>
* The adapter will be responsible for the Fragment lifecycle:
* <ul>
* <li>The Fragment will be used to display an item.</li>