《Android Fragment完全解析,關于碎片你所需知道的一切》
我們都知道,Android上的界面展示都是通過Activity實現的,
但是Activity也有它的局限性,同樣的界面在手機上顯示可能很好看,在平板上就未必了,因為平板的屏幕非常大,手機的界面放在平板上可能會有過分被拉大、控件間距過大等情況。這個時候更好的體驗效果是在Activity中嵌入“小Activity”,然后每個“小Activity”又可以擁有自己的布局。因此有了Fragment。
Fragment初探
為了讓界面在平板上更好地展示,Android在3.0版本引入了Fragment(碎片)功能的,它非常類似于Activity,可以像Activity一樣包含布局。Fragment通常是嵌套在Activity中使用的,現在想象這種場景:有兩個Fragment,Fragment1包含了一個ListView,每行顯示一本書的標題。Fragment2包含了TextView和ImageView,來顯示書的詳細內容和圖片。
如果現在程序運行豎屏模式的平板或手機上,Fragment 1可能嵌入在一個Activity中,而Fragment 2可能嵌入在另一個Activity中,如下圖所示:
而如果現在程序運行在橫屏模式的平板上,兩個Fragment就可以嵌入在同一個Activity中了,如下圖所示:
由此可以看出,使用Fragment可以讓我們更加充分地利用平板的屏幕空間。
需要注意,Fragment是在3.0版本引入的,如果你使用的是3.0之前的系統,需要先導入android-support-v4的jar包才能使用Fragment功能。
靜態使用Fragment
把Fragment當成普通的控件,直接寫在Activity的布局文件中。步驟:
1、繼承Fragment,重寫onCreateView決定Fragemnt的布局
2、在Activity中聲明此Fragment,就當和普通的View一樣
新建兩個分別名為fragment1和fragment2的xml布局文件
然后新建一個類Fragment1,這個類是繼承自Fragment的:
同樣建立Fragment2.
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}
然后打開或新建activity_main.xml作為主Activity的布局文件,在里面加入兩個Fragment的引用,使用android:name前綴來引用具體的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>
動態添加Fragment
在XML中使用Fragment,這僅僅是Fragment最簡單的功能而已。Fragment的真正強大之處在于可以動態地添加到Activity當中。
在上面的基礎上修改。打開activity_main.xml,將其中對Fragment的引用都刪除,只保最外層的LinearLayout,并給它添加一個id.因為我們要動態添加Fragment,不用在XML里添加了。
在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();
}
}
}
首先,我們要獲取屏幕的寬度和高度,然后進行判斷,如果屏幕寬度大于高度就添加fragment1,如果高度大于寬度就添加fragment2.動態添加fragment主要分為4步。
- 獲取到FragmentManager,在Activity中可以直接通過getFragmentManager得到。
- 開啟一個事務,通過調用beginTransaction方法開啟。
- 向容器內加入Fragment,一般使用replace方法實現,需要傳入容器的id和Fragment的實例。
- 提交事務。
Fragment的生命周期
因為Fragment必須嵌入在Activity中使用,所以Fragment的生命周期和它所處的Activity是密切相關的。
如果Activity是暫停狀態,其中所有的Fragment都是暫停狀態;如果Activity是stopped狀態,這個Activity中所有的Fragment都不能被啟動;如果Activity被銷毀,那么它其中的所有Fragment都會被銷毀。
但是,當Activity在活動狀態,可以獨立控制Fragment的狀態,比如加上或者移除Fragment。
使用Fragment時,需要繼承Fragment或者Fragment的子類(DialogFragment, ListFragment, PreferenceFragment, WebViewFragment),所以Fragment的代碼看起來和Activity的類似。
必須實現的三個回調函數:
onCreate()
系統在創建Fragment的時候調用這個方法,這里應該初始化相關的組件,一些即便是被暫停或者被停止時依然需要保留的東西。
onCreateView()
當第一次繪制Fragment的UI時系統調用這個方法,必須返回一個View,如果Fragment不提供UI也可以返回null。
注意,如果繼承自ListFragment,onCreateView()默認的實現會返回一個ListView,所以不用自己實現。
onPause()
當用戶離開Fragment時第一個調用這個方法,需要提交一些變化,因為用戶很可能不再返回來。
Fragment和Activity的生命周期非常相似,只有幾個Activity中沒有的新方法,如下:
onAttach方法:Fragment和Activity建立關聯的時候調用。
onCreateView方法:為Fragment加載布局時調用。
onActivityCreated方法:當Activity中的onCreate方法執行完后調用。
onDestroyView方法:Fragment中的布局被移除時調用。
onDetach方法:Fragment和Activity解除關聯的時候調用。
注意:除了onCreateView,其他的所有方法如果你重寫了,必須調用父類對于該方法的實現,
Fragment之間進行通信
通常情況下,Activity中都會包含多個Fragment,這時多個Fragment之間如何進行通信就是個非常重要的問題了。
主要都是通過getActivity這個方法實現的。getActivity方法可以讓Fragment獲取到關聯的Activity,然后再調用Activity的findViewById方法,就可以獲取到和這個Activity關聯的其它Fragment的視圖了。
Fragment是什么,生命周期、靜態和動態使用,
Fragment如何與Activity交互?Fragment如何創建對話框?Fragment如何與ActionBar集成等等。
Fragment家族常用的API
android.app.Fragment主要用于定義Fragment
android.app.FragmentManager 主要用于在Activity中操作Fragment
android.app.FragmentTransaction 保證一系列Fragment操作的原子性
a.獲取FragmentManager的方式
getFragmentManager(); //v4中,getSupportFragmentManager
b. 主要的操作都是FragmentTransaction
FragmentTransaction transaction = fm.beginTransaction();//開啟一個事務
- transaction.add()
往Activity中添加一個Fragment
- transaction.remove()
從Activity中移除一個Fragment,如果被移除的Fragment沒有添加到回退棧,這個Fragment實例將會被銷毀。 - transaction.replace()
使用另一個Fragment替換當前的,實際上就是remove()然后add()的合體 - transaction.hide()
隱藏當前的Fragment(),僅僅是設為不可見,并不會銷毀。 - transaction.show()
顯示之前隱藏的Fragment - detach()
會將view從UI中移除,和remove()不同,此時的Fragment的狀態依然由FragmentManager維護 - attach()
重建view視圖,附加到UI上并顯示。 - transatcion.commit()//提交一個事務
注意:常用Fragment的哥們,可能會經常遇到這樣Activity狀態不一致:State loss這樣的錯誤。主要是因為:commit方法一定要在Activity.onSaveInstance()之前調用。
上述,基本是操作Fragment的所有的方式了,在一個事務開啟到提交可以進行多個的添加、移除、替換等操作。
值得注意的是:如果你喜歡使用Fragment,一定要清楚這些方法,哪個會銷毀視圖,哪個會銷毀實例,哪個僅僅只是隱藏,這樣才能更好的使用它們。
a、比如:我在FragmentA中的EditText填了一些數據,當切換到FragmentB時,如果希望會到A還能看到數據,則適合你的就是hide和show;也就是說,希望保留用戶操作的面板,你可以使用hide和show,當然了不要使勁在那new實例,進行下非null判斷。
b、再比如:我不希望保留用戶操作,你可以使用remove(),然后add();或者使用replace()這個和remove,add是相同的效果。
c、remove和detach有一點細微的區別,在不考慮回退棧的情況下,remove會銷毀整個Fragment實例,而detach則只是銷毀其視圖結構,實例并不會被銷毀。那么二者怎么取舍使用呢?如果你的當前Activity一直存在,那么在不希望保留用戶操作的時候,你可以優先使用detach。
管理Fragment回退棧
類似于Android系統為Activity維護一個任務棧,我們也可以通過Activity維護一個回退棧來保存每次Fragment事務發生的變化。如果你將Fragment任務添加到回退棧,當用戶點擊后退按鈕時,將看到上一次的保存的Fragment。一旦Fragment完全從后退棧中彈出,用戶再次點擊后退鍵,則退出當前Activity。
添加一個Fragment事務到回退棧
FragmentTransaction.addToBackStack(String)
eg. Activity的布局文件
<RelativeLayout 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" >
<FrameLayout
android:id="@+id/id_content"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</FrameLayout>
</RelativeLayout>
不同的Fragment就在這個FrameLayout中顯示。
MainActivity
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.id_content, new FragmentOne(),"ONE");
tx.commit();
}
}
Fragment與Activity通信
因為所有的Fragment都是依附于Activity的,所以通信起來并不復雜,大概歸納為:
a、如果你Activity中包含自己管理的Fragment的引用,可以通過引用直接訪問所有的Fragment的public方法
b、如果Activity中未保存任何Fragment的引用,那么沒關系,每個Fragment都有一個唯一的TAG或者ID,可以通過getFragmentManager.findFragmentByTag()或者findFragmentById()獲得任何Fragment實例,然后進行操作。
c、在Fragment中可以通過getActivity得到當前綁定的Activity的實例,然后進行操作。
注:如果在Fragment中需要Context,可以通過調用getActivity(),如果該Context需要在Activity被銷毀后還存在,則使用getActivity().getApplicationContext()。
Fragment與Activity通信的最佳實踐
因為要考慮Fragment的重復使用,所以必須降低Fragment與Activity的耦合,而且Fragment更不應該直接操作別的Fragment,畢竟Fragment操作應該由它的管理者Activity來決定
首先看FragmentOne
public class FragmentOne extends Fragment implements OnClickListener{
private Button mBtn;
/**
* 設置按鈕點擊的回調
* @author zhy
*
*/
public interface FOneBtnClickListener {
void onFOneBtnClick();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
{
View view =inflater.inflate(R.layout.fragment_one, container, false);
mBtn = (Button)view.findViewById(R.id.id_**fragment**_one_btn);
mBtn.setOnClickListener(this);
return view;
}
/**
* 交給宿主Activity處理,如果它希望處理
*/
@Override
public void onClick(View v)
{
if (getActivity() instanceof FOneBtnClickListener)
{
((FOneBtnClickListener)getActivity()).onFOneBtnClick();
}
}
}
FragmentTwo代碼
public class FragmentTwo extends Fragment implements OnClickListener{
private Button mBtn ;
private FTwoBtnClickListener fTwoBtnClickListener ;
public interface FTwoBtnClickListener
{
void onFTwoBtnClick();
}
//設置回調接口
public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener)
{
this.fTwoBtnClickListener = fTwoBtnClickListener;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.**fragment**_two, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
mBtn.setOnClickListener(this);
return view ;
}
@Override
public void onClick(View v)
{
if(fTwoBtnClickListener != null)
{
fTwoBtnClickListener.onFTwoBtnClick();
}
}
}
與FragmentOne極其類似,但是我們提供了setListener這樣的方法,意味著Activity不僅需要實現該接口,還必須顯示調用mFTwo.setfTwoBtnClickListener(this)。
可以看到現在的FragmentOne不和任何Activity耦合,任何Activity都可以使用;并且我們聲明了一個接口,來回調其點擊事件,想要管理其點擊事件的Activity實現此接口就即可??梢钥吹轿覀冊趏nClick中首先判斷了當前綁定的Activity是否實現了該接口,如果實現了則調用
如何處理運行時配置發生變化
Android 屏幕旋轉 處理 AsyncTask 和 ProgressDialog 的最佳方案
當屏幕發生旋轉,Activity發生重新啟動,默認的Activity中的Fragment也會跟著Activity重新創建;這樣造成當旋轉的時候,本身存在的Fragment會重新啟動,然后當執行Activity的onCreate時,又會再次實例化一個新的Fragment。
如何解決呢?
其實通過檢查onCreate的參數Bundle savedInstanceState就可以判斷,當前是否發生Activity的重新創建:
默認的savedInstanceState會存儲一些數據,包括Fragment的實例。所以,我們簡單改一下代碼,只有在savedInstanceState==null時,才進行創建Fragment實例:
使用Fragment創建對話框
Android 官方推薦 : DialogFragment 創建對話框
1. 概述
一般情況下,我們在Activity里面會這么添加Fragment:
public class MainActivity extends FragmentActivity{
private ContentFragment mContentFragment ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
mContentFragment = (ContentFragment)
fm.findFragmentById(R.id.id_fragment_container);
if(mContentFragment == null )
{
mContentFragment = new ContentFragment();
fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();
}
}
}
針對上面代碼,問兩個問題:
1、為什么需要判null呢?
主要是因為,當Activity因為配置發生改變(屏幕旋轉)或者內存不足被系統殺死,造成重新創建時,我們的fragment會被保存下來,但是會創建新的FragmentManager,新的FragmentManager會首先會去獲取保存下來的fragment隊列,重建fragment隊列,從而恢復之前的狀態。
2、add(R.id.id_fragment_container,mContentFragment)中的布局的id有何作用?
一方面呢,是告知FragmentManager,此fragment的位置;另一方面是此fragment的唯一標識;就像我們上面通過fm.findFragmentById(R.id.id_fragment_container)查找
2. Fragment Arguments
需要通過Intent傳遞參數到目標Activity的Fragment中,那么此Fragment如何獲取當前的Intent的值呢(考慮解耦的情況下)?
public class ContentFragment extends Fragment{
private String mArgument;
public static final String ARGUMENT = "argument";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState); // mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);
Bundle bundle = getArguments();
if (bundle != null)
mArgument = bundle.getString(ARGUMENT);
}
/**
* 傳入需要的參數,設置給arguments
* @param argument
* @return
*/
public static ContentFragment newInstance(String argument)
{
Bundle bundle = new Bundle();
bundle.putString(ARGUMENT, argument);
ContentFragment contentFragment = new ContentFragment();
contentFragment.setArguments(bundle);
return contentFragment;
}
給Fragment添加newInstance方法,將需要的參數傳入,設置到bundle中,然后setArguments(bundle),最后在onCreate中進行獲??;
這樣就完成了Fragment和Activity間的解耦。當然了這里需要注意:
setArguments方法必須在fragment創建以后,添加給Activity前完成。千萬不要,首先調用了add,然后設置arguments。
3. Fragment的startActivityForResult
我們點擊跳轉到對應Activity的Fragment中,并且希望它能夠返回參數。
在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是,沒有setResult()方法,用于設置返回的intent,這樣我們就需要通過調用getActivity.setResult(ListTitleFrament.REQUEST_DETAIL, intent);
fragment能夠從Activity中接收返回結果,但是其自設無法產生返回結果,只有Activity擁有返回結果。
5. FragmentPaperAdapter與FragmentStatePagerAdapter
相信這兩個PagerAdapter的子類,大家都不陌生吧~~自從Fragment問世,使用ViewPager再結合上面任何一個實例的制作APP主頁的案例特別多
那么這兩個類有何區別呢?
主要區別就在與對于fragment是否銷毀,下面細
說:
FragmentPagerAdapter:對于不再需要的fragment,選擇調用detach方法,僅銷毀視圖,并不會銷毀fragment實例。
FragmentStatePagerAdapter:會銷毀不再需要的fragment,當當前事務提交以后,會徹底的將fragmeng從當前Activity的FragmentManager中移除,state標明,銷毀時,會將其onSaveInstanceState(Bundle outState)中的bundle信息保存下來,當用戶切換回來,可以通過該bundle恢復生成新的fragment,也就是說,你可以在onSaveInstanceState(Bundle outState)方法中保存一些數據,在onCreate中進行恢復創建。
如上所說,使用FragmentStatePagerAdapter當然更省內存,但是銷毀新建也是需要時間的。一般情況下,如果你是制作主頁面,就3、4個Tab,那么可以選擇使用FragmentPagerAdapter,如果你是用于ViewPager展示數量特別多的條目時,那么建議使用FragmentStatePagerAdapter。
參考: