Fragment基本使用


1. Fragment概述

1.1 介紹

Fragment是一種可以嵌入在活動中的UI片段,能夠讓程序更加合理和充分地利用大屏幕的空間,出現的初衷是為了適應大屏幕的平板電腦,可以將其看成一個小型Activity,又稱作Activity片段。

使用Fragment可以把屏幕划分成幾塊,然后進行分組,進行一個模塊化管理。Fragment不能夠單獨使用,需要嵌套在Activity中使用,其生命周期也受到宿主Activity的生命周期的影響

官方定義如下:

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.

從官方的定義可以得到:

  • Fragment依賴於Activity,不能獨立存在
  • 一個Activity可以有多個Fragment
  • 一個Fragment可以被多個Activity重用
  • Fragment有自己的生命周期,並能接收輸入事件
  • 可以在Activity運行時動態地添加或刪除Fragment

Fragment的優勢:

  • 模塊化(Modularity):我們不必把所有代碼全部寫在Activity中,而是把代碼寫在各自的Fragment中。
  • 可重用(Reusability):多個Activity可以重用一個Fragment。
  • 可適配(Adaptability):根據硬件的屏幕尺寸、屏幕方向,能夠方便地實現不同的布局,這樣用戶體驗更好。

1.2 Fragment基本生命周期

image

Fragment的一般生命周期如上圖所示:

  • onAttach():Fragment和Activity相關聯時調用。可以通過該方法獲取Activity引用,還可以通過getArguments()獲取參數。
  • onCreate():Fragment被創建時調用
  • onActivityCreated():當Activity完成onCreate()時調用
  • onStart():當Fragment可見時調用。
  • onResume():當Fragment可見且可交互時調用
  • onPause():當Fragment不可交互但可見時調用。
  • onStop():當Fragment不可見時調用。
  • onDestroyView():當Fragment的UI從視圖結構中移除時調用。
  • onDestroy():銷毀Fragment時調用。
  • onDetach():當Fragment和Activity解除關聯時調用。

Fragment生命周期會經歷:運行、暫停、停止、銷毀。

  • 運行狀態:碎片可見時,關聯活動處於運行狀態,其也為運行狀態
  • 暫停狀態:活動進入暫停狀態,相關聯可見碎片就會進入暫停狀態
  • 停止狀態:活動進入停止狀態,相關聯碎片就會進入停止狀態,或者通過FragmentTransaction的remove()、replace()方法將碎片從從活動中移除,但如果在事務提交之前調用addToBackStack()方法,這時的碎片也會進入到停止狀態。
  • 銷毀狀態:當活動被銷毀,相關聯碎片進入銷毀狀態。或者調用FragmentTransaction的remove()、replace()方法將碎片從活動中移除,但在事務提交之前並沒有調用addToBackStack()方法,碎片也會進入到銷毀狀態。

在介紹Fragment的具體使用時,先介紹一下Fragment的幾個核心類

  • Fragment:Fragment的基類,任何創建的Fragment都需要繼承該類
  • FragmentManager:管理和維護Fragment。他是抽象類,具體的實現類是FragmentManagerImpl。
  • FragmentTransaction:對Fragment的添加、刪除等操作都需要通過事務方式進行。他是抽象類,具體的實現類是BackStackRecord

擴展子類:

  • 對話框:DialogFragment
  • 列表:ListFragment
  • 選項設置:PreferenceFragment
  • WebView界面:WebViewFragment

備注:開發Fragment不建議使用android.app下的Fragment而應是android:support.v4.app,因為support庫是不斷更新的。

2. Fragment使用

使用Fragment有兩種方式,分別是靜態加載和動態加載

2.1 靜態加載

關於靜態加載的流程如下:

  • 定義Fragment的xml布局文件
  • 自定義Fragment類,繼承Fragment類或其子類,同時實現onCreate()方法,在方法中,通過inflater.inflate加載布局文件,接着返回其View
  • 在需要加載Fragment的Activity對應布局文件中 的name屬性設為全限定類名,即包名.fragment
  • 最后在Activity調用setContentView()加載布局文件即可

靜態加載一旦添加就不能在運行時刪除

示例:

  • 定義Fragment布局,新建left_fragment.xml和right_fragment.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Button" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#00ff00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="this is Fragment" />

</LinearLayout>
  • 自定義Fragment類,繼承Fragment或其子類,重寫onCreateView(),在方法中調用inflater.inflate()方法加載Fragment布局文件,接着返回加載的view對象
public class LeftFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.left_fragment, container,false);
        return view;
    }

}
public class RigthFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.right_fragment, container, false);
        return view;
    }
}
  • 在需要加載Fragment的Activity對應的布局文件中添加Fragment標簽
<fragment
    android:id="@+id/left_fragment"
    android:name="com.vivo.a11085273.secondfragmenttest.LeftFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1" />
<fragment
    android:id="@+id/right_fragment"
    android:name="com.vivo.a11085273.secondfragmenttest.RigthFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    />
  • 在Activity的onCreate()方法中調用setContentView()加載布局文件即可

2.2 動態加載Fragment

動態加載Fragment的流程如下:

  • 獲得FragmentManager對象,通過getSupportFragmentManager()
  • 獲得FragmentTransaction對象,通過fm.beginTransaction()
  • 調用add()方法或者repalce()方法加載Fragment;
  • 最后調用commit()方法提交事務

簡單示例:

  • 同靜態加載一樣,首先定義Fragment的布局和類,修改主布局文件,不指定 標簽的name屬性。
  • 實現Fragment調用
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
        replaceFragment(new RigthFragment());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                replaceFragment(new AnotherRightFragment());
                break;
            default:
                break;
        }
    }

    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();   // 開啟一個事務
        transaction.replace(R.id.right_layout, fragment);
        transaction.commit();
    }
}

2.3 使用注意點

  • Fragment的onCreateView()方法返回Fragment的UI布局,需要注意的是inflate()的第三個參數是false,因為在Fragment內部實現中,會把該布局添加到container中,如果設為true,那么就會重復做兩次添加,則會拋如下異常:

Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.

  • 如果在創建Fragment時要傳入參數,必須要通過setArguments(Bundle bundle)方式添加,而不建議通過為Fragment添加帶參數的構造函數,因為通過setArguments()方式添加,在由於內存緊張導致Fragment被系統殺掉並恢復(re-instantiate)時能保留這些數據

可以在Fragment的onAttach()中通過getArguments()獲得傳進來的參數。如果要獲取Activity對象,不建議調用getActivity(),而是在onAttach()中將Context對象強轉為Activity對象

示例:

public class Fragment1 extends Fragment{
    private static String ARG_PARAM = "param_key";
    private String mParam;
    private Activity mActivity;
    public void onAttach(Context context) {
        mActivity = (Activity) context;
        mParam = getArguments().getString(ARG_PARAM);  //獲取參數
    }
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_1, container, false);
        TextView view = root.findViewById(R.id.text);
        view.setText(mParam);
        return root;
    }
    public static Fragment1 newInstance(String str) {
        Fragment1 frag = new Fragment1();
        Bundle bundle = new Bundle();
        bundle.putString(ARG_PARAM, str);
        fragment.setArguments(bundle);   //設置參數
        return fragment;
    }
}
  • 動態加載Fragment中,FragmentTransaction類提供了方法完成增刪等操作,完成后調用FragmentTransaction.commit()方法提交修改

    • transaction.add():往Activity里面添加一個片段
    • transaction.remove():從Activity中移除一個Fragment,如果被移除的Fragment沒有添加到回退棧,這個Fragment實例將會被銷毀
    • transaction.replace():使用另一個Fragment替換當前的,實際上是remove()然后add()的合體
    • transaction.hide():隱藏當前Fragment,僅不可見,不會銷毀
    • transaction.show():顯示之前隱藏的Fragment
    • detach():會將view從UI中移除,和remove()不同,此時fragment的狀態依然由FragmentManager維護
    • attach():重建view視圖,附加到UI上並顯示。

commit方法一定要在Activity.onSaveInstance()之前調用

commit()操作是異步的,內部通過mManager.enqueueAction()加入處理隊列。對應的同步方法為commitNow(),commit()內部會有checkStateLoss()操作,如果開發人員使用不當(比如commit()操作在onSaveInstanceState()之后),可能會拋出異常,而commitAllowingStateLoss()方法則是不會拋出異常版本的commit()方法,但是盡量使用commit(),而不要使用commitAllowingStateLoss()。

  • FragmentManager擁有回退棧(BackStack),類似於Activity的任務棧,如果添加了該語句,就把該事務加入回退棧,當用戶點擊返回按鈕,會回退該事務(回退指的是如果事務是add(frag1),那么回退操作就是remove(frag1));如果沒添加該語句,用戶點擊返回按鈕會直接銷毀Activity。

  • Fragment常見異常

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)

at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)

at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)

at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

出現原因:commit()在onSaveInstanceState()后調用。

由於onSaveInstanceState()在onPause()之后,onStop()之前調用。onRestoreInstanceState()在onStart()之后,onResume()之前,因此避免出現該異常的方案有:

  • 不要把Fragment事務放在異步線程的回調中
  • 逼不得已時使用commitAllowingStateLoss()

3. Fragment與Activity聯動

image

Fragment和Activity完整的生命周期如上圖所示

  1. 當Fragment在Activity的onCreate()中被添加時
  • Activity super.onCreate執行完畢
  • Fragment onAttach
  • Fragment onCreate
  • Fragment onCreateView
  • Fragment onViewCreated
  • Activity.super.onStart()執行中
  • Fragment onActivityCreated
  • Fragment onViewStateRestored
  • Fragment onStart()
  • Activity super.onStart執行完畢
  • Activity super.onPostCreate()
  • Activity super.onResume()
  • Activity super.onPostResume()執行中
  • Fragment onResume()
  • Activity super.onPosResume()執行完畢
  • Activity onAttachedToWindow()
  • Activity onCreateOptionsMenu()
  • Fragment onCreateOptionsMenu()
  • Activity onPrepareOptionsMenu()
  • Fragment onPrepareOptionsMenu()
  • Activity onWindowFocusChanged()
  1. 暫停生命周期
  • Activity super.onPause()執行中
  • Fragment.onPause()
  • Activity super.onPause()執行完畢
  • Activity super.onSaveInstanceState()執行中
  • Fragment onSaveInstanceState()
  • Activity super.onSaveInstanceState()執行完畢
  • Activity super.onStop()執行中
  • Fragment onStop()
  • Activity super.onStop()執行完畢
  1. 銷毀的生命周期
  • Activity super.onDestroy()執行中
  • Fragment onDestroyView()
  • Fragment onDestroy()
  • Fragment onDetach()
  • Activity super.onDestroy()執行完畢
  1. 重啟的生命周期
  • Activity super.onRestart()
  • Activity super.onStart()執行中
  • Fragment onStart()
  • Activity super.onStart()執行完畢
  • Activity super.onResume()
  • Activity super.onPostResume()執行中
  • Fragment onResume()
  • Activity super.onPosResume()執行完畢
  • Activity onWindowFocusChanged()執行完畢

3.1 回退棧

類似Android系統為Activity維護一個任務棧,我們也可以通過Activity維護一個回退棧來保存每次Fragment事務發生的變化。如果你將Fragment任務添加到回退棧,當用戶點擊后退按鈕時,將看到上一次的保存的Fragment。

一旦Fragment完全從后退棧中彈出,用戶再次點擊后退鍵,則退出當前Activity

添加一個Fragment事務到回退棧:

FragmentTransaction.addToBackStack(String)

簡單示例:

private void replaceFragment(Fragment fragment) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.replace(R.id.right_layout, fragment);
    transaction.addToBackStack(null);   //添加進回退棧
    transaction.commit();
}

replace是remove和add的合體,並且如果不添加事務到回退棧,前一個Fragment實例會被銷毀。這里很明顯,我們調用transaction.addToBackStack(null);將當前的事務添加到了回退棧,所以FragmentOne實例不會被銷毀,但是視圖層次依然會被銷毀,即會調用onDestoryView和onCreateView

如果不希望視圖重繪,可以將原來的Fragment隱藏:

private void replaceFragment(Fragment fragment) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.hide(this);
    transaction.add(R.id.right_layout, fragment);
    transaction.addToBackStack(null);   //添加進回退棧
    transaction.commit();
}

4. Fragment與Activity通信

image

Fragment與Activity的通信交互如上圖所示:

  • 如果Activity中包含自己管理的Fragment的引用,可以通過引用直接訪問所有的Fragment的public方法
  • 如果Activity中未保存任何Fragment的引用,那么沒關系,每個Fragment都有一個唯一的TAG或者ID,可以通過getFragmentManager.findFragmentByTag()或者findFragmentById()獲得任何Fragment實例,然后進行操作
  • 在Fragment中可以通過getActivity得到當前綁定的Activity的實例,然后進行操作。

備注:

  • 如果在Fragment中需要Context,可以通過getActivity(),如果該Context需要在Activity被銷毀后還存在,則使用getActivity.getApplicationContext();

考慮Fragment的重復使用問題,降低與Activity的耦合,Fragment操作應該由它的管理者Activity決定。

4.1 傳遞數據給Fragment

步驟流程:

  • 在Activity中創建Bundle數據包,調用Fragment實例的setArguments(),將Bundle數據包傳給Fragment
  • Fragment調用getArguments()獲得Bundle對象,然后進行解析就可以

簡單示例:

//創建Fragment對象,並通過Bundle對象傳遞值(在onCreate方法中)
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString("key", values);
fragment.setArguments(bundle);
//(在Fragment類中的onCreateView方法中)
Bundle bundle = this.getArguments();
    if (bundle != null)
    {
        String str = bundle.getString("key");
    }
    TextView textView = new TextView(getActivity());
    textView.setText("上上下下的享受");//是電梯,別誤會

4.2 傳遞數據給Activity

步驟流程:

  • 在Fragment中定義一個內部回調接口,再讓包含該Fragment的Activity實現該回調接口
  • Fragment通過回調接口傳數據

簡單示例:

  • 首先在Fragment中定義一個接口(定義抽象方法,傳什么類型參數)
/*接口*/  
public interface Mylistener{
    public void thanks(String code);
}
  • Fragment類中定義該接口
private Mylistener listener;

  • 在onAttach方法中,將定義的該接口強轉為activity類型
@Override
    public void onAttach(Activity activity) {
        // TODO Auto-generated method stub
        listener=(Mylistener) activity;
        super.onAttach(activity);
    }
  • Activity只需實現該接口並重寫該方法即可
@Override
    public void thanks(String code) {
        // TODO Auto-generated method stub
        Toast.makeText(this, "已收到Fragment的消息:--"+code+"--,客氣了", Toast.LENGTH_SHORT).show();;
    }

除了接口回調,還可以使用EventBus進行交互通信。

5. Fragment間通信

5.1 setArguments()

示例:

  • 在Fragment B中新建一個函數:newInstance()接收傳過來的參數
public static Fragment2 newInstance(String text) {
        Fragment2 fragment = new Fragment2();
        Bundle args = new Bundle();
        args.putString("param", text);
        fragment.setArguments(args);
        return fragment;
    }
  • 在Fragment B的onCreateView中獲取參數
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view =  inflater.inflate(R.layout.fragment2, container, false);
        if (getArguments() != null) {
            String mParam1 = getArguments().getString("param");
            TextView tv =  (TextView)view.findViewById(R.id.textview);
            tv.setText(mParam1);
        }
        return view;
    }
  • 在Fragment A中,調用Fragment B時,通過newInstance函數獲取實例並傳遞參數:
public class Fragment1 extends Fragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);
        Button btn = (Button)view.findViewById(R.id.load_fragment2_btn);
        btn.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(final View view) {
                Fragment2 fragment2 = Fragment2.newInstance("從Fragment1傳來的參數");
 
                FragmentTransaction transaction = getFragmentManager().beginTransaction();
                transaction.add(R.id.main_layout, fragment2);
                transaction.addToBackStack(null);
                transaction.commit();
            }
        });
        return view;
    }
}

5.2 同Activity不同Container的Fragment交互

這種情況有三中方法解決:

方法一:直接在Activity中操作

​ 直接在Activity中找到對應控件的實例,然后直接操控即可

方法二:直接在Fragment中操作

​ 這里有兩個問題:如何獲取自己控件的引用?如何獲取其他Fragment頁控件的引用?

  • 首先獲取自己控件的引用

可以在onCreateView()中獲取

public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment1, container, false);
    listView = (ListView)rootView.findViewById(R.id.list);//獲取自己視圖里的控件引用,方法一
    return rootView;
}

在onCreateView()中,還沒有創建視圖,所以在這里如果使用getView()方法將返回空

另一種方法是在onActivityCreated()中獲取,其回調在onCreate()執行后再執行

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
 
    listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二
}
  • 獲取其它Fragment頁控件引用方法

獲取Activity資源,須等Activity創建完成后,必須放在onActivityCreated()回調函數中

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
 
    mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//獲取其它fragment中的控件引用的唯一方法!!!
 
}

總的實現示例如下:

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
 
    mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//獲取其它fragment中的控件引用的唯一方法!!!
    listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二
 
    ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings);
    listView.setAdapter(arrayAdapter);
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String str = mStrings[position];
            mFragment2_tv.setText(str);
       }
    });
}

方法三:在各自Fragment中操作

方法二在Fragment A中操作了Fragment B,違背模塊分離思想,應通過Activity將其分離

在Activity中可以直接通過FragmentManager.findFragmentById()獲取Fragment實例

示例:

在Fragment2設置TextView函數

public class Fragment2 extends Fragment {
    private TextView mTv;
    …………
    public void setText(String text) {
        mTv.setText(text);
    }
}

在Fragment1 中定義處理方式

  • 定義接口與變量
private titleSelectInterface mSelectInterface;
 
public interface titleSelectInterface{
    public void onTitleSelect(String title);
}
  • 接口變量賦值

接口給Activity使用,在Activity中給接口變量賦值,在Fragment與Activity關聯時,需要強轉

public void onAttach(Activity activity) {
    super.onAttach(activity);
 
    try {
        mSelectInterface = (titleSelectInterface) activity;
    } catch (Exception e) {
        throw new ClassCastException(activity.toString() + "must implement OnArticleSelectedListener");
    }
}
  • 調用接口變量
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
 
    listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二
    ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings);
    listView.setAdapter(arrayAdapter);
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String str = mStrings[position];
            mSelectInterface.onTitleSelect(str);
        }
    });
}
  • 在Activity中實現接口
public class MainActivity extends FragmentActivity implements Fragment1.titleSelectInterface {
 
    ……
    
    @Override
    public void onTitleSelect(String title) {
        FragmentManager manager = getSupportFragmentManager();
        Fragment2 fragment2 = (Fragment2)manager.findFragmentById(R.id.fragment2);
        fragment2.setText(title);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM