如果你已經非常熟練的,在工程內大量使用過自定義View,那麼學習使用和理解Fragment將變得很容易。
Fragment的到來更加體現OOP思想,獨立View模塊,有自己的生命週期。可隨時隨地複用,在Activity裏可以添加、移除、替換。功能強大。在Android開發過程中,自己經常性自定義View,其實和Fragment想實現的一樣,在我看來最大區別在於Fragment具有生命週期。下面來看圖解(圖片摘抄於https://github.com/xxv/android-lifecycle)
這張圖完整的描述了Fragment和Activity的生命週期。
Fragment相關主要類:
1.android.app.Fragment Or android.support.v4.app.Fragment
主要是定義Fragment
2.android.app.FragmentManager Or android.support.v4.app.FragmentManager
getFragmentManager() // v4中 在FragmentActivity中getSupportFragmentManager
其中最終要的是 Back Stack 的掌握
3. android.app.FragmentTransaction Or android.support.v4.app.FragmentTransaction
實現原子操作必須的事務
FragmentTransaction transaction = fm.benginTransatcion();// 開啓一個事務
transaction.add()
向當前Activity添加Fragment
transaction.remove()
從當前Activity中移除Fragment,如果被移除的Fragment沒有執行addToBackStack(),則該Fragment實例將會銷燬。
transaction.replace()
使用另一個Fragment替換當前的,實際是先remove()然後add()
transaction.hide()
隱藏當前的Fragment,僅僅是設爲不可見,並不會銷燬
transaction.show()
顯示之前隱藏的Fragment
detach()
將此Fragment從Activity中分離,會銷燬其佈局,但不會銷燬該實例
attach()
將從Activity中分離的Fragment,重新關聯到該Activity,重新創建其視圖層次
transatcion.commit()//提交一個事務
API使用的建議:
a) 比如:我在FragmentA中有一個通過網絡獲得ListView數據,當切換到FragmentB時,我當然會希望返回A還能看到數據,則適合你的就是hide和show;也就是說,希望保留用戶操作的面板,你可以使用hide和show。
b) 再比如:我不希望保留用戶操作,你可以使用remove(),然後add();或者使用replace()這個和remove,add是相同的效果。
c) remove和detach有一點細微的區別,在不考慮回退棧的情況下,remove會銷燬整個Fragment實例,而detach則只是銷燬其視圖結構,實例並不會被銷燬。那麼二者怎麼取捨使用呢?如果你的當前Activity一直存在,那麼在不希望保留用戶操作的時候,你可以優先使用detach。
主要代碼: @Override
public void onClick(View v)
{
FragmentThree fThree = new FragmentThree();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
//注意 hide + add 方式不會銷燬視圖
tx.hide(this);
tx.add(R.id.id_content , fThree, "THREE");
//replace 其實是 remove+add 方式 會銷燬視圖
// tx.replace(R.id.id_content, fThree, "THREE");
//我們可以用remove+add 方式來代替 replace方式
// tx.remove(getFragmentManager().getBackStackEntryAt(1));
// tx.add(R.id.id_content , fThree, "THREE");
//前一個Fragment被加入到棧中,不會被銷燬實例
tx.addToBackStack(null);
tx.commit();
}
Fragment可以重複利用,就像自定義View,來看下佈局文件中如何引用。注意android:name屬性 填寫對應的class
也可以使用class屬性來關聯Fragment
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.news.ArticleListFragment"
android:id="@+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.news.ArticleReaderFragment"
android:id="@+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
你可以提供 android:id 亦或是 android:tag 來唯一標識,兩者都可以。
項目場景學習:
網易新聞APP 汽車之家APP我們上邊說過,調用hide、replace 的區別在於是否銷燬視圖,而調用addToBackStatck會確定是否銷燬前一個Fragment實例.若是使用ViewPager,就要涉及到2個Adapter ---- FragmentPagerAdapter 、 FragmentStatePagerAdapter 從類的名字上即可看出最大區別是 “狀態”管理。詳細區別如下:
1. 若ViewPager使用的是FragmentPagerAdapter.出於使用FragmentPagerAdapter 時,Fragment對象會一直存留在內存中,所以當有大量的顯示頁時,就不適合用 它僅適用於只有少數的page情況,比如2-3個頁面。
2.使用FragmentStatePagerAdapter 時,如果Fragment不顯示,那麼Fragment對象會被銷燬,但在回調onDestroy()方法之前會回調onSaveInstanceState(Bundle outState)方法來保存Fragment的狀態,下次Fragment顯示時通過onCreate(Bundle savedInstanceState)把存儲的狀態值取出來,FragmentStatePagerAdapter 比較適合頁面比較多的情況 。我們來看下當自定義FragmentStatePagerAdapter時,我們需要重寫的方法。
getCount() : 獲得數量
getItem(): 獲得當前要顯示的Fragment
汽車之家APP應該只是重寫了必要的getCount() 與 getItem() 實例的管理直接交給Adapter來自己處理。
那爲什麼我們的Fragment狀態被保存了呢,請看這個方法。
/**
* 源碼的該方法主要是幫助我們從緩存中拿,拿不到重新new
* 所以頁面沒有被銷燬,因爲狀態是被保存的。這個類 Fragment.SavedState
*/
@Override
public Object [More ...] instantiateItem(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);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
那麼對應Fragment做了哪些東東呢,頁面狀態被保存,頁面沒有被銷燬,我們知道頁面被隱藏掉了,再show()的時候,會調用attach()。
看到了吧.這就是爲何狀態被保存了。記錄了你之前瀏覽的位置,頁面沒有被銷燬!狀態被保留!
今天重新梳理了一下,針對幾個業務問題分析並且解釋一下
1. 我想實現“懶加載” ,就是當Fragment對用戶可見時,請求服務器的數據,刷新列表操作等。
答: 這是一個長期的誤區,比如Fragment+ViewPager做,會默認加載到下一個Fragment,但是,要理解,你別把自己請求服務器的方法綁在一起。系統默認只是要準備頁面,狀態等等,刷不刷數據跟這個沒有關係,解決方法
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
//可見
}else{
//不可見
}
}
在你的fragment裏面複寫這個方法,實現去把。
2,. 我想知道,fragment是 new出來的,還是從緩存中取得。因爲如果從緩存中取,某些操作我就不做了,我就在fragment新建的時候初始化一次。
答: 記得麼,如果是FragmentPageAdapter 則會常駐內存中的,這種咱一般就認爲它僅僅會調用一次onCreate(Bundle onInstanceState),當然被回收了另一說。
咱們說說被回收瞭如何判斷。 拿FragmentStatePageAdapter,因爲它的實例不是常駐內存的,會被銷燬,所以每次onCreate()會調用。但是它的狀態會被保留,在OnCreate(Bundle onInstanceState)里根據onInstanceState是否爲null 來判斷是否是new的Fragment。所以有下面的代碼
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
// 狀態未保留,Fragment被系統回收了
}else{
//狀態被保留,Fragment實例被銷燬,但是狀態還在
}
}
另外重點理解下,如果使用的是 FragmentPageAdapter當不可見時,會調用onDestoryView() 這時候源碼裏
調用是detach() 就是View與Activity分離,銷燬View。
若是使用的FragmentStatePageAdapter ,則會調用 onDestoryView() -> onDestory -> onDetach()
源碼裏調用是 onremove 銷燬了view,並且銷燬了fragment實例,狀態保存。
若是被強制回收,則 銷燬View,銷燬fragment實例,狀態銷燬。
4. 如果我想保存自己的數據,等到Fragment再次顯示的時候用到,如何呢?
答: 看代碼
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
// 新new的Fragment ,實例被銷燬l
}else{
int value = (Integer) savedInstanceState.get("value");
}
EventBus.getDefault().register(this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
outState.putInt("value", 1234);
super.onSaveInstanceState(outState);
}
另外說一點,在Fragment的onSaveInstanceState(Bundke outState)裏這裏面是空的,那到底誰最後執行了保存Bundle State的代碼呢?
是 FragmentManager最後保存狀態的。
但是因爲FragmentActivity重寫了Activity的onSaveInstanceState(Bundke outState)
裏面都是讓FragmentManager最後保存的。Activity的是保存在Application裏面。
關於這個,我想說下我之前遇到困惑的問題。以前老項目大部分實現Tab都是TabActivity,但是因爲Fragment出現了,所以以前涉及到Activity的都會有對應的Fragment來代替,就出現了這個類 FragmentTabHost ,這貨不會自動的保存狀態,導致修改完的項目不是原來的效果,頁面沒有被保存,重新調用接口! 這就火大了,網上有使用這貨並且如何保存狀態的解決方案。http://www.cnblogs.com/tiantianbyconan/p/3360938.html 這老兄的想法也沒有錯,使用hide和show,據我們所知這倆個方法不會銷燬View,所以壞處是佔用內存。這樣你就相當於埋了一顆未知炸彈,不知道什麼時候內存沒了,奔潰了,或者其他異常的問題,最主要的應該是頁面會卡吧。所以若是讓我改,我會銷燬頁面,但是不選擇銷燬實例。使用detach()方法+緩存數據(不請求網絡,讓UI更快顯示)+記錄用戶瀏覽位置。 我沒有試驗,但是應該是可行的。
我分享個例子程序,效果和上面這倆個APP是一樣的。源碼 http://download.csdn.net/detail/u013651247/8150105
我改了下源碼,主要是添加一個setCutIndicatorWidth方法,減少左右下劃線的距離,使得當一個平面是有2個或者3個時,UI上更加好看。
請看效果
修改源碼後的效果。適應性更好了。
重要的屬性:
// 可以設置它的各種屬性比如
// 分割線
mTabStrip.setDividerColor(getResources().getColor(
android.R.color.transparent));
// 背景色,比如點擊有背景
mTabStrip.setBackground(getResources().getDrawable(
R.drawable.background_tab));
// 底線設置
mTabStrip.setUnderlineColorResource(R.color.public_theme_color);
mTabStrip.setUnderlineHeight(0);
// 滑動的線設置
mTabStrip.setIndicatorHeight(8);
mTabStrip.setIndicatorColorResource(R.color.public_theme_color);
// 分類字體設置
mTabStrip.setTextColor(getResources().getColor(
R.color.public_text_color));
mTabStrip.setTextSize(28);
//當item較少時,用不用自動適配屏幕
mTabStrip.setShouldExpand(true);
//減掉左右劃線,讓UI看起來更舒服
mTabStrip.setCutIndicatorWidth(25);