Fragment-踩坑

背景

Fragment已經成爲Android開發界面設計中不可或缺的一部分,同時也發揮着愈來愈重要的角色,雖然Fragment已經能出色的項目開發,可是在使用過程當中也暴露了愈來愈多的問題,雖然google也一直在及時的修復,可是仍是有不少坑,因此決定記錄Fragment使用過程當中的使用問題,避免小夥伴們重複踩坑。java

在瞭解踩坑以前,咱們須要先了解Fragment的使用要點和使用方法android


Fragment介紹

做爲 view 界面的一部分,Fragment 的存在必須依附於 FragmentActivit使用,而且與 FragmentActivit 同樣,擁有本身的獨立的生命週期,同時處理用戶的交互動做。同一個 FragmentActivit 能夠有一個或多個 Fragment 做爲界面內容,一樣Fragment也能夠擁有多個子Fragment,而且能夠動態添加、刪除 Fragment,讓UI的重複利用率和易修改性得以提高,一樣能夠用來解決部分屏幕適配問題。git

另外一方面,support v4 包中也提供了 Fragment,兼容 Android 3.0 以前的系統,使用兼容包須要注意兩點:github

  • 宿主Activity 必須繼承自 FragmentActivity網絡

  • 使用getSupportFragmentManager() 方法獲取 FragmentManager 對象;異步


生命週期

Fragment一樣是具有了獨立的生命週期,可是和Activity的生命週期還有不同的地方,如圖:原圖地址ide

Fragment初始化

Fragment默認有兩種初始化的方法,一種new另外一種是嵌入xml動畫

  • newthis

    FirstFragment firstFragment=new FirstFragment();複製代碼
  • xmlgoogle

    <fragment
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          class="com.wzgiceman.fragmentpit.Fragment.FirstFragment"/>複製代碼

上面兩種方法均可以初始獲得一個Fragment對象,可是前者比後者的有點在於,前者更加的靈活,因此推薦使用第一種方式。


ActivityFragment傳參

默認建立Fragment系統已經給咱們初始了傳參的代碼

/** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment FirstFragment. */
    // TODO: Rename and change types and number of parameters
    public static FirstFragment newInstance(String param1, String param2) {
        FirstFragment fragment = new FirstFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }複製代碼

這無疑是最好的選擇


回調

Fragment 類提供有startActivityForResult()方法用於 Activity 間的頁面跳轉和數據回傳,其實內部也是調用 Activity 的對應方法。可是在頁面返回時須要注意 Fragment 沒有提供 setResult() 方法,能夠經過宿主 Activity 實現。


FragmentManagerFragmentTransaction使用

FragmentManager

Activity中使用Fragment可使用getSupportFragmentManager獲取一個FragmentManager對象,可是在Fragment中顯示子Fragment須要調用FragmentgetChildFragmentManager()

源碼以下:

public final FragmentManager getChildFragmentManager() {
        throw new RuntimeException("Stub!");
    }複製代碼

FragmentTransaction

Fragment 的動態添加、刪除等操做都須要藉助於 FragmentTransaction 類來完成,好比上面提到的 commit() 操做,下面是幾種經常使用的方法:

  • add() 系列:添加 Fragment 到 Activity 界面中;

  • remove():移除 Activity 中的指定 Fragment;

  • replace() 系列:經過內部調用 remove() 和 add() 完成 Fragment 的修改;

  • hide() 和 show():隱藏和顯示 Activity 中的 Fragment;

  • addToBackStack():添加當前事務到回退棧中,即當按下返回鍵時,界面迴歸到當前事物狀態;

  • commit():提交事務,全部經過上述方法對 Fragment 的改動都必須經過調用 commit() 方法完成提交

replace()hide()區別

replace()hide()均可以動態的在Activity中顯示多個Fragment,而且能夠來回靈活的切換,可是它們有很大的區別,replace() 方法不會保留 Fragment 的狀態,也就是說諸如 EditText 內容輸入等用戶操做在 remove() 時會消失;可是hide()卻不會,能完整的保留用戶的處理信息。

addToBackStack()退棧
當用戶按下返回鍵時,若是回退棧中保存有以前的事務,會先執行事務回退,而後再執行Activityfinish()方法 。

簡單使用

經過FragmentManagerFragmentTransaction結合使用,咱們能夠將第一種初始化的Fragment動態的顯示到界面中,這裏使用replace()演示:

FragmentManager fm = getSupportFragmentManager();
 FragmentTransaction ft = fm.beginTransaction();
 ft.replace(R.id.fl_fragment, FirstFragment.newInstance("A","B"));
 ft.commit();複製代碼

踩坑

在瞭解了Fragment的基礎使用後,能夠開始使用過程當中的踩坑了

getActivity() 引用問題

Fragment中經常須要使用到content對象,好比網絡加載如今一個progress等等,這時候可能你遇到過getActivity()返回null,或者平時運行無缺的代碼,在「內存重啓」以後,調用getActivity()的地方卻返回null,報了空指針異常。

大多數狀況下的緣由:你在調用了getActivity()時,當前的Fragment已經onDetach()了宿主Activity
好比:你在popFragment以後,該Fragment的異步任務仍然在執行,而且在執行完成後調用了getActivity()方法,這樣就會空指針;

解決辦法

  • getContext()替代getActivity()

  • 定義全局變量,在FragmentonAttach(Activity activity)準備廢棄或者onAttach(Context context)方法中初始化

    Context context;
      @Override
      public void onAttach(Context context) {
          super.onAttach(context);
          this.context=context;
      }複製代碼

    顯然第一種方法更加靈活方便了。


高耦合

當子Fragment須要調用宿主Acitivity的方法時,好比子Fragment須要發送一個廣播,可是Fragment沒有改方法,因此須要藉助宿主Activity去發送,這時候經常須要強制轉換content對象,而後調用宿主Acitivity發方發送廣播,這種直接使用的方式違背了高聚低耦的設計原則;

解決辦法

經過接口抽象的方法,經過接口去調用宿主Activity的方法。

  • 定義接口
/** * 發送廣播 * Created by WZG on 2016/12/31. */

public interface SendBListener {
    void send();
}複製代碼
  • 實現接口
public class FirstFragment extends Fragment {
    SendBListener listener;

    public void setListener(SendBListener listener) {
        this.listener = listener;
    }

    @OnClick(value = R.id.tv)
    void onTvClick(View view) {
        listener.send();
    }
   }複製代碼
  • 調用
public class MainActivity extends AppCompatActivity implements SendBListener{
    @BindView(R.id.fl_fragment)
    FrameLayout mFlFragment;

    @Override
    public void send() {
        sendBroadcast(new Intent("xxxxxx"));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FirstFragment firstFragment=new FirstFragment();
        firstFragment.setListener(this);
      }
 }複製代碼

重疊

因爲採用建立對象的方式去初始化Fragment對象,當宿主Activity在界面銷燬或者界面從新執行onCreate()方法時,就有可能再一次的執行Fragment的建立初始,而以前已經存在的 Fragment 實例也會銷燬再次建立,這不就與 Activity 中 onCreate() 方法裏面第二次建立的 Fragment 同時顯示從而發生 UI 重疊的問題。

若是宿主界面Acitivity能夠橫豎屏切換,致使的生命週期從新刷新也同理可致使界面的重疊問題。

解決辦法

  • 推薦:利用savedInstanceState判斷
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        FirstFragment firstFragment;
        if (savedInstanceState==null) {
            firstFragment=new FirstFragment();
            ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
        }else {
            firstFragment = (FirstFragment) fm.findFragmentByTag("FirstFragment");
        }

    }複製代碼
  • Activity 提供的 onAttachFragment() 方法中處理
@Override
    public void onAttachFragment(Fragment fragment) {
        super.onAttachFragment(fragment);
        if (fragment instanceof  FirstFragment){
            firstFragment = (FirstFragment) fragment;
        }
   }複製代碼
  • 建立Fragment時判斷
Fragment fragment = getSupportFragmentManager().findFragmentByTag("FirstFragment");
        if (fragment==null) {
            firstFragment =new FirstFragment();
            ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
        }else {
            firstFragment = (FirstFragment) fragment;
        }複製代碼

Fragment轉場動畫

若是你想給下一個Fragment設置進棧動畫和出棧動畫,setCustomAnimations(enter, exit)只能設置進棧動畫,第二個參數並非設置出棧動畫;
請使用setCustomAnimations(enter, exit, popEnter, popExit),這個方法的第1個參數對應進棧動畫,第4個參數對應出棧動畫,因此是setCustomAnimations(進, exit, popEnter, 出))


Fragment狀態監聽

不少時候,咱們須要在多Fragment中刷新界面,固然因爲Fragment有本身獨立的生命週期可是也依賴宿主Activity存在,因此在刷新界面的時候須要注意如:

當宿主Activity A進入B中,又衝B返回到A,這時候宿主A執行onResume()方法,固然這時候的Fragment也會執行onResume()

當宿主Activity A中的Fragment所有初始完成顯示過,在切換Fragment的時候不會再一次觸發onResume()方法,可是卻能夠觸發Fragment的onHiddenChanged(boolean hidden)方法

因此當咱們須要實時刷新Fragment界面的時候,須要同時結合onResume()onHiddenChanged(boolean hidden)方法去刷新當前顯示Fragment而避免刷新hide()Fragment

使用

Override public void onResume() {
        super.onResume();
        //當前是不是現實狀態
        if (isVisible()){
            //刷新界面
            updateUI();
        }
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        //方法重複發起刷新界面
        if (isVisible() && isResumed()){
            updateUI();
        }
    }複製代碼

交流

QQ交流羣,談談夢想,聊聊人生!

相關文章
相關標籤/搜索