Android开发学习教程(27)- ViewPager+Fragment+FragmentPagerAdapter+FragmentStatePagerAd

302 阅读7分钟

—— 我从未见过一个早起勤奋谨慎诚实的人抱怨命运不好,良好的品格,坚强的意志,是不会被所谓的命运击败的。

ViewPager是什么

ViewPager是一个布局管理器,用户通过左右移动来滑动页面,其中每个页面使用Fragment而不是使用Activity。它还可以用在用户首次启动应用程序时的引导页。

ViewPager的用法

实现viewpager的步骤:

  1. 将 ViewPager 小部件添加到 XML 布局中。

  2. 通过扩展 FragmentPagerAdapter 或 FragmentStatePagerAdapter 类来创建适配器。

适配器用来填充 Viewpager 内的页面。PagerAdapter 是由 FragmentPagerAdapter 和 FragmentStatePagerAdapter 扩展的基类,让我们看一下这两个类之间的区别。

FragmentPagerAdapter 和 FragmentStatePagerAdapter 的区别:

(1)fragments对象的处理:FragmentPagerAdapter范围外fragments会保存在内存中(detach),但是fragment对应的View会被销毁;FragmentStatePagerAdapter范围外fragments不会保存在内存中(remove),View也会被销毁。

(2)状态的处理:FragmentPagerAdapter范围外fragments对应的SavedState会保存;FragmentStatePagerAdapter只保存范围内fragments对应的SavedState。这个SavedState在Fragment的生命周期回调中供外部传参数,和Activity类似。

(3)适用场景:相同数量的fragments,FragmentPagerAdapter内存较大,但页面切换更友好;FragmentStatePagerAdapter内存占用少,页面切换稍差。因此FragmentPagerAdapter适用于Fragment数量少的情况,FragmentStatePagerAdapter适用于Fragment数量多的情况。

FragmentPagerAdapter的用法

第 1 步:将 ViewPager 小部件添加到 XML 布局中:

1234567891011121314151617181920<?``xml version``=``"1.0" encoding``=``"utf-8"``?>``<``LinearLayout xmlns:android``=``"http://schemas.android.com/apk/res/android"``    ``xmlns:app``=``"http://schemas.android.com/apk/res-auto"``    ``android:layout_width``=``"fill_parent"``    ``android:layout_height``=``"fill_parent"``    ``android:orientation``=``"vertical"``>``    ``<``com.google.android.material.tabs.TabLayout``        ``android:id``=``"@+id/tab_layout"``        ``android:layout_width``=``"match_parent"``        ``android:layout_height``=``"wrap_content"``        ``app:tabGravity``=``"fill"``        ``app:tabMode``=``"fixed" />``    ``<``androidx.viewpager.widget.ViewPager``        ``android:id``=``"@+id/viewpager"``        ``android:layout_width``=``"match_parent"``        ``android:layout_height``=``"match_parent" />``</``LinearLayout``>

第 2 步:创建Fragment:

1234567891011121314151617181920212223242526import android.os.Bundle;``import android.view.LayoutInflater;``import android.view.View;``import android.view.ViewGroup;``import androidx.annotation.NonNull;``import androidx.annotation.Nullable;``import androidx.fragment.app.Fragment;``public class Page1Fragment ``extends Fragment {``    ``public Page1Fragment() {``        ``// required empty public constructor.``    ``}``    ``@Override``    ``public void onCreate(``@Nullable Bundle savedInstanceState) {``        ``super``.onCreate(savedInstanceState);``    ``}``    ``@Nullable``    ``@Override``    ``public View onCreateView(``@NonNull LayoutInflater inflater, ``@Nullable ViewGroup container, ``@Nullable Bundle savedInstanceState) {``        ``return inflater.inflate(R.layout.fragment_page1, container, ``false``);``    ``}``}

fragment_page1.xml文件:

1234567891011121314151617181920<?``xml version``=``"1.0" encoding``=``"utf-8"``?>``<``FrameLayout``    ``xmlns:android``=``"http://schemas.android.com/apk/res/android"``    ``xmlns:tools``=``"http://schemas.android.com/tools"``    ``android:layout_width``=``"match_parent"``    ``android:layout_height``=``"match_parent"``    ``android:background``=``"#0F9D58"``    ``tools:context``=``".Page1Fragment"``>``    ``    ``<!-- TODO: Update blank fragment layout -->``    ``<``TextView``        ``android:layout_width``=``"match_parent"``        ``android:layout_height``=``"match_parent"``        ``android:gravity``=``"center"``        ``android:text``=``"Page 1"``        ``android:textColor``=``"@color/white"``        ``android:textSize``=``"60sp"``        ``android:textStyle``=``"bold" />``</``FrameLayout``>

和上面一样再分别新建Page2Fragment、fragment_page2.xml、Page3Fragment、fragment_page3.xml

第 3 步:创建 ViewPager 适配器

123456789101112131415161718192021222324252627282930313233343536373839404142  ``import androidx.annotation.NonNull;``  ``import androidx.annotation.Nullable;``  ``import androidx.fragment.app.Fragment;``  ``import androidx.fragment.app.FragmentManager;``  ``import androidx.fragment.app.FragmentPagerAdapter;``  ``import java.util.ArrayList;``  ``import java.util.List;``  ``public class ViewPagerAdapter ``extends FragmentPagerAdapter {``      ``private final List<fragment> fragments = ``new ArrayList<>();``      ``private final List<string> fragmentTitle = ``new ArrayList<>();``      ``public ViewPagerAdapter(``@NonNull FragmentManager fm)``      ``{``          ``super``(fm);``      ``}``      ``public void add(Fragment fragment, String title)``      ``{``          ``fragments.add(fragment);``          ``fragmentTitle.add(title);``      ``}``      ``@NonNull @Override public Fragment getItem(``int position)``      ``{``          ``return fragments.get(position);``      ``}``      ``@Override public int getCount()``      ``{``          ``return fragments.size();``      ``}``      ``@Nullable``      ``@Override``      ``public CharSequence getPageTitle(``int position)``      ``{``          ``return fragmentTitle.get(position);``      ``}``  ``}``</string></fragment>

方法说明:

123456getCount():此方法返回要显示的片段数。(需要覆盖)``getItem(``int pos):返回 pos 索引处的片段。(需要覆盖)``ViewPagerAdapter(``@NonNull FragmentManager FM):(必需)ViewPager Adapter 需要有一个接受 FragmentManager 实例的参数化构造函数。它负责管理片段。FragmentManager 管理 Android 中的 Fragment,具体来说,它处理 Fragment 之间的事务。事务是一种添加、替换或删除片段的方法。``getPageTitle(``int pos):(可选)与 getItem() 类似,此方法返回索引 pos 处的页面标题。``add(Fragment fragment, String title):此方法负责填充片段和片段标题列表。分别持有片段和标题。

第 4 步:ViewPager设置适配器:

123456789101112131415161718192021222324252627282930313233import android.os.Bundle;``import androidx.appcompat.app.AppCompatActivity;``import androidx.viewpager.widget.ViewPager;``import com.google.android.material.tabs.TabLayout;``public class MainActivity ``extends AppCompatActivity {``    ``    ``private ViewPagerAdapter viewPagerAdapter;``    ``private ViewPager viewPager;``    ``private TabLayout tabLayout;``    ``    ``@Override``    ``protected void onCreate(Bundle savedInstanceState) {``        ``super``.onCreate(savedInstanceState);``        ``setContentView(R.layout.activity_main);``        ``viewPager = findViewById(R.id.viewpager);``        ``// 实例化适配器``        ``viewPagerAdapter = ``new ViewPagerAdapter(getSupportFragmentManager());``        ``viewPagerAdapter.add(``new Page1Fragment(), ``"Page 1"``);``        ``viewPagerAdapter.add(``new Page2Fragment(), ``"Page 2"``);``        ``viewPagerAdapter.add(``new Page3Fragment(), ``"Page 3"``);``        ``// viewPager设置适配器``        ``viewPager.setAdapter(viewPagerAdapter);``        ``tabLayout = findViewById(R.id.tab_layout);``        ``// 使用setupWithiewPager方法将 TabLayout 链接到 Viewpager``        ``tabLayout.setupWithViewPager(viewPager);``    ``}``}

Android开发学习教程(27)- ViewPager+Fragment+FragmentPagerAdapter+FragmentStatePagerAdapter

Android开发学习教程(27)- ViewPager+Fragment+FragmentPagerAdapter+FragmentStatePagerAdapter

FragmentStatePagerAdapter的用法

其它步骤和上面一样,这里Fragment我们使用同一个,只是针对不同的tab设置不同的内容:

1234567891011121314151617181920212223242526272829303132import android.os.Bundle;``import android.view.LayoutInflater;``import android.view.View;``import android.view.ViewGroup;``import android.widget.TextView;``import androidx.annotation.NonNull;``import androidx.annotation.Nullable;``import androidx.fragment.app.Fragment;``public class DynamicFragment ``extends Fragment {``    ``public static DynamicFragment newInstance(String arg) {``        ``Bundle bundle = ``new Bundle();``        ``bundle.putString(``"arg"``, arg);``        ``DynamicFragment fragment = ``new DynamicFragment();``        ``fragment.setArguments(bundle);``        ``return fragment;``    ``}``    ``@Nullable``    ``@Override``    ``public View onCreateView(``@NonNull LayoutInflater inflater, ``@Nullable ViewGroup container, ``@Nullable Bundle savedInstanceState) {``        ``return inflater.inflate(R.layout.fragment_page, container, ``false``);``    ``}``    ``@Override``    ``public void onViewCreated(``@NonNull View view, ``@Nullable Bundle savedInstanceState) {``        ``super``.onViewCreated(view, savedInstanceState);``        ``((TextView) getView().findViewById(R.id.tv)).setText(``"Page" + getArguments().getString(``"arg"``));``    ``}``}

适配器部分和上面的ViewPagerAdapter内容一样,区别只是继承的是 FragmentStatePagerAdapter

1234567891011121314151617181920212223242526272829303132333435363738394041  ``import java.util.ArrayList;``  ``import java.util.List;``  ``import androidx.annotation.Nullable;``  ``import androidx.fragment.app.Fragment;``  ``import androidx.fragment.app.FragmentManager;``  ``import androidx.fragment.app.FragmentStatePagerAdapter;``  ``public class DynamicFragmentAdapter ``extends FragmentStatePagerAdapter {``      ``private final List<fragment> fragments = ``new ArrayList<>();``      ``private final List<string> fragmentTitle = ``new ArrayList<>();``      ``public DynamicFragmentAdapter(FragmentManager fm) {``          ``super``(fm);``      ``}``      ``public void add(Fragment fragment, String title) {``          ``fragments.add(fragment);``          ``fragmentTitle.add(title);``      ``}``      ``// get the current item with position number``      ``@Override``      ``public Fragment getItem(``int position) {``          ``return fragments.get(position);``      ``}``      ``// get total number of tabs``      ``@Override``      ``public int getCount() {``          ``return fragmentTitle.size();``      ``}``      ``@Nullable``      ``@Override``      ``public CharSequence getPageTitle(``int position) {``          ``return fragmentTitle.get(position);``      ``}``  ``}``</string></fragment>

MainActivity也只是实例化适配器时改变了下:

1234567891011121314151617181920212223242526272829public class MainActivity2 ``extends AppCompatActivity {``    ``private DynamicFragmentAdapter dynamicFragmentAdapter;``    ``private ViewPager viewPager;``    ``private TabLayout tabLayout;``    ``@Override``    ``protected void onCreate(Bundle savedInstanceState) {``        ``super``.onCreate(savedInstanceState);``        ``setContentView(R.layout.activity_main1);``        ``viewPager = findViewById(R.id.viewpager);``        ``// 实例化适配器``        ``dynamicFragmentAdapter = ``new DynamicFragmentAdapter(getSupportFragmentManager());``        ``dynamicFragmentAdapter.add(DynamicFragment.newInstance(``"1"``), ``"PAGE 1"``);``        ``dynamicFragmentAdapter.add(DynamicFragment.newInstance(``"2"``), ``"PAGE 2"``);``        ``dynamicFragmentAdapter.add(DynamicFragment.newInstance(``"3"``), ``"PAGE 3"``);``        ``// viewPager设置适配器``        ``viewPager.setAdapter(dynamicFragmentAdapter);``        ``tabLayout = findViewById(R.id.tab_layout);``        ``// 使用setupWithiewPager方法将 TabLayout 链接到 Viewpager``        ``tabLayout.setupWithViewPager(viewPager);``    ``}``}

FragmentPagerAdapter和FragmentStatePagerAdapter不同的根本原因

任何不同的根本原因都可以从源码中找到,我们来看下从源码的角度两者有何不同

Ctrl+鼠标点击跳转到源码内部,我们发现,两者都是继承自PagerAdapter,整体代码非常简短,只有200行,里面有两个方法instantiateItem和destroyItem

FragmentPagerAdapter instantiateItem方法:

1234567891011121314151617181920212223242526272829@NonNull``@Override``public Object instantiateItem(``@NonNull ViewGroup container, ``int position) {``    ``if (mCurTransaction == ``null``) {``        ``mCurTransaction = mFragmentManager.beginTransaction();``    ``}``    ``final long itemId = getItemId(position);``    ``// Do we already have this 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);``        ``if (DEBUG) Log.v(TAG, ``"Adding item #" + itemId + ``": f=" + fragment);``        ``mCurTransaction.add(container.getId(), fragment,``                ``makeFragmentName(container.getId(), itemId));``    ``}``    ``if (fragment != mCurrentPrimaryItem) {``        ``fragment.setMenuVisibility(``false``);``        ``fragment.setUserVisibleHint(``false``);``    ``}``    ``return fragment;``}

此方法用来为每一个页面创建页面(这里也就是Fragment了),源码其实很简单,先获取FragmentManager事务对象,然后用findFragmentByTag查找Fragment,找到了就attach上去,没找到就调用getItem方法获取定义的Fragment然后add上去。

FragmentStatePagerAdapter instantiateItem方法:

12345678910111213141516171819202122232425262728293031323334353637@NonNull``@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.``    ``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``);``    ``fragment.setUserVisibleHint(``false``);``    ``mFragments.set(position, fragment);``    ``mCurTransaction.add(container.getId(), fragment);``    ``return fragment;``}

此方法一样用来为每一个页面创建页面,源码同样也很简单,先在mFragments集合中查找有无对应页面的Fragmet,刚开始mFragments肯定是0,继续就往下执行,获取FragmentManager事务对象,然后用调用getItem方法获取定义的Fragment然后add上去。

到这里,首先就有个很明显的区别,上面需要先经过findFragmentByTag而这里是直接从mFragments查找有无对应页面的Fragmet,效率肯定快多了吧,至于为什么这里可以直接从mFragments查找,继续往下看

FragmentPagerAdapter destroyItem方法:

12345678910@Override``public void destroyItem(``@NonNull ViewGroup container, ``int position, ``@NonNull Object object) {``    ``if (mCurTransaction == ``null``) {``        ``mCurTransaction = mFragmentManager.beginTransaction();``    ``}``    ``if (DEBUG) Log.v(TAG, ``"Detaching item #" + getItemId(position) + ``": f=" + object``            ``+ ``" v=" + ((Fragment)object).getView());``    ``mCurTransaction.detach((Fragment)object);``}

很简单,就是detach掉某个Fragment页面

FragmentStatePagerAdapter destroyItem方法:

12345678910111213141516171819@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``);``    ``mCurTransaction.remove(fragment);``}

这里是将mFragments对应位置设为NULL,而且从FragmentManager中remove掉Fragment。这里也解释了上面为什么可以直接从mFragments查找而不必再使用findFragmentByTag去查找Fragment了,因为这里将mFragments对应位置设为NULL了。另一方面,这里直接从FragmentManager中remove掉Fragment了,所以FragmentManager在当前内存中不会缓存3个以上的Fragment(假设读者使用默认的mOffscreenPageLimit),这也是为什么FragmentStatePagerAdapter适用于页面数量多的情形了。

**