Android开发学习教程(27)- ViewPager+Fragment+FragmentPagerAdapter+FragmentStatePagerAdapter
—— 我从未见过一个早起勤奋谨慎诚实的人抱怨命运不好,良好的品格,坚强的意志,是不会被所谓的命运击败的。
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 布局中:
<?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:
import 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文件:
<?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 适配器
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 Listfragments = new ArrayList<>(); private final List 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); } }
方法说明:
getCount():此方法返回要显示的片段数。(需要覆盖) 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设置适配器:
import 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); } }
FragmentStatePagerAdapter的用法
其它步骤和上面一样,这里Fragment我们使用同一个,只是针对不同的tab设置不同的内容:
import 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
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 Listfragments = new ArrayList<>(); private final List 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); } }
MainActivity也只是实例化适配器时改变了下:
public 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方法:
@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方法:
@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方法:
@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方法:
@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适用于页面数量多的情形了。
参考:
https://www.geeksforgeeks.org/viewpager-using-fragments-in-android-with-example/
https://guides.codepath.com/android/viewpager-with-fragmentpageradapter
https://segmentfault.com/a/1190000012455727
本篇源码下载地址:https://pan.baidu.com/s/1B7PAwog-m_-FgDGs73Rbbg 提取码: helu
------转载请注明出处,感谢您对原创作者的支持------
有偿提供技术支持、Bug修复、项目外包、毕业设计、大小作业
Android学习小站
Q Q:1095817610
微信:jx-helu
邮箱:1095817610@qq.com
添加请备注"Android学习小站"
