ViewPager2+Fragment operation notes

ViewPager2+Fragment operation notes

A good mood

Introduction to ViewPager2

ViewPager2 official website introduction

ViewPager2 Official Website Samples

It has been more than a year since ViewPager2the release of the official version, and ViewPagerthe update has already stopped. The official encourages the use of ViewPager2 instead. ViewPager2The bottom layer is based on RecyclerViewimplementation, so you can get RecyclerViewmany benefits:

  • Abandon the traditional PagerAdapter, unified ;AdapterAPI
  • Both horizontal and vertical layouts can slide freely;
  • Support DiffUitl, partial refresh can be realized;
  • Support RTL(right-to-left), very useful for some apps that need to go overseas;
  • Support ItemDecorator, collocation PageTransformerto achieve cool jump animation;

ViewPager2It is more of a coordinated Fragmentuse, which requires help FragmentStateAdapter.

They are occasionally TabLayoutused together. You can read the relevant code directly or run the Samples on the official website of ViewPager2 . I won’t repeat the explanation here.

The following mainly talks about the problems encountered in the use process~!

Actual operation effect

Swipe up the ceiling + slide left and right on the title page + slide horizontally and vertically to list + update data and quantity on the title page

Swipe up ceiling
CoordinatorLayout+ AppBarLayout+ CollapsingToolbarLayout
slide left and right
ViewPager2+ TabLayout+ Fragment
horizontal and vertical slide list
RecycleView+ NestedScrollableHost
title page data and quantity
TabLayoutMediator+ statement cycle detection + cache optimization

viewpage2_tabLayout_nestedscroll.gif

Sliding conflict between RecycleView and Viewpage2

/**
 * Created by Tanzhenxing
 * Date: 2021/4/7 7:04 下午
 * Description:解决 [RecyclerView] 嵌套到 [androidx.viewpager2.widget.ViewPager2] 左右滑动冲突
 * 目前只解决了左右滑动冲突
 */
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)
        }
    }
}

Lazy loading of Fragment in ViewPager2

lazy loading

Generally, when we use Fragmentthe lazy loading of data on the page, we use onHiddenChangedthe method to judge the display and hide, and then call the interface when it is displayed for the first time .

 @Override
public final void onHiddenChanged(boolean hidden) {
    
    
  super.onHiddenChanged(hidden);
  if (!hidden) {
    
    
    onUserVisible();
  } else {
    
    
    onUserGone();
  }
}

But in ViewPager2, Fragmentthe setUserVisibleHintand onHiddenChangedmethods are not executed.

  • ViewPagerDisplay the first page, and then cut the log in the background:
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

Then switch back to the foreground log:

04-17 16:53:40.749 D/tanzhenxing:11(22006): onStart
04-17 16:53:40.752 D/tanzhenxing:11(22006): onResume
  • ViewPagerShow the first page, and then manually slide to the second page log:
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

It seems that we can use the and methods Fragmentin the statement cycle to do lazy loading.onStartonResume

Preloading

As long as the data request is written in onCreateViewor onStartthe off-screen request of the interface can be performed.

FragmentStateAdapter

ViewPager2Inherited from RecyclerView, most likely FragmentStateAdapterinherited from RecyclerView.Adapter:

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

onCreateViewHolder

onCreateViewHolderis the method RecycleVeiwused to create ViewHolder:

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

FragmentViewHolderThe main role is used as a container FrameLayoutby providing :Fragmentcontainer

@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

onBindViewHolderis RecycleVeiwthe method used for data binding:

@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
    
    
    /**部分代码省略 */
	  ensureFragment(position);
    /**部分代码省略 */
  	gcFragments();
}

ensureFragment(position), which internally will eventually call back to createFragmentcreate the current 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);
  }
}

mFragmentsThe cache is created Fragmentfor later placeFramentInViewholderuse; gcFragmentsthe ones that are no longer used Fragment( the corresponding items have been deleted ) are recycled to save memory overhead.

onViewAttachedToWindow

onViewAttachedToWindowis ViewHoldera callback that appears on the page.

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

Using FragmentStateAdapter

  • Fragmentobject container;
  • production fragmentidentification id;
class MyFragmentStateAdapter(val data: List<Int>, fragment: Fragment) : FragmentStateAdapter(fragment){
    
    
    private val fragments = mutableMapOf<Int, Fragment>()
    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
    }
}

in progress

Get Fragment instance

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

Source code analysis:

public abstract class FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder>
    implements StatefulAdapter {
    
    
    /**部分代码省略 */
    void placeFragmentInViewHolder(@NonNull
    final FragmentViewHolder holder) {
    
    
        /**部分代码省略 */
        if (!shouldDelayFragmentTransactions()) {
    
    
            scheduleViewAttach(fragment, container);
            mFragmentManager.beginTransaction()
                            .add(fragment, "f" + holder.getItemId())
                            .setMaxLifecycle(fragment, STARTED).commitNow();
            mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);
        }

        /**部分代码省略 */
    }

    /**部分代码省略 */
    @Override
    public long getItemId(int position) {
    
    
        return position;
    }
}

To Fragmentadd TAGyes "f" + holder.getItemId().

exception handling

  • A crash encountered during initialization;
Fragment HomeFragment{
    
    b793d14 (e67290fe-7ab1-4b2b-b98c-4e08d146644c)} has not been attached yet.
com.xxx.xxx.xxx.adapter.HomeFragmentStateAdapter.<init>(SourceFile:29)

If you encounter problems during the development process, you need to make judgments on the state of the project FragmentStateAdapterduring construction .FragmentisAdded()

  • The crash encountered when updating data:
Fragment already added

Override getItemIdmethod that returns a value relative to the data rather than its index in the list. Because it represents Fragmentuniqueness, whether it can be reused.

ViewPager2 sliding monitoring

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) {
    
    
    }
}

accomplish:

var pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
    
    
  override fun onPageSelected(position: Int) {
    
    
    //需要注意的是postion需要做大于0的判断
  }
}

TabLayout+TabLayoutMediator

It is convenient to realize the association TABwith ViewPagersliding or jumping.

implementation 'com.google.android.material:material:1.2.0'

The recommended materialversion number is approximately 1.0.0, otherwise TABthe custom layout width implemented will show some problems.

Use: ViewPager2 Official Website Samples

DiffUtil partial update

DiffUtil and its difference algorithm

Summarize

This article mainly introduces the usage method of ViewPager2cooperation Fragmentand the problems that need to be paid attention to in the process of use, and mentions TabLayout , OnPageChangeCallback , DiffUtil, etc. by the way.

The article is all told here, if you have other needs to communicate, you can leave a message~! ~!

If you want to read more articles by the author, you can check out my personal blog and public account:
Revitalize Book City

Guess you like

Origin blog.csdn.net/stven_king/article/details/116781155