Android开发学习教程(27)- ViewPager+Fragment+FragmentPagerAdapter+Fragm
—— 我从未见过一个早起勤奋谨慎诚实的人抱怨命运不好,良好的品格,坚强的意志,是不会被所谓的命运击败的。
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<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>
方法说明:
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<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也只是实例化适配器时改变了下:
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://yunjunet.cn/876823.html