ViewPager2

2019初Google发布了ViewPager2预览版,并在同年I/O上推出正式版。只要你已经从Suppor库切换到AndroidX,便可以使用ViewPager2完全取代旧的ViewPager。

ViewPager2最显著的特点是基于 RecyclerView 实现,RecyclerView是目前Android端最成熟的AdapterView解决方案,这带来诸多好处:

  • 抛弃传统的PagerAdapter,统一了Adapter的API
  • 通过 LinearLayoutManager 可以实现类似抖音的纵向滑动
  • 支持 DiffUitl ,可以通过diff实现局部刷新
  • 支持 RTL (right-to-left)布局,对于一些有出海需求的APP非常有用
  • 支持 ItemDecorator

ViewPager2 + Fragment

跟ViewPager一样,除了View以外,ViewPager2更多的是配合 Fragment 的使用,这需要借助 FragmentStateAdapter

View Adapter
ViewPager FragmentStatePagerAdapter、PagerAdapter
ViewPager2 FragmentStateAdapter

gradle

 implementation 'androidx.viewpager2:viewpager2:1.0.0'
<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 itemsCount: Int) :
    FragmentStateAdapter(activity) {
  override fun getItemCount(): Int {
    return itemsCount
  override fun createFragment(position: Int): Fragment {
    return DoppelgangerFragment.getInstance(position)

API使用起来跟旧的adapter很相似:

  • getItemCount:返回Item的数量
  • createFragment:用来根据position创建fragment
  • DoppelgangerFragment:是示例中创建的Fragment类型

MainActivity

在Activity中为ViewPager2设置Adapter:

val doppelgangerAdapter = DoppelgangerAdapter(this, doppelgangerNamesArray.size) 
doppelgangerViewPager.adapter = doppelgangerAdapter

doppelgangerViewPager通过kotlin-android-extension获取ViewPager2控件

揭秘FragmentStateAdapter

因为ViewPager2继承自RecyclerView,因此可以推断出FragmentStateAdapter继承自RecyclerView.Adapter

public abstract class FragmentStateAdapter extends 
  RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {

虽然是overide的关系,但两者的API却不一致,RecyclerView.Adapter关注的是ViewHolder的复用,但是在FragmentStateAdapter中的Framgent是不会复用的,即有多少个item就应该创建多少个Fragment,那么这其中是如何转换的呢?

我们来探究一下这个override是如何实现的:

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);

gcFragments,回收已经不在item集合中的Fragment,节省内存开销

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的交替显示

OnPageChangeCallback

监听页面滑动是一个常见需求,ViewPager2的使用方式也不同

ViewCallback
ViewPagerOnPageChangeListener
ViewPager2OnPageChangeCallback

使用效果如下:

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: 当前页面滑动状态变动时
    在这里插入图片描述

设置纵向滑动很简单,一行代码搞定

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();

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 = doppelgangerNamesArray[position].split(" ")[0]
}.attach()

attach方法很关键,经过前面一系列配置后最终需要通过它关联两个组件。

加入TabLayout后的最终效果如下:
在这里插入图片描述

本文主要介绍了Fragment搭配ViewPager2的使用方法以及FragmentStateAdapter的实现原理,顺便介绍了一下TabLayout、OnPageChangeCallback等常见需求。ViewPager2的使用非常简单,在性能以及使用体验等各方面都要优于传统的ViewPager,没尝试的小伙伴抓紧用起来吧~

ViewPager22019初Google发布了ViewPager2预览版,并在同年I/O上推出正式版。只要你已经从Suppor库切换到AndroidX,便可以使用ViewPager2完全取代旧的ViewPager。ViewPager2最显著的特点是基于RecyclerView实现,RecyclerView是目前Android端最成熟的AdapterView解决方案,这带来很多好处:抛弃传统的PagerAdapter,统一了Adapter的API通过LinearLayoutManager可以实. 在项目中,有时会用到在ViewPager中显示同样类型的Fragment,同时这样的Fragment的个数是动态的,但是PagerAdapter没有给我们提供getCurrentFragment类似的方法。下面就给大家介绍下AndroidViewPager获取当前显示的Fragment的方法,一起看看吧。 一、使用 getSupportFragmentManager().findFragmentByTag()方法 Viewpager + FragmentPagerAdapter 情况下 才好使; FragmentPagerAdapter 有一个特点 凡是加载过的Fragment 都会
从名字就可以看出,FragmentStatePagerAdapter就是用Fragment作为ViewPager的view来显示 所以使用方法和ViewPager差不多,但也有区别 总体的思路是,在主布局中有个帧布局FrameLayout用来Fragment替换使用,然后再适当的时机,使用FragmentStatePagerAdapter得到想要的 Fragment来替换FrameLayou
然后在创建一个fragmentstateAdapter的子类 重写里面的 createFragment()、getItemCount()、containsItem()和getItemId()四个方法 class DemoCollectionAdapter(fragment: Fragment,val fragmentlist:List<Fragment>) : FragmentStateAdapter(frag..
遇到的问题:第一次选中JobFragment的时候,界面能够正常初始化,而且获取的网络数据也能正常显示到4个fragment中,问题是,如果切换到其他item对应的fragment时候再选中 item A对应的JobFragment,也就是第二次进入JobFragment,界面可以正常显示,但是数据就是不能显示出来,调试的时候发现数据都能正常显示,但是界面就是一片 空白,而且发现第二次进入的时
FragmentPagerAdapter FragmentPagerAdapterandroid-support-v4支持包里面出现的一个新的适配器,继承自PagerAdapter,是专门用来给支持包中出现的ViewPager进行数据适配的。 FragmentPagerAdapter,见名知意,这个适配器就是用来实现FragmentViewPager里面进行滑动切...
public class MyFragmentStateAdapter extends FragmentStateAdapter { private ArrayList<Fragment> fragments; public MyFragmentStateAdapter(@NonNull FragmentActivity fragmentActivity,ArrayList<Fragment> fragments,LocalStorageViewModel view.
Android中,Fragment是一种常用的组件,可以将UI界面分割成多个可复用的部分。而ViewPager2则是Android的另一种组件,可以实现滑动切换多个页面。 然而,在使用Fragment嵌套ViewPager2时,可能会出现无法显示的情况。造成这种问题的原因有很多种,其中比较常见的原因是没有正确设置ViewPager2的适配器或者没有正确添加Fragment。 为了解决这个问题,可以采用以下方法: 1. 确保ViewPager2的适配器正常工作。在适配器中,需要实现getItemCount()、createFragment()等方法,以保证ViewPager2可以正确显示内容。 2. 确认Fragment已经正确添加到ViewPager2中。可以使用FragmentManager和FragmentTransaction等相关类进行操作,确保Fragment已经被正确添加。 3. 检查布局文件中的容器设置是否正确。ViewPager2需要添加到一个容器中才能正常工作,因此需要确认容器的设置是否正确。 4. 检查Fragment的生命周期方法是否被正确调用。在Fragment的生命周期方法中需要对ViewPager2的适配器进行操作,如调用notifyDataSetChanged()方法等。 总之,要解决Android Fragment嵌套ViewPager2不显示的问题,需要仔细检查以上几个方面,并确保对每一个细节都进行了正确的处理。只有这样,才能保证FragmentViewPager2之间的互动正常工作。