網絡電臺研究(3)Fragment系統學習,解決各種問題

如果你已經非常熟練的,在工程內大量使用過自定義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);








發佈了30 篇原創文章 · 獲贊 8 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章