1. 开启ViewPager2之旅
距离ViewPager2正式版的发布已经一年多了,目前ViewPager早已停止更新,官方鼓励使用ViewPager2替代。 ViewPager2底层基于RecyclerView
实现,因此可以获得RecyclerView带来的诸多收益:
- 抛弃传统的
PagerAdapter
,统一了Adapter的API - 通过
LinearLayoutManager
可以实现类似抖音的纵向滑动 - 支持
DiffUitl
,可以实现局部刷新 - 支持
RTL
(right-to-left),对于一些有出海需求的APP非常有用 - 支持
ItemDecorator
2. ViewPager2 + Fragment
跟ViewPager一样,除了View以外,ViewPager2更多的是配合Fragment
使用,这需要借助于FragmentStateAdapter
。
接下来,本文简单介绍一下FragmentStateAdapter的使用及实现原理:
首先在gradle中引入ViewPager2:
implementation 'androidx.viewpager2:viewpager2:1.1.0'
然后在xml中布局:
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/doppelgangerViewPager" android:layout_width="match_parent" android:layout_height="match_parent" />
FragmentStateAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
class DoppelgangerAdapter(activity: AppCompatActivity, val doppelgangerList: List<DoppelgangerItem>) :
FragmentStateAdapter(activity) {
override fun getItemCount(): Int {
return doppelgangerList.size
}
override fun createFragment(position: Int): Fragment {
return DoppelgangerFragment.getInstance(doppelgangerList[position])
}
}
FragmentStateAdapter的API跟旧的Adapter很相似:
getItemCount
:返回Item的数量createFragment
:用来根据position创建fragmentDoppelgangerFragment
:创建的具体Fragment类型
MainActivity
在Activity中为ViewPager2设置Adapter:
val doppelgangerAdapter = DoppelgangerAdapter(this, doppelgangerList)
doppelgangerViewPager.adapter = doppelgangerAdapter
3. 揭秘FragmentStateAdapter的实现
因为ViewPager2继承自RecyclerView,因此可以推断出FragmentStateAdapter
继承自RecyclerView.Adapter
:
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
}
虽说是继承关系,但两者的API却不一致,RecyclerView.Adapter关注的是ViewHolder
的复用,而在FragmentStateAdapter
中Framgent是不会复用的,即有多少个item就应该创建多少个Fragment,那么这其中是如何转换的呢?
onCreateViewHolder
通过FragmentStateAdapter声明中的泛型可以知道,ViewPager2之所以能够在RecyclerView的基础上对外屏蔽对ViewHolder的使用,其内部是借助FragmentViewHolder
实现的。
onCreateViewHolder
中会创建一个FragmentViewHolder
:
@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
@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已经删除),节省内存开销。
placeFragmentInViewHolder
@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
placeFragmentInViewHolder(holder);
gcFragments();
}
onViewAttachToWindow
的时候调用placeFragmentInViewHolder
,将FragmentViewHolder的container与当前Fragment绑定
void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {
Fragment fragment = mFragments.get(holder.getItemId());
if (fragment == null) {
throw new IllegalStateException("Design assumption violated.");
}
FrameLayout container = holder.getContainer();
View view = fragment.getView();
...
if (fragment.isAdded() && view.getParent() != null) {
if (view.getParent() != container) {
addViewToContainer(view, container);
}
return;
}
...
}
void addViewToContainer(@NonNull View v, @NonNull FrameLayout container) {
...
if (container.getChildCount() > 0) {
container.removeAllViews();
}
if (v.getParent() != null) {
((ViewGroup) v.getParent()).removeView(v);
}
container.addView(v);
}
通过上面源码分析可以知道,虽然Fragment没有被复用,但是通过复用了ViewHolder的container实现了Framgent的交替显示
4. 滑动监听
监听页面滑动是一个常见需求,ViewPager2的API也发生了变化,使用OnPageChangeCallback
:
使用效果如下:
var doppelgangerPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
Toast.makeText(this@MainActivity, "Selected position: ${position}",
Toast.LENGTH_SHORT).show()
}
}
OnPageChangeCallback同样也有三个方法:
onPageScrolled
: 当前页面开始滑动时onPageSelected
: 当页面被选中时onPageScrollStateChanged
: 当前页面滑动状态变动时
5. 纵向滑动
设置纵向滑动很简单,一行代码搞定
doppelgangerViewPager.orientation = ViewPager2.ORIENTATION_VERTICAL
源码也很简单
/** * Sets the orientation of the ViewPager2. * * @param orientation {@link #ORIENTATION_HORIZONTAL} or {@link #ORIENTATION_VERTICAL} */
public void setOrientation(@Orientation int orientation) {
mLayoutManager.setOrientation(orientation);
mAccessibilityProvider.onSetOrientation();
}
6. TabLayout
配合TabLayout
的使用也是一个常见需求,TabLayout需要引入material
库
implementation 'com.google.android.material:material:1.2.0-alpha04'
然后在xml中声明
<com.google.android.material.tabs.TabLayout android:id="@+id/tabLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorPrimary" app:tabMode="scrollable" app:tabTextColor="@android:color/white" />
TabsLayoutMediator
要关联TabLayout和ViewPager2需要借助TabLayoutMediator
:
public TabLayoutMediator( @NonNull TabLayout tabLayout, @NonNull ViewPager2 viewPager, @NonNull TabConfigurationStrategy tabConfigurationStrategy) {
this(tabLayout, viewPager, true, tabConfigurationStrategy);
}
其中,TabConfigurationStrategy
定义如下:根据position
配置当前tab
/** * A callback interface that must be implemented to set the text and styling of newly created * tabs. */
public interface TabConfigurationStrategy {
/** * Called to configure the tab for the page at the specified position. Typically calls {@link * TabLayout.Tab#setText(CharSequence)}, but any form of styling can be applied. * * @param tab The Tab which should be configured to represent the title of the item at the given * position in the data set. * @param position The position of the item within the adapter's data set. */
void onConfigureTab(@NonNull TabLayout.Tab tab, int position);
}
在MainActivity中具体使用如下:
TabLayoutMediator(tabLayout, doppelgangerViewPager) { tab, position ->
//To get the first name of doppelganger celebrities
tab.text = doppelgangerList[position].title
}.attach()
attach
方法很关键,经过前面一系列配置后最终需要通过它关联两个组件。
加入TabLayout后的最终效果如下:
7. DiffUtil 局部更新
RecyclerView基于DiffUtil可以实现局部更新,如今,FragmentStateAdapter也可以对Fragment实现局部更新。
首先定义DiffUtil.Callback
class PagerDiffUtil(private val oldList: List<DoppelgangerItem>, private val newList: List<DoppelgangerItem>) : DiffUtil.Callback() {
enum class PayloadKey {
VALUE
}
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].id == newList[newItemPosition].id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].value == newList[newItemPosition].value
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
return listOf(PayloadKey.VALUE)
}
}
然后在Adapter中使用DiffUtil更新数据
class DoppelgangerAdapter(private val activity: FragmentActivity) : FragmentStateAdapter(activity) {
private val items: ArrayList<DoppelgangerItem> = arrayListOf()
override fun createFragment(position: Int): Fragment {
return DoppelgangerFragment.getInstance(doppelgangerList[position])
}
override fun getItemCount() = items.size
override fun getItemId(position: Int): Long {
return items[position].id.toLong()
}
override fun containsItem(itemId: Long): Boolean {
return items.any { it.id.toLong() == itemId }
}
fun setItems(newItems: List<PagerItem>) {
//不借助DiffUtil更新数据
//items.clear()
//items.addAll(newItems)
//notifyDataSetChanged()
//使用DiffUtil更新数据
val callback = PagerDiffUtil(items, newItems)
val diff = DiffUtil.calculateDiff(callback)
items.clear()
items.addAll(newItems)
diff.dispatchUpdatesTo(this)
}
}
8. 总结
本文主要介绍了ViewPager2配合Fragment的使用方法以及FragmentStateAdapter的实现原理,顺带介绍了TabLayout、OnPageChangeCallback、DiffUtil等常见功能的用法。ViewPager2的使用非常简单,在性能以及使用体验等各方面都要优于传统的ViewPager,没尝试的小伙伴抓紧用起来吧~
今天的文章为Fragment换装ViewPager2分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/23602.html