[Android Framework Series] Chapter 15 Related Principles of Fragment+ViewPager and Viewpager2

1 Introduction

In the previous chapter [Android Framework Series] Chapter 14 Core Principles of Fragment (AndroidX version), we learned the core principles of Fragment. In this chapter, we learn the commonly used Fragment+ViewPagerand Fragment+ViewPager2related uses and some basic source code analysis.

Insert image description here

2 Fragment+ViewPager

We commonly use two PagerAdapterimplementation classes, namely FragmentStatePagerAdapterand FragmentPagerAdapter. Today, we will learn how to use them and compare them.

2.1 The difference between FragmentPagerAdapter and FragmentStatePagerAdapter

1. fragmentsObject processing:
FragmentPagerAdapter: Out of scope fragmentswill be saved in memory ( detach), but fragmentthe corresponding object Viewwill be destroyed
FragmentStatePagerAdapter: Out of scope fragmentswill not be saved in memory ( remove), Viewand will also be destroyed.
2. Status processing: : The corresponding ones
FragmentPagerAdapteroutside the range will be saved : Only the corresponding ones within the range will be saved . This is used for external parameters to be passed in the life cycle callback, and is similar. 3. Applicable scenarios: The same amount , the memory is larger, but the page switching is more friendly; the memory usage is small, the page switching is slightly worse.fragmentsSavedState
FragmentStatePagerAdapterfragmentsSavedStateSavedStateFragmentActivity
fragmentsFragmentPagerAdapterFragmentStatePagerAdapter

Therefore, FragmentPagerAdapterit is suitable for Fragmentsituations where the number is small, and FragmentStatePagerAdapterit is suitable for situations where the number of Fragments is large.

Let’s take a look first FragmentPagerAdapter:

	@Override
    public Object instantiateItem(ViewGroup container, int position) {
    
    
        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        //判断请求的Fragment是否已经被生成过
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
    
    
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            // 当前缓存有则直接使用
            mCurTransaction.attach(fragment);
        } else {
    
    
            fragment = getItem(position); 
            //调用这个方法来生成新的Fragment
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            // 将新生成的Fragment存储起来,以便以后再次用到时,直接attach()
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId)); 
        }
   		if (fragment != mCurrentPrimaryItem) {
    
    
            fragment.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    
    
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            } else {
    
    
                fragment.setUserVisibleHint(false);
            }
        }

        return fragment;
    }
    
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + fragment.getView());
        mCurTransaction.detach(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
    
    
            mCurrentPrimaryItem = null;
        }
    }

FragmentPagerAdapterThe destroyItemmethod called detach()only changes the Fragment state, which means that only by eliminating the entire adaptertime can all the generated ones Fragmentbe eliminated, otherwise they will be directly in the memory.

Next we compare FragmentStatePagerAdapter:

  @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
    
    
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        // mFragments中对应位置有Fragment的情况下直接返回
        if (mFragments.size() > position) {
    
    
            Fragment f = mFragments.get(position);
            if (f != null) {
    
    
                return f;
            }
        }

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
    
    
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
    
    
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
    
    
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
    
    
            fragment.setUserVisibleHint(false);
        }

        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    
    
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        }

        return fragment;
    }
    
  	@Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        while (mSavedState.size() <= position) {
    
    
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);

		// 缓存中移除fragment,下次使用得重新创建
        mCurTransaction.remove(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
    
    
            mCurrentPrimaryItem = null;
        }
    }

FragmentStatePagerAdapterIt was removed when destroyItemthe method was called Fragment, so it needs to be re-created next time.Fragment

3 Fragment+ViewPager2

ViewPager2It is Android Jetpacka component in the library and is a container used to implement page switching and sliding effects in the application. In fact, ViewPager2itself is inherited fromRecyclerView . You can review our [Android Framework Series] Chapter 12 RecycleView related principles and four-level cache strategy analysis

3.1 The role and purpose of ViewPager2

3.1.1 ViewPager2 usage scenarios

It is a powerful sliding container that can be used in a variety of scenarios. It provides flexible page switching and layout customization functions, making the application interface richer and more interactive. It can be used in the following scenarios:

  1. Implementing onboarding or welcome pages
    ViewPager2can be used to create onboarding or welcome pages that let users swipe through to introduce app features or display welcome content.

  2. Create a picture browser
    ViewPager2can be used to create a picture browser, allowing users to switch between different pictures by sliding, and supports zoom and gesture interaction.

  3. Building carousels
    ViewPager2is very suitable for building carousel functions, which can dynamically load different carousel items through adapters and provide automatic cycle scrolling.

  4. Implementing tabbed layout
    Combination can be used to create a tabbed layout, allowing users to switch between different content pages by sliding tabs TabLayout.ViewPager2

  5. Create a vertical sliding page
    Unlike ViewPager, ViewPager2it supports vertical sliding, so it can be used to create vertical sliding page layouts, such as vertical sliding navigation menus or vertical news lists.

  6. Implementing paging data display
    ViewPager2can be used to display paging data, such as loading a large amount of data by page and displaying a portion of the content on each page.

  7. Nested sliding layout
    ViewPager2can be used nested with other sliding components (such as RecyclerView) to implement complex sliding layout structures.

  8. Achieve customized sliding effects
    By using a customized converter ( Transformer), you can achieve various cool page switching effects, such as gradient, zoom, rotation, etc.

3.1.2 Improvements and advantages of ViewPager2 compared to ViewPager

ViewPager2is ViewPageran improved version of the original, offering better performance, more flexible adapters, and richer functionality. It is the preferred component for building sliding page layouts. It can implement various sliding page requirements in applications and provide a better user experience. It has the following improvements and advantages:

  1. Support for vertical sliding
    ViewPager2is ViewPagerimproved upon, and one of the most significant improvements is 支持垂直滑动. In ViewPagerAndroid, only horizontal sliding is supported. This makes ViewPager2the vertical sliding function more convenient and flexible when creating vertical layouts or specific scenarios.

  2. Better performance and stability
    ViewPager2The internal implementation is used RecyclerViewas a container and no longer relies on ViewPagerthe implementation. This gives the advantage ViewPager2of e.g. At the same time, some known problems and instabilities have also been solved , such as incorrect entry positions and untimely refresh of data.RecyclerView更好的性能和内存管理更流畅的滑动体验及更好的布局回收和复用机制ViewPager2ViewPager

  3. Supports the use of Fragment as a page.
    Unlike Fragment ViewPager, ViewPager2it directly supports the use Fragmentof Fragment as a page without passing FragmentPagerAdapteror FragmentStatePagerAdapteradapting. This simplifies page management and lifecycle handling and provides a more intuitive and consistent usage experience.

  4. More flexible adapters
    ViewPager2introduce new adapter interfaces, RecyclerView.Adaptersubclasses of RecyclerView.Adapter. This makes adapter creation and management more flexible while providing more functionality and extensibility.

  5. Richer functions and interfaces
    ViewPager2provide many new functions and interfaces, such as support for page preloading, more powerful page switching animation support, richer callback interfaces, etc. These features and interfaces give developers greater control and customization ViewPager2of behavior and appearance.

3.2 FragmentStateAdapter

Above we have learned about some differences and optimization points of ViewPager2comparison ViewPager, let's continue to look at ViewPager2the corresponding ones Adapter.
We know that ViewPager2it inherits from RecyclerView, then its corresponding one FragmentStateAdaptermust inherit from RecyclerView.Adapter, which friends should be able to understand. Then RecyclerVieweach Itemjoin we add in Fragmentis presented as a container.
RecyclerView.AdapterThe focus is ViewHolderon the reuse, but the ones in FragmentStateAdapterare Framgentnot reused, that is, as many as there itemare should be created Fragment, so how to convert them?

Let’s take a look at the source code first FragmentStateAdapter:

public abstract class FragmentStateAdapter extends
        RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
    
    
        
    // 通过FragmentStateAdapter声明中的泛型可以知道,
    // ViewPager2之所以能够在RecyclerView的基础上能对外屏蔽对ViewHolder的使用,
    // 其内部是借助FragmentViewHolder实现的,其内部就new了一个FrameLayout。
    @Override
    public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        return FragmentViewHolder.create(parent);
    }

    @Override
    public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
    
    
        final long itemId = holder.getItemId();
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
        if (boundItemId != null && boundItemId != itemId) {
    
    
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }

        mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
        // 内部会最终回调到createFragment用来创建当前Fragment
        ensureFragment(position);

        /** Special case when {@link RecyclerView} decides to keep the {@link container}
         * attached to the window, but not to the view hierarchy (i.e. parent is null) */
        final FrameLayout container = holder.getContainer();
        if (ViewCompat.isAttachedToWindow(container)) {
    
    
            if (container.getParent() != null) {
    
    
                throw new IllegalStateException("Design assumption violated.");
            }
            container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    
    
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom,
                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
    
    
                    if (container.getParent() != null) {
    
    
                        container.removeOnLayoutChangeListener(this);
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }
		
		// 回收已经不在item集合中的Fragment,节省内存开销
        gcFragments();
    }

   	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);
        }
    }
    
    void gcFragments() {
    
    
        if (!mHasStaleFragments || shouldDelayFragmentTransactions()) {
    
    
            return;
        }

        // Remove Fragments for items that are no longer part of the data-set
        Set<Long> toRemove = new ArraySet<>();
        for (int ix = 0; ix < mFragments.size(); ix++) {
    
    
            long itemId = mFragments.keyAt(ix);
            if (!containsItem(itemId)) {
    
    
                toRemove.add(itemId);
                mItemIdToViewHolder.remove(itemId); // in case they're still bound
            }
        }

        // Remove Fragments that are not bound anywhere -- pending a grace period
        if (!mIsInGracePeriod) {
    
    
            mHasStaleFragments = false; // we've executed all GC checks

            for (int ix = 0; ix < mFragments.size(); ix++) {
    
    
                long itemId = mFragments.keyAt(ix);
                if (!isFragmentViewBound(itemId)) {
    
    
                    toRemove.add(itemId);
                }
            }
        }

        for (Long itemId : toRemove) {
    
    
            removeFragment(itemId);
        }
    }


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

 	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 != null) {
    
    
            throw new IllegalStateException("Design assumption violated.");
        }

        // { f:added, v:notCreated, v:notAttached} -> schedule callback for when created
        if (fragment.isAdded() && view == null) {
    
    
            scheduleViewAttach(fragment, container);
            return;
        }

        // { f:added, v:created, v:attached } -> check if attached to the right container
        if (fragment.isAdded() && view.getParent() != null) {
    
    
            if (view.getParent() != container) {
    
    
                addViewToContainer(view, container);
            }
            return;
        }

        // { f:added, v:created, v:notAttached} -> attach view to container
        if (fragment.isAdded()) {
    
    
            addViewToContainer(view, container);
            return;
        }

        // { f:notAdded, v:notCreated, v:notAttached } -> add, create, attach
        if (!shouldDelayFragmentTransactions()) {
    
    
            scheduleViewAttach(fragment, container);
            mFragmentManager.beginTransaction()
                    .add(fragment, "f" + holder.getItemId())
                    .setMaxLifecycle(fragment, STARTED)
                    .commitNow();
            mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);
        } else {
    
    
            if (mFragmentManager.isDestroyed()) {
    
    
                return; // nothing we can do
            }
            mLifecycle.addObserver(new LifecycleEventObserver() {
    
    
                @Override
                public void onStateChanged(@NonNull LifecycleOwner source,
                        @NonNull Lifecycle.Event event) {
    
    
                    if (shouldDelayFragmentTransactions()) {
    
    
                        return;
                    }
                    source.getLifecycle().removeObserver(this);
                    if (ViewCompat.isAttachedToWindow(holder.getContainer())) {
    
    
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }
    }
    
}
public final class FragmentViewHolder extends ViewHolder {
    
    
    private FragmentViewHolder(@NonNull FrameLayout container) {
    
    
        super(container);
    }

	// FragmentViewHolder实际上就创建了一个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);
    }

    @NonNull FrameLayout getContainer() {
    
    
        return (FrameLayout) itemView;
    }
}

From the above source code analysis, we can know that although Fragmentit is not reused, the alternate display is achieved ViewHolderthrough reuse.containerFramgent

4 Summary

To summarize the contents of this chapter:

  1. Fragment+ViewPagerIt can only scroll horizontally and its performance is relatively poor. Corresponding to different Adaptereffects are different.
  2. Fragment+ViewPagerWhen used FragmentPagerAdapter, the outside of the scope fragmentswill be saved in the memory ( detach), but fragmentthe corresponding ones Viewwill be destroyed, and fragmentsthe corresponding ones SavedStatewill be saved. FragmentPagerAdapterThe memory is larger but the page switching is more friendly, suitable for Fragmentsmall quantities.
  3. Fragment+ViewPagerWhen used FragmentStatePagerAdapter, fragmentsit will not be saved in memory ( remove) outside the scope and Viewwill also be destroyed. fragmentsOnly the corresponding ones within the range are saved SavedState. This is used for external parameters to be passed in the life cycle callback, and SavedStateis similar. The memory usage is small and the page switching is slightly poor. Suitable for large quantities.FragmentActivityFragment
  4. Fragment+ViewPager2It can scroll horizontally or vertically, and it inherits RecyclerViewbasically all its advantages, including memory usage, cache management, etc. FragmentStateAdapterInherited from RecyclerView.Adapter, although Fragmentnot reused, the alternate display is implemented ViewHolderthrough reuse.containerFramgent

Guess you like

Origin blog.csdn.net/u010687761/article/details/132674389