Android Fragment 該怎么用?

這篇文章是講有關于Fragment的使用,大部分還是比較基礎的知識點。之所以寫出來呢,因為我在工作中發現在使用fragment時走了很多彎路,遇見了很多坑。就是因為還有很多細節的東西沒有掌握。在這里分享出來,也能方便自己回顧。

1. fragment概述

fragment是一種控制器對象,activity可以委派一些任務給它,通常這些任務是管理用戶界面。受管的用戶界面可以是整個屏幕或者是小部分屏幕。

fragment與支持庫

Google在API11的時候引入的fragment(為滿足平板UI設計的靈活性要求),最低能兼容到API4。
為了應用能夠兼容舊版本設備,Android提供了開發支持庫,分別是Fragment(android.support.v4.app.Fragment)、FragmentActivity(android.support.v4.app.Fragment-Activity)

關于操作系統內置版的fragment庫

開發者在平時都會優先使用支持庫版的fragment而不是系統內置的版本,是因為Google每年都會更新支持庫,通過此來發布引入新特性,修復Bug,如果想體現這些好處,我們只需要升級項目的支持庫版本就好,Google提供支持庫的本意就是能夠方便開發人員在不支持API的版本是能體驗Fragment。

2. 托管UI fragment

我們都知道fragment是由activity來管理的,在托管fragment的期間,activity必須處理好以下倆件事:

  1. 管理好fragment的生命周期
  2. 在布局中為fragment安排位置

2.1 fragment的生命周期

fragment生命周期 fragment與Activity生命周期的對比

fragment生命周期與activity的生命周期方法非常類似,也具備停止、暫停、運行轉態,也擁有可以覆蓋的方法,在關鍵的節點時候完成一些任務,許多方法也對應了activity的生命周期方法。大部分人都知道fragment的onAttach()、onCreat()、onCreatView()這三個生命周期方法是在Activity的onCreat()的時候執行的??催^源碼應該知道,這三個方法是在onCreate()中的setContentView()中調用的。

生命周期方法在開發中是非常重要的,因為fragment代表activity工作,它的狀態就反應著activity的狀態,顯然,fragment需要相對應的生命周期方法來處理activity的工作。那么fragment與activity的生命周期方法最為關鍵的區別就在于,fragment的生命周期方法是由activity來調用而不是操作系統,操作系統不關心activity用來管理視圖的fragment。

2.2 托管的倆種方式

activity托管fragment有以下倆種方式:

  1. 在activity布局中添加fragment
  2. 在activity通過code添加

第一種方式比較簡單,直接通過activityu的布局綁定fragment,但是不夠靈活,在fragment生命周期過程中,無法切換其他fragment視圖,所以在此我們不多作介紹。

第二種方式:
雖然我們是要在托管的activity中通過代碼添加fragment,但是首先得定義布局容器,也就是為frgament安排位置,布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:id="@+id/fragment_container"
             android:layout_width="match_parent"
             android:layout_height="match_parent">

</FrameLayout>

創建fragment以及實現生命周期方法

package com.haife.album_master.activities;


import android.os.Bundle;
import android.support.v4.app.Fragment;

/**
 * A simple {@link Fragment} subclass.
 */
public class BlankFragment extends Fragment {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
   
}

以上代碼需要注意幾點:

  • Fragment的生命周期方法都是公共方法,與Activity不同,activity生命周期方法是保護方法,前面也說了,這樣是為了讓托管的activity能夠調用
  • 類似于activity,fragment中也有保存和獲取狀態的Bundle。如同activity的onSaveInstance()方法使用相同,我們也可以覆蓋這個方法在fragment中
  • fragment的試圖創建并不是在Fragment.onCreat()方法中生成,雖然我們在方法中配置了fragment 的實例,但創建和配置視圖在另一個生命周期方法onCreatView()中完成的
package com.haife.album_master.activities;


import android.os.Bundle;
import android.support.v4.app.Fragment;

/**
 * A simple {@link Fragment} subclass.
 */
public class BlankFragment extends Fragment {
  
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        TextView textView = new TextView(getActivity());
        textView.setText(R.string.hello_blank_fragment);
        return textView;
    }
}

添加fragment到FragmentManager

FragmentManager類管理的是fragment隊列和fragment的事物回退棧,下圖為FragmentManager圖解:

Paste_Image.png

要以代碼的方式添加fragment到activity中,我們只需要調用activity中的FragmentManager,首先獲取FragmentManager。

 FragmentManager fm = getSupportFragmentManager();

Fragment事務

當我們在activity中獲取到FragmentManager時,我們需要把fragment交由其管理。代碼如下:

public class MainActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {
            fragment = new BlankFragment();
            fm.beginTransaction()
                    .add(R.id.fragment_container, fragment)
                    .commit();
        }
    }
}

add(...)方法是整個事務的核心,除了添加,fragment事務還可被用作移除、附加、分離、替換fragment隊列中的fragment,這個是使用fragment在運行時組裝和重新組裝用戶界面的關鍵,FragmentManager管理著fragment事務的回退棧。

FragmentManager.beginTransaction()方法是創建并返回FragmentTransaction實例,由此可得到一個FragmentTransaction隊列。

現在我們重頭到位總結一下上面的代碼,首先,用過資源Id向FragmenbManager發出請求獲取fragment實例,如果獲取的fragment不為空,那么FragmentManager直接返回它,這里有個問題,很多人會問為什么fragment會存在于隊列中呢?你明明還沒有添加fragment,其實很簡單,我們之前說過,當設備旋轉的時候activity的生命周期會發生變化,在activity銷毀的時候,activity的FragmentManager會將fragment隊列保存起來,當activity執行生命周期的onCreat()方法的時候,FragmentManager會優先回去到保存的Fragment隊列,然后重建重而恢復之前的狀態,這樣你們就知道原因了吧!下面判空的時候如果為空,就直接添加到fragment隊列中0。

抽象的activity類

我們可以看到上面的實現代碼幾乎通用,但是唯一不足的地方就是在添加fragment到FragmentManager的時候實例化的fragment只能是BlankFragment,為了應用的靈活性。所以接下來我們繼續優化。如何處理呢,那我們是不是可以在上面的activity定義一個抽象的方法來當作activity的父類,那么子類就會實現該方法來返回fragment實例。

為了區分,我們創建一個BaseFragmentActivity

public class BaseFragmentActivity extends FragmentActivity {

    protect abstrct Fragment creatFragment();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {
            fragment = creatFragment();
            fm.beginTransaction()
                    .add(R.id.fragment_container, fragment)
                    .commit();
        }
    }
}

MainActivity修改如下

public class MainActivity extends BaseFragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
    }
    
    @Override
    protected Fragment creatFragment(){
        retrun new BlankFragment();
    }
}

現在看起來我們的MainActivity是不是簡練整潔,在實際開發中多使用抽象類會大大的節約你的開發時間,提高效率。

3.使用Fragment argument

在開發中有一個非常普遍你的需求,就是activity與fragmenr之間的值傳遞。
比如,現在我們需求,從一個fragment跳轉到另一新的目標activity中(攜帶參數),然H后目標Fragment要負責接收這個值。實現代碼部分如下:

目標activity

public class TargetActivity extends BaseFragmentActivity {

    public static final String EXTRA_UUID = "com.haife.album_master.activities.targetId"
    
    public static Intent newIntent(Context ctx,String targetId){
        Intent intent = new Intent(ctx,TargetActivity.class);
        intent.putExtra(EXTRA_UUID,targetId);
        return intent;
    }
}

跳轉處

 @Override
public void onClick(){
    Intent intent = TargetActivity.newInstance(getActivity(),id);
    startActivity(intent);
}

到這里,參數id已經被被安全存儲在TargetActivity中,然而獲取參數和使用的是Fragment類。

fragment中獲取extra信息

fragment有倆種方式來獲取intent中的數據,一種比較簡單直接,另一種復雜靈活的方式是涉及到fragment arguement。我們先看簡單的實現方式,在fragment中通過getActivity()方式獲取到TargetActivity的intent,返回至TargetFragment類,得到TargetActivity的intent 的extra信息后,在用它獲取String id的值。

public class TargetFragment extends Fragment {

   public void oncreat(Bundle saveInstanceState){
       String id = getActivity().getIntent().getStringExtra(TargetAtivity.EXTRA_UUID);
   }
}


繼續幾行代碼,我們就能從托管的activity的intent中獲取到信息,然而這種方式會讓我們的TargetFragment無法復用,當前只能被用于TargetActivity,如何優化?我們可以將要獲取的值保存在TargetFragment的某個地方,而不是在TargetAvcivity的私有空間中,這樣,fragment就不用依賴于activity的intent內指定的intent,就能獲取自己所需的extra數據。這“某個地方”其實就是fragment argument。

fragment argument

每個Fragment都存在一個Bundle對象,bundle中含有鍵值對。我們可以類似于附加extra到activity的intent中一樣,一個鍵值對應一個argument。如何附加arguement給fragment呢?我們需要調用Fragment.setArgument(Bundle),注意這個方法必須在fragment創建后調用。andriod開發者通用的做法是在fragment中定一個靜態的newInstance()方法,在此方法內完成fragment的實例和Bundle對象的創建,然后把argument放入bundle中附加給fragment。代碼如下:

package com.haife.album_master.activities;


import android.os.Bundle;
import android.support.v4.app.Fragment;

/**
 * A simple {@link Fragment} subclass.
 */
public class TargetFragment extends Fragment {
   private static final String ARG_TARGET_ID = "arg_target_id";

    public static BlankFragment newInstance(String id){
        Bundle bundle = new Bundle();
        bundle.putString(ARG_TARGET_ID,id);

        BlankFragment blankFragment = new BlankFragment();
        blankFragment.setArguments(bundle);
        return blankFragment;
    }

}

現在,需要創建fragment的時候,activity要調用BlankFragment.newInstance(String)的方法,而不是向上面那樣直接調用構造方法,activity可傳任意參數到newInstance()方法,按實際開發需要,這里傳的是在activity中extra的值:

public class TargetActivity extends BaseFragmentActivity {

    private static final String EXTRA_UUID = "com.haife.album_master.activities.targetId"
    
   @Override
    protected Fragment creatFragment(){
       String id = getIntent().getStringExtra(EXTRA_UUID);
       return TargetFragment.newInstance(id);
    }
}

這里將EXTRA_ID改為私有是因為其他類都用不到了。注意的是,activity和fragment不需要也無法同時保持獨立性,activity必要要了解fragment 的內部細節。

獲取 argument

上面我們已經附加了argument給TargetFragment,接下來我們如何獲取到他的argument?

public class TargetFragment extends Fragment {
   private static final String ARG_TARGET_ID = "arg_target_id";
    private static final String DEFAULT_VALUE = "default_value";
   @Override
   public void onCreate(Bundle saveInstanceState){
         String id = getArguments().getString(ARG_TARGET_ID, DEFAULT_VALUE);
   }

}

代碼寫到這里,雖然比較少,但是很通用。下面是fragment的一點小拓展,如果frament中存在一個列表項(RecycleView),當數據源發生改變時,我們該如何在fragment中刷新它?

 public class BlankFragment extends Fragment {

    @Override
    public void onResume() {
        super.onResume();
        updateUI();
    }

    private void updateUI() {
        if (mAdapter == null) {
           mAdapter = new MyAdapter(userList);
            recycle.setAdapter(mAdapter);
        }else {
            mAdapter.notifyDataSetChanger();
        }
    }


}

解釋一下為什么選擇覆蓋onResum()方法來刷新列表,而不用onStart(),當有其他activbity位于BlankFragment的宿主activity的之前時,我們無法確定宿主activity是否會被停止,如果前面的activity是透明的,那么在onStart()方法中刷新列表是無效,一般來說,要保證fragment視圖得到刷新,在onResum()方法中處理是最安全的。

到這里fragment argument就介紹完了,那為什么要使用它呢?我們為什么不直接在fragment內部創建一個實例變量?想想就知道,當操作系統重建fragment或者用戶離開應用,甚至系統回收內存,又或是應用配置發生改變時,所有的實例變量就不復存在了,所以設計fragment argument的本意就是為了上述場景。當然又有人說你可以用實例狀態保存下來,然后通過onSaveInstanceState(Bundle)方法存儲。這當然也可以,前提是,你回過頭來看代碼的時候能夠記的住~~~

4.通過fragment獲取返回結果

fragment與activity,有Fragment.startActivityForResult(...)和Fragment.onActivityResult(...),用法類似于Activity的同名方法.但是,從fragment中返回結果的處理有些不同,fragment能從activity中接受返回結果,其自身是無法持有返回結果的。盡管Fragment有自己的startActivityForResult(...)和onActivityResult(...),卻沒有setResult(...)方法,相反,我們可以讓activity返回結果值。具體代碼如下:


public class BlankFragment extends Fragment {
    ...

    public void returnResult(){
        getActivity().setResult(Activity.Result_OK,null);
    }

    ...

5. 結尾

最后說點閑話吧,在我們設計應用時,正確的使用fragment是非常重要的,但是我們也不能濫用它。Google設計fragment的本意是封裝可復用的組件,這里的組件是指屏幕的組件,單屏上最多使用2-3個fragment是比較好的。其實在選擇activity還是UI fragment來管理用戶界面的時候,我認為不用考慮太多。能使用fragment實現的時候,就不要去考慮activity了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容