Fragment的一些坑

創建實例

一般情況下我們創建Fragment可能都是像下面的做法:

OneFragment fragment = new OneFragment();

如果在創建時需要傳遞參數的話就是

OneFragment fragment = new OneFragment(xxx);

這樣的話一般情況下是沒問題的,但是一些特殊情況會出現問題。
比如當屏幕旋轉內存吃緊時,Activity會被回收重新創建Activity,依附在Activity上的Fragment也會跟著重新創建,Fragment會調用默認的無參構造函數,這會導致無法執行有參構造函數進行初始化工作。

解決方法:
newInstance的方法創建Fragment實例,然后在創建時把參數放在Bundle里邊,setArguments()傳進去,然后在onCreate()方法里邊取出來。
這樣即使出現上述的特殊情況,也能在onCreate()方法里把 參數取出來進行初始化工作。

public static OneFragment newInstance(int args){
     OneFragment oneFragment = new OneFragment();

    Bundle bundle = new Bundle();
    bundle.putInt("someArgs", args);

    oneFragment.setArguments(bundle);
    return oneFragment;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Bundle bundle = getArguments();
    int args = bundle.getInt("someArgs");
}

通信方式

通常存在3種通信場景:

  1. Activity 操作內嵌的Fragment
  2. Fragment 操作宿主Activity
  3. Fragment 操作同級的 Fragment

一般的情況下:

  1. 我們在Activity中創建的 Fragment,所以自然會持有Fragment對象實例,或者通過findFragmentById()findFragmentByTag()方法都能獲取到Fragment對象實例,所以可以操作到Fragment
  2. Fragment通過getActivity()方法可以獲取到宿主Activity對象,進而可以操作宿主Activity
  3. 既然通過getActivity()方法就可以獲取到Activity,那自然也就可以操作其他的Fragment對象

問題:
雖然上述方法可以解決所有的通信問題,但會造成代碼邏輯紊亂的情況,不符合高內聚低耦合的編程思想。
Fragment做好自己的事情即可,所有涉及到Fragment之間的控制顯示等操作,應該由Activity統一管理。

解決方法:
通過對外開放接口的形式,將Fragment對Activity的一些操作,由Activity來管理。相當于Fragment操作Activity,但是Fragment只提供一個接口,讓Activity自個自覺的把Fragment需要操作的方法放進去。

實現方式如下:

public class OneFragment extends Fragment implements View.OnClickListener{

    public interface IOneFragmentClickListener{
        void onOneFragmentClick();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View contentView = inflater.inflate(R.layout.fragment_one, null);
        contentView.findViewById(R.id.edt_one).setOnClickListener(this);
        return contentView;
    }

    @Override
    public void onClick(View v) {
        if (getActivity() instanceof IOneFragmentClickListener){
             ((IOneFragmentClickListener) getActivity()).onOneFragmentClick();
        }
    }

}

只要在宿主 Activity 實現 Fragment 定義的對外接口 IOneFragmentClickListener,便可以實現 Fragment 調用 Activity 的功能。

也可以這樣:

public class OneFragment extends Fragment implements View.OnClickListener{
    
    private IOneFragmentClickListener clickListener;

    public interface IOneFragmentClickListener{
        void onOneFragmentClick();
    }

    public void setClickListener(IOneFragmentClickListener clickListener) {
        this.clickListener = clickListener;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View contentView = inflater.inflate(R.layout.fragment_one, null);
        contentView.findViewById(R.id.edt_one).setOnClickListener(this);
        return contentView;
    }

    @Override
    public void onClick(View v) {
        clickListener.onOneFragmentClick();
    }

}

原理是一樣的,只是相比第一種方式,需要在宿主 Activity 中額外添加一步監聽設置:

oneFragment.setClickListener(this);

Fragment重疊問題

原因:

  • 情況1:重復創建實例導致重疊
    一般情況下我們是在ActivityonCreate()FragmentonCreateView()里加載Fragment,如果遇到頁面重啟的情況(比如屏幕旋轉、內存回收、更換字體等情況被強殺重啟),由于系統的保存機制,會自動幫我們保存Fragment的狀態,然后重啟以后幫我們恢復,但是在我們的onCreate()或onCreateView()方法內又重新創建add了一次,所以導致重疊

  • 情況2:沒有保存mHidden狀態導致重疊
    mHidden參數用于保存Fragment顯示狀態(mHidden=ture代表隱藏,mHidden=false代表顯示),比如你有2個Fragment,一個為ture狀態,一個是false狀態,此時遇到界面重啟的情況,系統會自動幫你恢復Fragment,這時由于mHidden狀態沒有保存,所有的Fragment默認mHidden都是false(即顯示狀態),所以會導致重疊
    </br>

情況 2
在v4-24.0.0+ 開始,官方修復了上述 沒有保存mHidden的問題,所以如果你在使用24.0.0+的v4包,即可不考慮情況 2 的問題。
但是! 如果你用的不是V4包的Fragment,而是android.app.Fragment包的Fragment,那么問題依然是存在的!

解決辦法:

  • 情況1:
    直接在Fragment的onCreate()內加個判斷:
//為空說明是首次加載,不是頁面重啟的情況
if (savedInstanceState == null){
    //進行初始化工作
}

</br>

  • 情況2:
    手動維護一個變量mSupportHidden,用于保存每個Fragment自己的顯示狀態,結合情況1、2的解決辦法代碼:
public class BaseFragment extends Fragment {
    private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    if (savedInstanceState != null) {
        boolean isSupportHidden = savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN);

        FragmentTransaction ft = getFragmentManager().beginTransaction();
        if (isSupportHidden) {
            ft.hide(this);
        } else {
            ft.show(this);
        }
        ft.commit();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        ...
        outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden());
    }
}

remove()方法不能出棧

還需實踐踩坑...

getActivity()空指針

還需實踐踩坑...

多個Fragment同時出棧的深坑BUG

還需實踐踩坑...

深坑 Fragment轉場動畫(僅分析v4包下的Fragment)

還需實踐踩坑...

參考
Fragment全解析系列(一):那些年踩過的坑

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

推薦閱讀更多精彩內容