引言
項(xiàng)目實(shí)施過(guò)程中,最常見(jiàn)的莫過(guò)于ViewPager(多頁(yè)面滑動(dòng)控件)。
基本概念敘述
FragmentPagerAdapter
對(duì)于Fragment,它所使用的適配器是:FragmentPagerAdapter。先看看官方對(duì)于這個(gè)類的解釋:
原文:
Class Overview
Implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.
This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter.
When using FragmentPagerAdapter the host ViewPager must have a valid ID set.
Subclasses only need to implement getItem(int) and getCount() to have a working adapter.
譯文:
FragmentPagerAdapter派生自PagerAdapter,它是用來(lái)呈現(xiàn)Fragment頁(yè)面的,這些Fragment頁(yè)面會(huì)一直保存在fragment manager中,以便用戶可以隨時(shí)取用。
這個(gè)適配器最好用于有限個(gè)靜態(tài)fragment頁(yè)面的管理。盡管不可見(jiàn)的視圖有時(shí)會(huì)被銷毀,但用戶所有訪問(wèn)過(guò)的fragment都會(huì)被保存在內(nèi)存中。因此fragment實(shí)例會(huì)保存大量的各種狀態(tài),這就造成了很大的內(nèi)存開(kāi)銷。所以如果要處理大量的頁(yè)面切換,建議使用FragmentStatePagerAdapter.
每一個(gè)使用FragmentPagerAdapter的ViewPager都要有一個(gè)有效的ID集合,有效ID的集合就是Fragment的集合。
對(duì)于FragmentPagerAdapter的派生類,只需要重寫getItem(int)和getCount()就可以了。
適配器實(shí)現(xiàn)——FragmentPagerAdapter
先看完整代碼,再細(xì)講:
public class FragAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragments;
public FragAdapter(FragmentManager fm,List<Fragment> fragments) {
super(fm);
// TODO Auto-generated constructor stub
mFragments=fragments;
}
@Override
public Fragment getItem(int arg0) {
// TODO Auto-generated method stub
return mFragments.get(arg0);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mFragments.size();
}
}
這里有三個(gè)函數(shù),根據(jù)第一部分的官方文檔,可知,對(duì)于FragmentPagerAdapter的派生類,只重寫getItem(int)和getCount()就可以了。
對(duì)于構(gòu)造函數(shù),這里申請(qǐng)了一個(gè)Fragment的List對(duì)象,用于保存用于滑動(dòng)的Fragment對(duì)象,并在創(chuàng)造函數(shù)中初始化:
public FragAdapter(FragmentManager fm,List<Fragment> fragments) {
super(fm);
// TODO Auto-generated constructor stub
mFragments=fragments;
}
然后在getItem(int arg0)中,根據(jù)傳來(lái)的參數(shù)arg0,來(lái)返回當(dāng)前要顯示的fragment,下面是getItem的官方解釋,難度不大,不再細(xì)講。
public abstract Fragment getItem (int position)
Return the Fragment associated with a specified position.
最后,getCount()返回用于滑動(dòng)的fragment總數(shù);
從構(gòu)造函數(shù)所以看出,我們要構(gòu)造Fragment的集合才行,所以下面我們就先產(chǎn)生我們所需要的Fragment類;
Fragment類
XML:(fragment_layout.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical" >
<Button android:id="@+id/fragment1_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="show toast"
/>
</LinearLayout>
Java代碼:
public class CustomFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view= inflater.inflate(R.layout.fragment_layout, container, false);
//對(duì)View中控件的操作方法
Button btn = (Button)view.findViewById(R.id.fragment1_btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(getActivity(), "點(diǎn)擊了第一個(gè)fragment的BTN", Toast.LENGTH_SHORT).show();
}
});
return view;
}
}
在onCreateView()中返回要顯示的View,上面這段代碼簡(jiǎn)單演示了如何對(duì)視圖里的控件進(jìn)行操作,難度不大,不再細(xì)講。
主activity實(shí)現(xiàn)
核心代碼:
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//構(gòu)造適配器
List<Fragment> fragments=new ArrayList<Fragment>();
fragments.add(new Fragment1());
fragments.add(new Fragment2());
fragments.add(new Fragment3());
FragAdapter adapter = new FragAdapter(getSupportFragmentManager(), fragments);
//設(shè)定適配器
ViewPager vp = (ViewPager)findViewById(R.id.viewpager);
vp.setAdapter(adapter);
}
}
首先有一個(gè)最值得注意的地方:Activity派生自FragmentActivity,其實(shí)這是有關(guān)Fragment的基礎(chǔ)知識(shí),只有FragmentActivity才能內(nèi)嵌fragment頁(yè)面,普通Activity是不行的。
這段代碼主要分為兩步,第一步:構(gòu)造適配器;第二步:設(shè)定適配器。
先看構(gòu)造適配器的過(guò)程:
//構(gòu)造適配器
List<Fragment> fragments=new ArrayList<Fragment>();
fragments.add(new Fragment1());
fragments.add(new Fragment2());
fragments.add(new Fragment3());
FragAdapter adapter = new FragAdapter(getSupportFragmentManager(), fragments);
構(gòu)造一個(gè)fragment列表,然后將上面的三個(gè)Fragment類對(duì)應(yīng)的實(shí)例添加進(jìn)去,最后生成FragAdapter實(shí)例。
至于第二步,設(shè)定適配器,沒(méi)什么好講的。
可能出現(xiàn)的問(wèn)題
問(wèn)題:在MainActivity中,當(dāng)寫到這句:fragments.add(new Fragment1()); 向Fragment列表中添加Fragement對(duì)象實(shí)例時(shí),會(huì)提示“無(wú)法將Fragment1()轉(zhuǎn)換為fragment”
解決辦法 :這是因?yàn)閷?dǎo)入包不一致,一般的問(wèn)題在于:在Fragment1中導(dǎo)入的是android.app.Fragment, 而在這里導(dǎo)入類卻是:android.support.v4.app.Fragment,包不同當(dāng)然無(wú)法轉(zhuǎn)換,統(tǒng)一導(dǎo)入為android.support.v4.app.Fragment之后就正常了.
Fragment初探
我們都知道,Android上的界面展示都是通過(guò)Activity實(shí)現(xiàn)的,Activity實(shí)在是太常用了,我相信大家都已經(jīng)非常熟悉了,這里就不再贅述。
但是Activity也有它的局限性,同樣的界面在手機(jī)上顯示可能很好看,在平板上就未必了,因?yàn)槠桨宓钠聊环浅4?,手機(jī)的界面放在平板上可能會(huì)有過(guò)分被拉長(zhǎng)、控件間距過(guò)大等情況。這個(gè)時(shí)候更好的體驗(yàn)效果是在Activity中嵌入"小Activity",然后每個(gè)"小Activity"又可以擁有自己的布局。因此,我們今天的主角Fragment登場(chǎng)了。
為了讓界面可以在平板上更好地展示,Android在3.0版本引入了Fragment(碎片)功能,它非常類似于Activity,可以像Activity一樣包含布局。Fragment通常是嵌套在Activity中使用的,現(xiàn)在想象這種場(chǎng)景:有兩個(gè)Fragment,F(xiàn)ragment 1包含了一個(gè)ListView,每行顯示一本書(shū)的標(biāo)題。Fragment 2包含了TextView和ImageView,來(lái)顯示書(shū)的詳細(xì)內(nèi)容和圖片。
如果現(xiàn)在程序運(yùn)行豎屏模式的平板或手機(jī)上,F(xiàn)ragment 1可能嵌入在一個(gè)Activity中,而Fragment 2可能嵌入在另一個(gè)Activity中,如下圖所示:

而如果現(xiàn)在程序運(yùn)行在橫屏模式的平板上,兩個(gè)Fragment就可以嵌入在同一個(gè)Activity中了,如下圖所示:

由此可以看出,使用Fragment可以讓我們更加充分地利用平板的屏幕空間,下面我們一起來(lái)探究下如何使用Fragment。
首先需要注意,F(xiàn)ragment是在3.0版本引入的,如果你使用的是3.0之前的系統(tǒng),需要先導(dǎo)入android-support-v4的jar包才能使用Fragment功能。
新建一個(gè)項(xiàng)目叫做Fragments,然后在layout文件夾下新建一個(gè)名為fragment1.xml的布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00ff00" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is fragment 1"
android:textColor="#000000"
android:textSize="25sp" />
</LinearLayout>
可以看到,這個(gè)布局文件非常簡(jiǎn)單,只有一個(gè)LinearLayout,里面加入了一個(gè)TextView。我們?nèi)绶ㄅ谥圃傩陆ㄒ粋€(gè)fragment2.xml :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff00" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is fragment 2"
android:textColor="#000000"
android:textSize="25sp" />
</LinearLayout>
然后新建一個(gè)類Fragment1,這個(gè)類是繼承自Fragment的:
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}
我們可以看到,這個(gè)類也非常簡(jiǎn)單,主要就是加載了我們剛剛寫好的fragment1.xml布局文件并返回。同樣的方法,我們?cè)賹懞肍ragment2 :
public class Fragment2 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2, container, false);
}
}
然后打開(kāi)或新建activity_main.xml作為主Activity的布局文件,在里面加入兩個(gè)Fragment的引用,使用android:name前綴來(lái)引用具體的Fragment:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false" >
<fragment
android:id="@+id/fragment1"
android:name="com.example.fragmentdemo.Fragment1"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragment2"
android:name="com.example.fragmentdemo.Fragment2"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
最后打開(kāi)或新建MainActivity作為程序的主Activity,里面的代碼非常簡(jiǎn)單,都是自動(dòng)生成的:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
現(xiàn)在我們來(lái)運(yùn)行一次程序,就會(huì)看到,一個(gè)Activity很融洽地包含了兩個(gè)Fragment,這兩個(gè)Fragment平分了整個(gè)屏幕,效果圖如下:

動(dòng)態(tài)添加Fragment
你已經(jīng)學(xué)會(huì)了如何在XML中使用Fragment,但是這僅僅是Fragment最簡(jiǎn)單的功能而已。Fragment真正的強(qiáng)大之處在于可以動(dòng)態(tài)地添加到Activity當(dāng)中,因此這也是你必須要掌握的東西。當(dāng)你學(xué)會(huì)了在程序運(yùn)行時(shí)向Activity添加Fragment,程序的界面就可以定制的更加多樣化。下面我們立刻來(lái)看看,如何動(dòng)態(tài)添加Fragment。
還是在上面代碼的基礎(chǔ)上修改,打開(kāi)activity_main.xml,將其中對(duì)Fragment的引用都刪除,只保留最外層的LinearLayout,并給它添加一個(gè)id,因?yàn)槲覀円獎(jiǎng)討B(tài)添加Fragment,不用在XML里添加了,刪除后代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false" >
</LinearLayout>
然后打開(kāi)MainActivity,修改其中的代碼如下所示:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Display display = getWindowManager().getDefaultDisplay();
if (display.getWidth() > display.getHeight()) {
Fragment1 fragment1 = new Fragment1();
getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit();
} else {
Fragment2 fragment2 = new Fragment2();
getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit();
}
}
}
首先,我們要獲取屏幕的寬度和高度,然后進(jìn)行判斷,如果屏幕寬度大于高度就添加fragment1,如果高度大于寬度就添加fragment2。動(dòng)態(tài)添加Fragment主要分為4步:
1.獲取到FragmentManager,在Activity中可以直接通過(guò)getFragmentManager得到。
2.開(kāi)啟一個(gè)事務(wù),通過(guò)調(diào)用beginTransaction方法開(kāi)啟。
3.向容器內(nèi)加入Fragment,一般使用replace方法實(shí)現(xiàn),需要傳入容器的id和Fragment的實(shí)例。
4.提交事務(wù),調(diào)用commit方法提交。
現(xiàn)在運(yùn)行一下程序,效果如下圖所示:

如果你是在使用模擬器運(yùn)行,按下ctrl + F11切換到豎屏模式。效果如下圖所示:

Fragment的生命周期
和Activity一樣,Fragment也有自己的生命周期,理解Fragment的生命周期非常重要,我們通過(guò)代碼的方式來(lái)瞧一瞧Fragment的生命周期是什么樣的:
public class Fragment1 extends Fragment {
public static final String TAG = "Fragment1";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
return inflater.inflate(R.layout.fragment1, container, false);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.d(TAG, "onAttach");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, "onActivityCreated");
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
Log.d(TAG, "onDetach");
}
}
可以看到,上面的代碼在每個(gè)生命周期的方法里都打印了日志,然后我們來(lái)運(yùn)行一下程序,可以看到打印日志如下:

這時(shí)點(diǎn)擊一下home鍵,打印日志如下:

如果你再重新進(jìn)入進(jìn)入程序,打印日志如下:

然后點(diǎn)擊back鍵退出程序,打印日志如下:

看到這里,我相信大多數(shù)朋友已經(jīng)非常明白了,因?yàn)檫@和Activity的生命周期太相似了。只是有幾個(gè)Activity中沒(méi)有的新方法,這里需要重點(diǎn)介紹一下:
onAttach方法:Fragment和Activity建立關(guān)聯(lián)的時(shí)候調(diào)用。
onCreateView方法:為Fragment加載布局時(shí)調(diào)用。
onActivityCreated方法:當(dāng)Activity中的onCreate方法執(zhí)行完后調(diào)用。
onDestroyView方法:Fragment中的布局被移除時(shí)調(diào)用。
onDetach方法:Fragment和Activity解除關(guān)聯(lián)的時(shí)候調(diào)用。
Fragment之間進(jìn)行通信
通常情況下,Activity都會(huì)包含多個(gè)Fragment,這時(shí)多個(gè)Fragment之間如何進(jìn)行通信就是個(gè)非常重要的問(wèn)題了。我們通過(guò)一個(gè)例子來(lái)看一下,如何在一個(gè)Fragment中去訪問(wèn)另一個(gè)Fragment的視圖。
還是在第一節(jié)代碼的基礎(chǔ)上修改,首先打開(kāi)fragment2.xml,在這個(gè)布局里面添加一個(gè)按鈕:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#ffff00" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is fragment 2"
android:textColor="#000000"
android:textSize="25sp" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Get fragment1 text"
/>
</LinearLayout>
然后打開(kāi)fragment1.xml,為TextView添加一個(gè)id:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00ff00" >
<TextView
android:id="@+id/fragment1_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is fragment 1"
android:textColor="#000000"
android:textSize="25sp" />
</LinearLayout>
接著打開(kāi)Fragment2.java,添加onActivityCreated方法,并處理按鈕的點(diǎn)擊事件:
public class Fragment2 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button button = (Button) getActivity().findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
TextView textView = (TextView) getActivity().findViewById(R.id.fragment1_text);
Toast.makeText(getActivity(), textView.getText(), Toast.LENGTH_LONG).show();
}
});
}
}
現(xiàn)在運(yùn)行一下程序,并點(diǎn)擊一下fragment2上的按鈕,效果如下圖所示:

我們可以看到,在fragment2中成功獲取到了fragment1中的視圖,并彈出Toast。這是怎么實(shí)現(xiàn)的呢?主要都是通過(guò)getActivity這個(gè)方法實(shí)現(xiàn)的。getActivity方法可以讓Fragment獲取到關(guān)聯(lián)的Activity,然后再調(diào)用Activity的findViewById方法,就可以獲取到和這個(gè)Activity關(guān)聯(lián)的其它Fragment的視圖了。
ViewPager+Fragment
在viewpager內(nèi)包含了多個(gè)fragment,也就是我們說(shuō)的滑動(dòng)的頁(yè)面,這里我只用了兩個(gè)頁(yè)面的滑動(dòng),借用一張界面分析圖。

布局文件
1、main_common_layout.xml 這是一個(gè)頂部菜單欄
<?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="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
style="@style/MyToolbar"
app:popupTheme="@style/AppTheme.PopupOverlay"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true">
<TextView
style="@style/MytoolbarTitle"
android:text="聊天"
android:id="@+id/tb_title"/>
<ImageButton
style="@style/MytoolbarButton"
android:id="@+id/tb_bt"
android:src="@drawable/iab_popup_ic_retry_pressed"
android:onClick="refresh"/>
</android.support.v7.widget.Toolbar>
</LinearLayout>
<LinearLayout
android:id="@+id/menu_top"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@color/mytoolbarcolor"
android:gravity="center"
android:layout_alignParentStart="true">
<LinearLayout
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center">
<ImageView
android:id="@+id/friend_realtime_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:src="@drawable/gnb_chats_bg"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1">
<ImageView
android:id="@+id/friend_name_group_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:src="@drawable/gnb_friends_bg" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/line_layout"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="@color/mytoolbarcolor">
<ImageView
android:id="@+id/tabline"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@color/white" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#e9e8e8"/>
</LinearLayout>
</LinearLayout>
2、myfragment_activiy.xml 是MyFragmentActivity的布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<include layout="@layout/main_common_layout"/>
<android.support.v4.view.ViewPager
android:id="@+id/main_fragment_vp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
3、兩個(gè)Fragment內(nèi)容一樣,就一個(gè)TextView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/msg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white" />
</LinearLayout>
Java代碼
1、MFragmentActivity.java
package com.example.administrator.wechat.activity;
import android.content.ActivityNotFoundException;
...
import butterknife.Bind;
import butterknife.ButterKnife;
public class MyFragmentActivity extends Netcheck_Activity {
@Bind(R.id.tb_title)
TextView tb_title;
@Bind(R.id.tb_bt)
ImageButton tb_button;
@Bind(R.id.friend_realtime_info)
ImageView friend_realtime_info;
@Bind(R.id.friend_name_group_list)
ImageView friend_name_group_list;
@Bind(R.id.tabline)
ImageView tabline;
@Bind(R.id.main_fragment_vp)
ViewPager viewPager;// 聲明一個(gè)viewpager對(duì)象
private List<Fragment> frag_list;// 聲明一個(gè)list集合存放Fragment(數(shù)據(jù)源)
private int tabLineLength;// 1/3屏幕寬
private int currentPage = 0;// 初始化當(dāng)前頁(yè)為0(第一頁(yè))
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myfragment_activity);
ButterKnife.bind(this); //注解,用的是ButterKnife包
// 初始化滑動(dòng)條1/2
initTabLine();
// 初始化界面
initView();
//單擊事件
initListener();
}
private void initTabLine() {
// 得到顯示屏寬度(網(wǎng)上一堆方法,這里我是直接調(diào)用的) ,tabline為1/3屏幕寬度
tabLineLength = MyApplication.screenWidth / 2;
// 控件參數(shù)
ViewGroup.LayoutParams lp = tabline.getLayoutParams();
lp.width = tabLineLength;
tabline.setLayoutParams(lp);
}
//單擊事件監(jiān)聽(tīng)
private void initListener(){
//再加按鈕,只需要add,并改initTabLine中的數(shù)字即可
list_imgview = new ArrayList<>();
list_imgview.add(friend_realtime_info);
list_imgview.add(friend_name_group_list);
for (int i= 0;i< list_imgview.size();i++){
final int finalI = i;
list_imgview.get(i).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
viewPager.setCurrentItem(finalI);
}
});
}
}
private void initView() {
// 設(shè)置數(shù)據(jù)源
MainFragment fragment1 = new MainFragment();
FriendGroupFragment fragment2 = new FriendGroupFragment();
// 實(shí)例化對(duì)象
frag_list = new ArrayList<Fragment>();
frag_list.add(fragment1);
frag_list.add(fragment2);
// 設(shè)置適配器
FragmentPagerAdapter adapter = new FragmentPagerAdapter(
getSupportFragmentManager()) {
@Override
public int getCount() {
return frag_list.size();
}
@Override
public Fragment getItem(int arg0) {
return frag_list.get(arg0);
}
};
// 綁定適配器
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(0);
// 設(shè)置滑動(dòng)監(jiān)聽(tīng)
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
// 當(dāng)頁(yè)面被選擇時(shí),先將2個(gè)imageview的圖標(biāo)初始化
friend_realtime_info.setImageResource(R.drawable.gnb_ic_chats_normal);
friend_name_group_list.setImageResource(R.drawable.gnb_ic_friends_normal);
// 再改變當(dāng)前選擇頁(yè)(position)對(duì)應(yīng)的圖標(biāo)和textview
switch (position) {
case 0:
friend_realtime_info.setImageResource(R.drawable.gnb_ic_chats_selected);
tb_title.setText("聊天");
break;
case 1:
friend_name_group_list.setImageResource(R.drawable.gnb_ic_friends_selected);
tb_title.setText("好友");
break;
}
currentPage = position;
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
//arg0在第一頁(yè)到第二頁(yè)的過(guò)程中curentPage=0,arg0=0,最后到達(dá)第二頁(yè)curentPage=1,arg0=1,中間過(guò)程全為0
// arg1是個(gè)偏移值
// 取得該控件的實(shí)例
LinearLayout.LayoutParams ll = (android.widget.LinearLayout.LayoutParams) tabline
.getLayoutParams();
if (currentPage == 0 && arg0 == 0) {
// 0->1移動(dòng)(第一頁(yè)到第二頁(yè))
}
tabline.setLayoutParams(ll);
}
@Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
});
}
}
2、fragment1.java (第二個(gè)省略)
public class FriendGroupFragment extends Fragment {
private TextView tview;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.friendgroup,container,false);
tview= view.findViewById(R.id.tv);
tview.setText("這是第一個(gè)");
return view;
}
}
ViewPager + Fragment組合實(shí)現(xiàn)局部刷新Fragment
在開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)用到ViewPager與Fragment實(shí)現(xiàn)多頁(yè)面切換效果,有時(shí),我們想要局部刷新某些Fragment,而其他Fragment保持狀態(tài)不變,該如何做到呢?
先上代碼!
/**
* Created by .
*/
public abstract class BaseFragmentPagerAdapter extends FragmentPagerAdapter {
private FragmentManager mFragmentManager;
//保存每個(gè)Fragment的Tag,刷新頁(yè)面的依據(jù)
protected SparseArray<String> tags = new SparseArray<>();
public BaseFragmentPagerAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
//得到緩存的fragment
Fragment fragment = (Fragment) super.instantiateItem(container, position);
String tag = fragment.getTag();
//保存每個(gè)Fragment的Tag
tags.put(position, tag);
return fragment;
}
//拿到指定位置的Fragment
public Fragment getFragmentByPosition(int position) {
return mFragmentManager.findFragmentByTag(tags.get(position));
}
public List<Fragment> getFragments(){
return mFragmentManager.getFragments();
}
//刷新指定位置的Fragment
public void notifyFragmentByPosition(int position) {
tags.removeAt(position);
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
Fragment fragment = (Fragment) object;
//如果Item對(duì)應(yīng)的Tag存在,則不進(jìn)行刷新
if (tags.indexOfValue(fragment.getTag()) > -1) {
return super.getItemPosition(object);
}
return POSITION_NONE;
}
}
/**
* Created by .
*/
public class CustomLrcPagerAdapter extends BaseFragmentPagerAdapter {
private List<String> lrcs = new ArrayList<>();
private MusicInfo info;
public CustomLrcPagerAdapter(FragmentManager fm, MusicInfo info) {
super(fm);
this.info = info;
}
public void addDatas(List<String> lrcs) {
this.lrcs.addAll(lrcs);
notifyDataSetChanged();
}
@Override
public Fragment getItem(int position) {
return CustomLrcFragment.newInstance(info, lrcs.get(position), position);
}
//除了給定位置,其他位置的Fragment不進(jìn)行刷新
public void notifyChangeWithoutPosition(int position) {
String valueP = tags.valueAt(position);
tags.clear();
tags.put(position, valueP);
notifyDataSetChanged();
}
@Override
public int getCount() {
return lrcs.size();
}
}
刷新的核心原理很簡(jiǎn)單,相信看過(guò)源碼的都會(huì),在PagerAdapter中提供了一個(gè)方法:
/**
* Called when the host view is attempting to determine if an item's position
* has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
* item has not changed or {@link #POSITION_NONE} if the item is no longer present
* in the adapter.
*
* <p>The default implementation assumes that items will never
* change position and always returns {@link #POSITION_UNCHANGED}.
*
* @param object Object representing an item, previously returned by a call to
* {@link #instantiateItem(View, int)}.
* @return object's new position index from [0, {@link #getCount()}),
* {@link #POSITION_UNCHANGED} if the object's position has not changed,
* or {@link #POSITION_NONE} if the item is no longer present.
*/
public int getItemPosition(Object object) {
return POSITION_UNCHANGED;
}
注釋中已經(jīng)說(shuō)明了,當(dāng)我們返回了POSITION_UNCHANGED,則表示頁(yè)面數(shù)據(jù)不變,不進(jìn)行更新;
返回POSITION_NONE,則表示頁(yè)面不存在,需要進(jìn)行更新。
因此,我重寫了該方法:
@Override
public int getItemPosition(Object object) {
Fragment fragment = (Fragment) object;
//如果Item對(duì)應(yīng)的Tag存在,則不進(jìn)行刷新
if (tags.indexOfValue(fragment.getTag()) > -1) {
return super.getItemPosition(object);
}
return POSITION_NONE;
}
在接觸公司項(xiàng)目的過(guò)程中,發(fā)現(xiàn)公司項(xiàng)目中ViewPager+Fragment組合的使用方式存在問(wèn)題,估計(jì)很多人也這么用過(guò),就是定義一個(gè)集合用來(lái)緩存放到ViewPager中的Fragment,類似我們公司項(xiàng)目的這種做法:
/**
* Created by Administrator on 2016/11/30.
*/
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> mList_Fragment = new ArrayList<>();
private HashMap<Integer, Boolean> mList_Need_Update = new HashMap<>();
private FragmentManager mFragmentManager;
public ViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
mFragmentManager = fm;
mList_Need_Update.clear();
mList_Fragment.clear();
if (fragments != null) {
mList_Fragment.addAll(fragments);
}
}
// @Override
// public Object instantiateItem(ViewGroup container, int position) {
// Fragment fragment = (Fragment) super.instantiateItem(container, position); //得到緩存的fragment
//
// Boolean update = mList_Need_Update.get(position);
// if (update != null && update) {
// String fragmentTag = fragment.getTag(); //得到tag,這點(diǎn)很重要
// FragmentTransaction ft = mFragmentManager.beginTransaction();
// ft.remove(fragment); //移除舊的fragment
// fragment = getItem(position); //換成新的fragment
// ft.add(container.getId(), fragment, fragmentTag); //添加新fragment時(shí)必須用前面獲得的tag,這點(diǎn)很重要
// ft.attach(fragment);
// ft.commit();
// mList_Need_Update.put(position, false); //清除更新標(biāo)記(只有重新啟動(dòng)的時(shí)候需要去創(chuàng)建新的fragment對(duì)象),防止正常情況下頻繁創(chuàng)建對(duì)象
// }
//
// return fragment;
// }
public List<Fragment> getListFragment(){
return mList_Fragment;
}
public void setListFragment(List<Fragment> list_Fragment) {
// if(list_Fragment != null){
// FragmentTransaction ft = mFragmentManager.beginTransaction();
// for (int i = 0; i < mList_Fragment.size(); i++) {
// Fragment fragment = (Fragment) mList_Fragment.get(i);
// ft.remove(fragment);
// }
// ft.commit();
// ft = null;
// mFragmentManager.executePendingTransactions();
// }
mList_Need_Update.clear();
this.mList_Fragment.clear();
if (list_Fragment != null) {
this.mList_Fragment.addAll(list_Fragment);
}
notifyDataSetChanged();
}
public void setListNeedUpdate(List<Fragment> fragments) {
mList_Fragment.clear();
if (fragments != null) {
mList_Fragment.addAll(fragments);
}
mList_Need_Update.clear();
for (int i = 0; i < mList_Fragment.size(); i++) {
mList_Need_Update.put(i, true);
}
}
@Override
public Fragment getItem(int position) {
if(mList_Fragment.size() < position){
return null;
}
return mList_Fragment.get(position);
}
@Override
public int getCount() {
return mList_Fragment.size();
}
@Override
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
try {
super.restoreState(state, loader);
} catch (Exception e) {
}
}
}
這種做法看似方便我們操作ViewPager中的Fragment,但是存在一個(gè)很致命的問(wèn)題。
某些情況下,我們?cè)趶钠渌?yè)面回退到ViewPager,在進(jìn)行Fragment數(shù)據(jù)更新時(shí),會(huì)發(fā)現(xiàn)居然沒(méi)有效果(或者效果很詭異,例如會(huì)出現(xiàn)多次調(diào)用的情況)。
這種情況其實(shí)就是Fragment進(jìn)行了熱啟動(dòng)。(我的說(shuō)法不知是否準(zhǔn)確,指的就是內(nèi)存不足時(shí),頁(yè)面被銷毀了并調(diào)用了onSaveInstanceState方法,在重新回到頁(yè)面時(shí),我們可以從Bundle savedInstanceState中拿到之前緩存的數(shù)據(jù)。)
由于FragmentPagerAdapter中的FragmentManager已經(jīng)幫我們緩存了所有Fragment,并且在數(shù)據(jù)恢復(fù)時(shí),也自動(dòng)幫我們進(jìn)行恢復(fù)處理。
所以,個(gè)人猜測(cè) (未經(jīng)源碼驗(yàn)證的?。?,在FragmentManager進(jìn)行數(shù)據(jù)恢復(fù)時(shí),如果我們本地通過(guò)集合緩存了一份Fragment,則這份Fragment與FragmentManager進(jìn)行數(shù)據(jù)恢復(fù)后的Fragment是不同的!
我個(gè)人的做法是,每次需要操作ViewPager中的Fragment時(shí),都從FragmentManager中拿:
//拿到指定位置的Fragment
public Fragment getFragmentByPosition(int position) {
return mFragmentManager.findFragmentByTag(tags.get(position));
}
public List<Fragment> getFragments(){
return mFragmentManager.getFragments();
}