您当前的位置:首页 > Android教程

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

时间:2022-01-23 07:04:24 阅读数:30,024人阅读
版权声明:转载请注明出处,谢谢!
—— 我从未见过一个早起勤奋谨慎诚实的人抱怨命运不好,良好的品格,坚强的意志,是不会被所谓的命运击败的。

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 List fragments = 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 List fragments = 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学习小站"