ViewPager2+Fragment Operation note
ViewPager2 profile
ViewPager2
ViewPager2 website Samples
It has been more than a year since ViewPager2 was officially released. Currently, ViewPager has stopped updating and is officially encouraged to use ViewPager2 instead. The bottom layer of ViewPager2 is realized based on RecyclerView, so many benefits brought by RecyclerView can be obtained:
- untraditional
PagerAdapter
And unifiedAdapter
theAPI
; - Horizontal and vertical layout can achieve free sliding;
- support
DiffUitl
, can achieve local refresh; - support
RTL
(Right-to-left), which is very useful for some apps that need to go abroad; - support
ItemDecorator
, tie-inPageTransformer
Realize cool jump animation;
ViewPager2 is used more in conjunction with fragments, with the help of a FragmentStateAdapter.
They are occasionally used together with TabLayout, and the relevant code can be directly read or run Samples on ViewPager2 website, which will not be repeated here.
The following is mainly about the problems encountered in the process of use ~!
Actual operation effect
Up slide top + title page slide left and right + horizontal and vertical slide list + title page data and quantity update
Slide the top
CoordinatorLayout
+AppBarLayout
+CollapsingToolbarLayout
Sliding around
ViewPager2
+TabLayout
+Fragment
Horizontal and vertical slide lists
RecycleView
+NestedScrollableHost
Title page data and quantity
TabLayoutMediator+ Declaration cycle detection + cache optimization
RecycleView and Viewpage2 sliding conflict
/** * Created by Tanzhenxing * Date: 2021/4/7 7:04 * Description: this afternoon to solve [RecyclerView] nested [androidx. Viewpager2. Widget. Viewpager2] about sliding around * currently only has solved the conflict conflict * /
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 no slide up and down and [androidx. Viewpager2. Widget. Viewpager2] slide up and down the conflict}}else {
handleDefaultScroll()
}
return super.dispatchTouchEvent(event)
}
fun handleDefaultScroll(a) {
val canScrollHorizontally = canScrollHorizontally(-1) || canScrollHorizontally(1)
val canScrollVertically = canScrollVertically(-1) || canScrollVertically(1)
if (canScrollHorizontally || canScrollVertically) {
this.parent? .requestDisallowInterceptTouchEvent(true)}else {
this.parent? .requestDisallowInterceptTouchEvent(false)}}}Copy the code
Lazy loading of fragments in ViewPager2
Lazy loading
Generally, the onHiddenChanged method is used to determine whether to display or hide the data when the Fragment is used for lazy loading of the page. The interface is called when the Fragment is displayed for the first time.
@Override
public final void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if(! hidden) { onUserVisible(); }else{ onUserGone(); }}Copy the code
But in ViewPager2, the Fragment setUserVisibleHint and onHiddenChanged methods are not executed.
ViewPager
Display the first page, then cut the background log:
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
Copy the code
Then before cutting 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
Copy the code
ViewPager
Display the first page, then manually swipe 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
Copy the code
So it looks like we can use the onStart and onResume methods in the Fragment declaration cycle for lazy loading.
preload
Just write the data request in onCreateView or onStart and you can make an off-screen request to the interface.
FragmentStateAdapter
ViewPager2 inherited from RecyclerView, high probability FragmentStateAdapter inherited from RecyclerView.Adapter:
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
}
Copy the code
onCreateViewHolder
CreateViewHolder onCreateViewHolder onCreateViewHolder is a RecycleVeiw method to create a ViewHolder:
@NonNull
@Override
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return FragmentViewHolder.create(parent);
}
Copy the code
The FragmentViewHolder’s main role is to provide a container for the Fragment to use as a container via FrameLayout:
@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);
}
Copy the code
onBindViewHolder
OnBindViewHolder is a RecycleVeiw method for data binding:
@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
/** ** ** /
ensureFragment(position);
/** ** ** /
gcFragments();
}
Copy the code
EnsureFragment (Position), which will eventually call createFragment to 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 FragmentFragment newFragment = createFragment(position); newFragment.setInitialSavedState(mSavedStates.get(itemId)); mFragments.put(itemId, newFragment); }}Copy the code
MFragments cache fragments created for use by placeFramentInViewholder; GcFragments recover fragments that are no longer used (the corresponding item has been deleted) to save memory.
onViewAttachedToWindow
OnViewAttachedToWindow is the ViewHolder callback that appears on the page.
@Override public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) { / / will FragmentViewHolder container with current fragments binding placeFragmentInViewHolder (holder); gcFragments(); }Copy the code
FragmentStateAdapter use
Fragment
Object container;- production
fragment
Identification of theid
;
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
}
/** * Generate unique id from data ** When calling [notifyDataSetChanged], * * will throw new IllegalStateException("Fragment already Added ") */
override fun getItemId(position: Int): Long {
return data[position].toLong()
}
/** * Determines whether */ is added to the fragment corresponding to the current ID
override fun containsItem(itemId: Long): Boolean {
data.forEach {
if (it.toLong() == itemId) {
return true}}return false
}
override fun getItemCount(a): Int {
return data.size
}
}
Copy the code
In the
Obtaining the Fragment instance
fun getCurrentFragment(position: Int): Fragment? =
fragment.childFragmentManager.findFragmentByTag("f$position")
Copy the code
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) {
returnposition; }}Copy the code
Add “f” + holder.getitemID () TAG to Fragment.
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)
Copy the code
If you encounter problems during development, you need to judge the Fragment state isAdded() when constructing a FragmentStateAdapter.
- Crash encountered while updating data:
Fragment already added
Copy the code
Override the getItemId method that returns a value related to the data rather than the index of the data in the list. Because it represents the uniqueness of the Fragment, whether it can be reused.
ViewPager2 slide listen
public abstract static class OnPageChangeCallback {
// When the current page starts to slide
public void onPageScrolled(int position, float positionOffset,@Px int positionOffsetPixels) {}// When the page is selected
public void onPageSelected(int position) {}// When the current page sliding state changes
public void onPageScrollStateChanged(@ScrollState int state) {}}Copy the code
Implementation:
var pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
// Postion must be greater than 0}}Copy the code
TabLayout+TabLayoutMediator
Easy to implement TAB and ViewPager slide or jump association.
implementation 'com. Google. Android. Material: material: 1.2.0'
Copy the code
It is recommended that the material version number be around 1.0.0, otherwise implementing custom TAB layout widths presents some problems.
Samples: ViewPager2 official website
DiffUtil is locally updated
DiffUtil and its difference algorithm
conclusion
This paper mainly introduces the use method of ViewPager2 with Fragment and the problems to be paid attention to in the use process, along with TabLayout, OnPageChangeCallback, DiffUtil and so on.
The article here is all about the end, if there are other need to exchange can leave a message oh ~! ~!