ViewPager2+Fragment operation notes
Article directory
Introduction to ViewPager2
ViewPager2 official website introduction
ViewPager2 Official Website Samples
It has been more than a year since ViewPager2
the release of the official version, and ViewPager
the update has already stopped. The official encourages the use of ViewPager2 instead. ViewPager2
The bottom layer is based on RecyclerView
implementation, so you can get RecyclerView
many benefits:
- Abandon the traditional
PagerAdapter
, unified ;Adapter
API
- 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
, collocationPageTransformer
to achieve cool jump animation;
ViewPager2
It is more of a coordinated Fragment
use, which requires help FragmentStateAdapter
.
They are occasionally TabLayout
used 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
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 Fragment
the lazy loading of data on the page, we use onHiddenChanged
the 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
, Fragment
the setUserVisibleHint
and onHiddenChanged
methods are not executed.
ViewPager
Display 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
ViewPager
Show 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 Fragment
in the statement cycle to do lazy loading.onStart
onResume
Preloading
As long as the data request is written in onCreateView
or onStart
the off-screen request of the interface can be performed.
FragmentStateAdapter
ViewPager2
Inherited from RecyclerView
, most likely FragmentStateAdapter
inherited from RecyclerView.Adapter
:
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
}
onCreateViewHolder
onCreateViewHolder
is the method RecycleVeiw
used to create ViewHolder
:
@NonNull
@Override
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return FragmentViewHolder.create(parent);
}
FragmentViewHolder
The main role is used as a container FrameLayout
by providing :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
is RecycleVeiw
the 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 createFragment
create 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);
}
}
mFragments
The cache is created Fragment
for later placeFramentInViewholder
use; gcFragments
the ones that are no longer used Fragment
( the corresponding items have been deleted ) are recycled to save memory overhead.
onViewAttachedToWindow
onViewAttachedToWindow
is ViewHolder
a callback that appears on the page.
@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
//将FragmentViewHolder的container与当前Fragment绑定
placeFragmentInViewHolder(holder);
gcFragments();
}
Using FragmentStateAdapter
Fragment
object container;- production
fragment
identificationid
;
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 Fragment
add TAG
yes "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 FragmentStateAdapter
during construction .Fragment
isAdded()
- The crash encountered when updating data:
Fragment already added
Override getItemId
method that returns a value relative to the data rather than its index in the list. Because it represents Fragment
uniqueness, 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 TAB
with ViewPager
sliding or jumping.
implementation 'com.google.android.material:material:1.2.0'
The recommended material
version number is approximately 1.0.0
, otherwise TAB
the 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 ViewPager2
cooperation Fragment
and 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: