Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中的Fragment類使用進階

Android中的Fragment類使用進階

編輯:關於Android編程

0、回顧
Fragment 代表 Activity 當中的一項操作或一部分用戶界面。 一個 Activity 中的多個 Fragment 可以組合在一起,形成一個多部分拼接而成的用戶界面組件,並可在多個 Activity 中復用。一個 Fragment 可被視為 Activity 中一個模塊化的部分, 它擁有自己的生命周期,並接收自己的輸入事件,在 Activity 運行過程中可以隨時添加或移除它 (有點類似“子 Activity”,可在不同的 Activity 中重用)。

Fragment 必須嵌入某個 Activity 中,其生命周期直接受到宿主 Activity 生命周期的影響。 例如,當 Activity 被暫停(Paused)時,其內部所有的 Fragment 也都會暫停。 而當 Activity 被銷毀時,它的 Fragment 也都會被銷毀。 不過,在 Activity 運行期間(生命周期狀態處於 恢復(Resumed) 狀態時),每一個 Fragment 都可以被獨立地操作,比如添加或移除。 在執行這些操作事務時,還可以將它們加入該 Activity 的回退棧(Back Stack)中 — Activity 回退棧的每個入口就是一條操作過的 Fragment 事務記錄。 回退堆棧使得用戶可以通過按下 回退(Back) 鍵來回退 Fragment 事務(後退一步)。

當把 Fragment 加入 Activity 布局(Layout) 後,它位於 Activity View 層次架構(Hierarchy)的某個 ViewGroup 裡,且擁有自己的 View 布局定義。 通過在 Activity 的 Layout 文件中聲明 <fragment> 元素,可以在 Layout 中添加一個 Fragment。 也可以用程序代碼在已有的 ViewGroup 中添加一個 Fragment。 不過, Fragment 並不一定非要是 Activity 布局的一部分,它也可以沒有自己的界面,而是用作 Activity 的非可視化工作組件。


1、概述
相信大家對Fragment的都不陌生,對於Fragment的使用,一方面Activity需要在布局中為Fragment安排位置,另一方面需要管理好Fragment的生命周期。Activity中有個FragmentManager,其內部維護fragment隊列,以及fragment事務的回退棧。
一般情況下,我們在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
下面描述一個簡單的場景,比如我們某個按鈕觸發Activity跳轉,需要通過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); 
     
  } 

我們直接在Fragment的onCreate中,拿到宿主Activty,宿主Activity中肯定能通過getIntent拿到Intent,然後通過get方法,隨意拿參數~~
這麼寫,功能上是實現了,但是呢?存在一個大問題:我們用Fragment的一個很大的原因,就是為了復用。你這麼寫,相當於這個Fragment已經完全和當前這個宿主Activity綁定了,復用直接廢了~~~所以呢?我們換種方式,推薦使用arguments來創建Fragment。

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
依舊是一個簡單的場景:兩個Fragment,一個展示文章列表的Fragment(叫做ListTitleFragment),一個顯示詳細信息的Fragment(叫做:ContentFragment),當然了,這兩個Fragment都有其宿主Activity。
現在,我們點擊列表Fragment中的列表項,傳入相應的參數,去詳細信息的Fragment展示詳細的信息,在詳細信息頁面,用戶可以進行點評,當用戶點擊back以後,我們以往點評結果顯示在列表的Fragment對於的列表項中;
也就是說,我們點擊跳轉到對應Activity的Fragment中,並且希望它能夠返回參數,那麼我們肯定是使用Fragment.startActivityForResult ;
在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是呢,沒有setResult()方法,用於設置返回的intent,這樣我們就需要通過調用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);。
詳細代碼:
ListTitleFragment

public class ListTitleFragment extends ListFragment 
{ 
 
  public static final int REQUEST_DETAIL = 0x110; 
  private List<String> mTitles = Arrays.asList("Hello", "World", "Android"); 
  private int mCurrentPos ;  
  private ArrayAdapter<String> mAdapter ;  
 
   
  @Override 
  public void onActivityCreated(Bundle savedInstanceState) 
  { 
    super.onActivityCreated(savedInstanceState); 
    setListAdapter(mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mTitles)); 
  } 
   
  @Override 
  public void onListItemClick(ListView l, View v, int position, long id) 
  { 
    mCurrentPos = position ;  
    Intent intent = new Intent(getActivity(),ContentActivity.class); 
    intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position)); 
    startActivityForResult(intent, REQUEST_DETAIL); 
  } 
 
   
  @Override 
  public void onActivityResult(int requestCode, int resultCode, Intent data) 
  { 
    Log.e("TAG", "onActivityResult"); 
    super.onActivityResult(requestCode, resultCode, data); 
    if(requestCode == REQUEST_DETAIL) 
    { 
      mTitles.set(mCurrentPos, mTitles.get(mCurrentPos)+" -- "+data.getStringExtra(ContentFragment.RESPONSE)); 
      mAdapter.notifyDataSetChanged(); 
    } 
  } 
} 

ContentFragment

public class ContentFragment extends Fragment 
{ 
 
  private String mArgument; 
  public static final String ARGUMENT = "argument"; 
  public static final String RESPONSE = "response"; 
 
  @Override 
  public void onCreate(Bundle savedInstanceState) 
  { 
    super.onCreate(savedInstanceState); 
    Bundle bundle = getArguments(); 
    if (bundle != null) 
    { 
      mArgument = bundle.getString(ARGUMENT); 
      Intent intent = new Intent(); 
      intent.putExtra(RESPONSE, "good"); 
      getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent); 
    } 
 
  } 
 
  public static ContentFragment newInstance(String argument) 
  { 
    Bundle bundle = new Bundle(); 
    bundle.putString(ARGUMENT, argument); 
    ContentFragment contentFragment = new ContentFragment(); 
    contentFragment.setArguments(bundle); 
    return contentFragment; 
  } 
 
  @Override 
  public View onCreateView(LayoutInflater inflater, ViewGroup container, 
      Bundle savedInstanceState) 
  { 
    Random random = new Random(); 
    TextView tv = new TextView(getActivity()); 
    tv.setText(mArgument); 
    tv.setGravity(Gravity.CENTER); 
    tv.setBackgroundColor(Color.argb(random.nextInt(100), 
        random.nextInt(255), random.nextInt(255), random.nextInt(255))); 
    return tv; 
  } 
} 

貼出了兩個Fragment的代碼,可以看到我們在ListTitleFragment.onListItemClick,使用startActivityForResult()跳轉到目標Activity,在目標Activity的Fragment(ContentFragment)中獲取參數,然後調用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);進行設置返回的數據;最後在ListTitleFragment.onActivityResult()拿到返回的數據進行回顯;
為大家以後在遇到類似問題時,提供了解決方案;也說明了一個問題:fragment能夠從Activity中接收返回結果,但是其自設無法產生返回結果,只有Activity擁有返回結果。
接下來我要貼一下,這兩個Fragment的宿主Activity:
ListTitleActivity

public class ListTitleActivity extends FragmentActivity 
{ 
 
  private ListTitleFragment mListFragment; 
 
  @Override 
  protected void onCreate(Bundle savedInstanceState) 
  { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_single_fragment); 
   
    FragmentManager fm = getSupportFragmentManager(); 
    mListFragment = (ListTitleFragment) fm.findFragmentById(R.id.id_fragment_container); 
     
    if(mListFragment == null ) 
    { 
      mListFragment = new ListTitleFragment(); 
      fm.beginTransaction().add(R.id.id_fragment_container,mListFragment).commit(); 
    } 
 
  } 
} 


ContentActivity:

public class ContentActivity extends FragmentActivity 
{ 
 
  private ContentFragment mContentFragment; 
 
  @Override 
  protected void onCreate(Bundle savedInstanceState) 
  { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_single_fragment); 
   
    FragmentManager fm = getSupportFragmentManager(); 
    mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container); 
     
    if(mContentFragment == null ) 
    { 
      String title = getIntent().getStringExtra(ContentFragment.ARGUMENT); 
      mContentFragment = ContentFragment.newInstance(title); 
      fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit(); 
    } 
 
  } 
} 

有沒有發現兩個Activity中的代碼極其的類似,且使用了同一個布局文件:
activity_single_fragment.xml

<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" 
  android:id="@+id/id_fragment_container" 
> 
 
 
</RelativeLayout> 

為什麼要貼這Acticity的代碼呢?因為我們項目中,如果原則上使用Fragment,會發現大量類似的代碼,那麼我們就可以抽象一個Activity出來,托管我們的Single Fragment。

4、SingleFragmentActivity
於是抽象出來的Activity的代碼為:

package com.example.demo_zhy_23_fragments; 
 
import android.os.Bundle; 
import android.support.v4.app.Fragment; 
import android.support.v4.app.FragmentActivity; 
import android.support.v4.app.FragmentManager; 
 
public abstract class SingleFragmentActivity extends FragmentActivity 
{ 
  protected abstract Fragment createFragment(); 
   
  @Override 
  protected void onCreate(Bundle savedInstanceState) 
  { 
 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_single_fragment); 
   
    FragmentManager fm = getSupportFragmentManager(); 
    Fragment fragment =fm.findFragmentById(R.id.id_fragment_container); 
     
    if(fragment == null ) 
    { 
      fragment = createFragment() ; 
       
      fm.beginTransaction().add(R.id.id_fragment_container,fragment).commit(); 
    } 
  } 
   
} 

那麼,有了這個SingleFragmentActivity,我們的ContentActivity和ListTitleActivity也能大變身了~

package com.example.demo_zhy_23_fragments; 
 
import android.support.v4.app.Fragment; 
 
public class ContentActivity extends SingleFragmentActivity 
{ 
  private ContentFragment mContentFragment; 
 
  @Override 
  protected Fragment createFragment() 
  { 
    String title = getIntent().getStringExtra(ContentFragment.ARGUMENT); 
 
    mContentFragment = ContentFragment.newInstance(title); 
    return mContentFragment; 
  } 
} 

package com.example.demo_zhy_23_fragments; 
 
import android.support.v4.app.Fragment; 
 
public class ListTitleActivity extends SingleFragmentActivity 
{ 
  private ListTitleFragment mListFragment; 
 
  @Override 
  protected Fragment createFragment() 
  { 
    mListFragment = new ListTitleFragment(); 
    return mListFragment; 
  } 
} 

是不是簡潔很多,相信優先使用Fragment的項目,類似的Activity非常多,使用SingleFragmentActivity來簡化你的代碼吧~~
好了,此代碼是來自文章開始推薦的書中的,再次推薦一下~~。

5、FragmentPagerAdapter與FragmentStatePagerAdapter
相信這兩個PagerAdapter的子類,大家都不陌生吧~~自從Fragment問世,使用ViewPager再結合上面任何一個實例的制作APP主頁的案例特別多~~~
那麼這兩個類有何區別呢?
主要區別就在與對於fragment是否銷毀,下面細說:
(1)FragmentPagerAdapter:對於不再需要的fragment,選擇調用detach方法,僅銷毀視圖,並不會銷毀fragment實例。
(2)FragmentStatePagerAdapter:會銷毀不再需要的fragment,當當前事務提交以後,會徹底的將fragmeng從當前Activity的FragmentManager中移除,state標明,銷毀時,會將其onSaveInstanceState(Bundle outState)中的bundle信息保存下來,當用戶切換回來,可以通過該bundle恢復生成新的fragment,也就是說,你可以在onSaveInstanceState(Bundle outState)方法中保存一些數據,在onCreate中進行恢復創建。
如上所說,使用FragmentStatePagerAdapter當然更省內存,但是銷毀新建也是需要時間的。一般情況下,如果你是制作主頁面,就3、4個Tab,那麼可以選擇使用FragmentPagerAdapter,如果你是用於ViewPager展示數量特別多的條目時,那麼建議使用FragmentStatePagerAdapter。
篇幅原因,具體的案例就不寫了,大家自行測試。

6、Fragment間的數據傳遞
上面3中,我們展示了,一般的兩個Fragment間的數據傳遞。
那麼還有一種比較特殊的情況,就是兩個Fragment在同一個Activity中:例如,點擊當前Fragment中按鈕,彈出一個對話框(DialogFragment),在對話框中的操作需要返回給觸發的Fragment中,那麼如何數據傳遞呢?對於對話框的使用推薦:Android 官方推薦 : DialogFragment 創建對話框
我們繼續修改我們的代碼:現在是ListTitleFragment , ContentFragment , 添加一個對話框:EvaluateDialog,用戶點擊ContentFragment 內容時彈出一個評價列表,用戶選擇評價。
現在我們的關注點在於:ContentFragment中如何優雅的拿到EvaluateDialog中返回的評價:
記住我們在一個Activity中,那麼肯定不是使用startActivityForResult;但是我們返回的數據,依然在onActivityResult中進行接收。
好了看代碼:
ContentFragment 

public class ContentFragment extends Fragment 
{ 
 
  private String mArgument; 
  public static final String ARGUMENT = "argument"; 
  public static final String RESPONSE = "response"; 
  public static final String EVALUATE_DIALOG = "evaluate_dialog"; 
  public static final int REQUEST_EVALUATE = 0X110; 
 
  //... 
 
  @Override 
  public View onCreateView(LayoutInflater inflater, ViewGroup container, 
      Bundle savedInstanceState) 
  { 
    Random random = new Random(); 
    TextView tv = new TextView(getActivity()); 
    ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( 
        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 
    tv.setLayoutParams(params); 
    tv.setText(mArgument); 
    tv.setGravity(Gravity.CENTER); 
    tv.setBackgroundColor(Color.argb(random.nextInt(100), 
        random.nextInt(255), random.nextInt(255), random.nextInt(255))); 
    // set click 
    tv.setOnClickListener(new OnClickListener() 
    { 
 
      @Override 
      public void onClick(View v) 
      { 
        EvaluateDialog dialog = new EvaluateDialog(); 
        //注意setTargetFragment 
        dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE); 
        dialog.show(getFragmentManager(), EVALUATE_DIALOG); 
      } 
    }); 
    return tv; 
  } 
 
  //接收返回回來的數據 
  @Override 
  public void onActivityResult(int requestCode, int resultCode, Intent data) 
  { 
    super.onActivityResult(requestCode, resultCode, data); 
 
    if (requestCode == REQUEST_EVALUATE) 
    { 
      String evaluate = data 
          .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE); 
      Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show(); 
      Intent intent = new Intent(); 
      intent.putExtra(RESPONSE, evaluate); 
      getActivity().setResult(Activity.REQUEST_OK, intent); 
    } 
 
  } 
} 

刪除了一些無關代碼,注意看,我們在onCreateView中為textview添加了click事件,用於彈出我們的dialog,注意一行代碼:
dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);
我們調用了Fragment.setTargetFragment ,這個方法,一般就是用於當前fragment由別的fragment啟動,在完成操作後返回數據的,符合我們的需求吧~~~注意,這句很重要。
接下來看EvaluateDialog代碼:

package com.example.demo_zhy_23_fragments; 
 
import android.app.Activity; 
import android.app.AlertDialog; 
import android.app.Dialog; 
import android.content.DialogInterface; 
import android.content.DialogInterface.OnClickListener; 
import android.content.Intent; 
import android.os.Bundle; 
import android.support.v4.app.DialogFragment; 
 
public class EvaluateDialog extends DialogFragment 
{ 
  private String[] mEvaluteVals = new String[] { "GOOD", "BAD", "NORMAL" }; 
  public static final String RESPONSE_EVALUATE = "response_evaluate"; 
 
  @Override 
  public Dialog onCreateDialog(Bundle savedInstanceState) 
  { 
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 
 
    builder.setTitle("Evaluate :").setItems(mEvaluteVals, 
        new OnClickListener() 
        { 
          @Override 
          public void onClick(DialogInterface dialog, int which) 
          { 
            setResult(which); 
          } 
        }); 
    return builder.create(); 
  } 
 
  // 設置返回數據 
  protected void setResult(int which) 
  { 
    // 判斷是否設置了targetFragment 
    if (getTargetFragment() == null) 
      return; 
 
    Intent intent = new Intent(); 
    intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]); 
    getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE, 
        Activity.RESULT_OK, intent); 
 
  } 
} 

重點就是看點擊後的setResult了,我們首先判斷是否設置了targetFragment,如果設置了,意味我們要返回一些數據到targetFragment。
我們創建intent封裝好需要傳遞數據,最後手動調用onActivityResult進行返回數據~~
最後我們在ContentFragment的onActivityResult接收即可。

ok,終於把這些tips貫穿到一起了,到此我們的Fragment的一些建議的用法就結束了~~~那麼,最後提供下源碼,也順便貼個效果圖:

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved