Android開(kāi)發(fā)學(xué)習(xí)教程(27)- ViewPager+Fragment+FragmentPagerAdapter+Fragm
—— 我從未見(jiàn)過(guò)一個(gè)早起勤奮謹(jǐn)慎誠(chéng)實(shí)的人抱怨命運(yùn)不好,良好的品格,堅(jiān)強(qiáng)的意志,是不會(huì)被所謂的命運(yùn)擊敗的。
ViewPager是什么
ViewPager是一個(gè)布局管理器,用戶通過(guò)左右移動(dòng)來(lái)滑動(dòng)頁(yè)面,其中每個(gè)頁(yè)面使用Fragment而不是使用Activity。它還可以用在用戶首次啟動(dòng)應(yīng)用程序時(shí)的引導(dǎo)頁(yè)。
ViewPager的用法
實(shí)現(xiàn)viewpager的步驟:
1. 將 ViewPager 小部件添加到 XML 布局中。
2. 通過(guò)擴(kuò)展 FragmentPagerAdapter 或 FragmentStatePagerAdapter 類(lèi)來(lái)創(chuàng)建適配器。
適配器用來(lái)填充 Viewpager 內(nèi)的頁(yè)面。PagerAdapter 是由 FragmentPagerAdapter 和 FragmentStatePagerAdapter 擴(kuò)展的基類(lèi),讓我們看一下這兩個(gè)類(lèi)之間的區(qū)別。
FragmentPagerAdapter 和 FragmentStatePagerAdapter 的區(qū)別:
(1)fragments對(duì)象的處理:FragmentPagerAdapter范圍外fragments會(huì)保存在內(nèi)存中(detach),但是fragment對(duì)應(yīng)的View會(huì)被銷(xiāo)毀;FragmentStatePagerAdapter范圍外fragments不會(huì)保存在內(nèi)存中(remove),View也會(huì)被銷(xiāo)毀。
(2)狀態(tài)的處理:FragmentPagerAdapter范圍外fragments對(duì)應(yīng)的SavedState會(huì)保存;FragmentStatePagerAdapter只保存范圍內(nèi)fragments對(duì)應(yīng)的SavedState。這個(gè)SavedState在Fragment的生命周期回調(diào)中供外部傳參數(shù),和Activity類(lèi)似。
(3)適用場(chǎng)景:相同數(shù)量的fragments,F(xiàn)ragmentPagerAdapter內(nèi)存較大,但頁(yè)面切換更友好;FragmentStatePagerAdapter內(nèi)存占用少,頁(yè)面切換稍差。因此FragmentPagerAdapter適用于Fragment數(shù)量少的情況,F(xiàn)ragmentStatePagerAdapter適用于Fragment數(shù)量多的情況。
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 步:創(chuàng)建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 步:創(chuàng)建 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>
方法說(shuō)明:
getCount():此方法返回要顯示的片段數(shù)。(需要覆蓋)
getItem(
int
?pos):返回 pos 索引處的片段。(需要覆蓋)
ViewPagerAdapter(
@NonNull
?FragmentManager FM):(必需)ViewPager Adapter 需要有一個(gè)接受 FragmentManager 實(shí)例的參數(shù)化構(gòu)造函數(shù)。它負(fù)責(zé)管理片段。FragmentManager 管理 Android 中的 Fragment,具體來(lái)說(shuō),它處理 Fragment 之間的事務(wù)。事務(wù)是一種添加、替換或刪除片段的方法。
getPageTitle(
int
?pos):(可選)與 getItem() 類(lèi)似,此方法返回索引 pos 處的頁(yè)面標(biāo)題。
add(Fragment fragment, String title):此方法負(fù)責(zé)填充片段和片段標(biāo)題列表。分別持有片段和標(biāo)題。
第 4 步:ViewPager設(shè)置適配器:
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);
????????
// 實(shí)例化適配器
????????
viewPagerAdapter =?
new
?ViewPagerAdapter(getSupportFragmentManager());
????????
viewPagerAdapter.add(
new
?Page1Fragment(),?
"Page 1"
);
????????
viewPagerAdapter.add(
new
?Page2Fragment(),?
"Page 2"
);
????????
viewPagerAdapter.add(
new
?Page3Fragment(),?
"Page 3"
);
????????
// viewPager設(shè)置適配器
????????
viewPager.setAdapter(viewPagerAdapter);
????????
tabLayout = findViewById(R.id.tab_layout);
????????
// 使用setupWithiewPager方法將 TabLayout 鏈接到 Viewpager
????????
tabLayout.setupWithViewPager(viewPager);
????
}
}
FragmentStatePagerAdapter的用法
其它步驟和上面一樣,這里Fragment我們使用同一個(gè),只是針對(duì)不同的tab設(shè)置不同的內(nèi)容:
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內(nèi)容一樣,區(qū)別只是繼承的是 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也只是實(shí)例化適配器時(shí)改變了下:
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);
????????
// 實(shí)例化適配器
????????
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設(shè)置適配器
????????
viewPager.setAdapter(dynamicFragmentAdapter);
????????
tabLayout = findViewById(R.id.tab_layout);
????????
// 使用setupWithiewPager方法將 TabLayout 鏈接到 Viewpager
????????
tabLayout.setupWithViewPager(viewPager);
????
}
}
FragmentPagerAdapter和FragmentStatePagerAdapter不同的根本原因
任何不同的根本原因都可以從源碼中找到,我們來(lái)看下從源碼的角度兩者有何不同
Ctrl+鼠標(biāo)點(diǎn)擊跳轉(zhuǎn)到源碼內(nèi)部,我們發(fā)現(xiàn),兩者都是繼承自PagerAdapter,整體代碼非常簡(jiǎn)短,只有200行,里面有兩個(gè)方法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;
}
此方法用來(lái)為每一個(gè)頁(yè)面創(chuàng)建頁(yè)面(這里也就是Fragment了),源碼其實(shí)很簡(jiǎn)單,先獲取FragmentManager事務(wù)對(duì)象,然后用findFragmentByTag查找Fragment,找到了就attach上去,沒(méi)找到就調(diào)用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;
}
此方法一樣用來(lái)為每一個(gè)頁(yè)面創(chuàng)建頁(yè)面,源碼同樣也很簡(jiǎn)單,先在mFragments集合中查找有無(wú)對(duì)應(yīng)頁(yè)面的Fragmet,剛開(kāi)始mFragments肯定是0,繼續(xù)就往下執(zhí)行,獲取FragmentManager事務(wù)對(duì)象,然后用調(diào)用getItem方法獲取定義的Fragment然后add上去。
到這里,首先就有個(gè)很明顯的區(qū)別,上面需要先經(jīng)過(guò)findFragmentByTag而這里是直接從mFragments查找有無(wú)對(duì)應(yīng)頁(yè)面的Fragmet,效率肯定快多了吧,至于為什么這里可以直接從mFragments查找,繼續(xù)往下看
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);
}
很簡(jiǎn)單,就是detach掉某個(gè)Fragment頁(yè)面
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對(duì)應(yīng)位置設(shè)為NULL,而且從FragmentManager中remove掉Fragment。這里也解釋了上面為什么可以直接從mFragments查找而不必再使用findFragmentByTag去查找Fragment了,因?yàn)檫@里將mFragments對(duì)應(yīng)位置設(shè)為NULL了。另一方面,這里直接從FragmentManager中remove掉Fragment了,所以FragmentManager在當(dāng)前內(nèi)存中不會(huì)緩存3個(gè)以上的Fragment(假設(shè)讀者使用默認(rèn)的mOffscreenPageLimit),這也是為什么FragmentStatePagerAdapter適用于頁(yè)面數(shù)量多的情形了。
源碼鏈接:https://yunjunet.cn/876823.html