Android知識(shí)點(diǎn)回顧之Fragment

Fragment是在API 11(Android3.0)引入的,為了能夠支持適配寬屏設(shè)備,提供靈活多變的UI設(shè)計(jì)。

Fragment是嵌在Activity里面能夠交互的用戶界面,它的存在必須依賴于Activity,不能獨(dú)立存在。

多個(gè)Activity可以復(fù)用同一個(gè)Fragment。一個(gè)Activity可以嵌入多個(gè)Fragment。

Fragment的生命周期

Fragment的生命周期

Fragment的生命周期如上圖所示。

加入Activity的話則如上圖所示。

如果不加入Activity的生命周期的話,則為:
onAttach()->onCreate()->onCreateView()->onStart()->onResume()->Fragment處于活動(dòng)狀態(tài)
onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()

其中onDestroyView()->onCreateView(),發(fā)生此種情況的條件是當(dāng)Fragment從回退棧(back stack)返回時(shí)才會(huì)發(fā)生。例如:Activity啟動(dòng)了Fragment A,然后又啟動(dòng)了FragmentB替換了Fragment A并把A添加到回退棧,當(dāng)B返回后,退回到A,A的生命周期從onDestroyView()->onCreateView()開始。

幾個(gè)回調(diào)方法

onCreate(),可以在這里進(jìn)行初始化

onCreateView(),開始對(duì)Fragment界面進(jìn)行繪制,需要返回一個(gè)View,通常為Fragment的布局。

onPause(),當(dāng)調(diào)用到此方法時(shí),說明用戶正在離開Fragment,在這里需要對(duì)數(shù)據(jù)和狀態(tài)進(jìn)行保存

啟動(dòng)

宿主BaseActivity切換到后臺(tái)(Activity不可見)

宿主BaseActivity切換回前臺(tái)
宿主BaseActivity銷毀

Fragment的創(chuàng)建

通常的只需要繼承實(shí)現(xiàn)Fragment類,為了保證兼容性,一般使用support包的Fragment。

public class BaseFragment extends Fragment{
  onCreate(){
  
  }
  @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_article_list, container, false);
    }
}

Fragment添加到Activity的兩種方法

1、通過在Activity的布局中聲明fragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

其中<fragment>中的android:name屬性的值為Fragment的類全名。

此時(shí)Activity可直接加載布局中的Fragment

為了給Fragment設(shè)定一個(gè)唯一的標(biāo)識(shí),可以使用如下兩種方式:

  • android:id,指定id屬性為唯一的ID
  • android:tag,指定tag屬性作為唯一的標(biāo)識(shí),為字符串類型

設(shè)置唯一標(biāo)識(shí)可以對(duì)數(shù)據(jù)恢復(fù)有幫助。

2、通過代碼動(dòng)態(tài)添加Fragment

可以通過指定Activity布局中的ViewGroup布局來放置Fragment。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
     ...
    <FrameLayout
        android:id="@+id/fl_fragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
     ...
</LinearLayout>

Activity建議使用擴(kuò)展包中的FragmentActivity的子類

public class BaseActivity extends FragmentActivity{
  ...
  //獲取FragmentTransaction
  FragmentManager fragmentManager = getSupportFragmentManager();
   FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
  ...
  //添加Fragment
  BaseFragment fragment = new BaseFragment();
  fragmentTransaction.add(R.id.fl_fragment, fragment);
  fragmentTransaction.commit();
  ...
}

此外,還可以通過FragmentTransaction的add的重載方法add(Fragment fragment,String tag)添加無界面的Fragment。此時(shí)的tag參數(shù)為Fragment的標(biāo)識(shí)ID,要取唯一值。并且不需要實(shí)現(xiàn)onCreateView()。由于此時(shí)Fragment無界面,所以不會(huì)對(duì)Activity的當(dāng)前界面造成影響。

如果需要獲取到無界面Fragment,可以使用fragmentManager.findFragmentByTag(tag)。

Fragment的管理

可以使用FragmentTransaction對(duì)Fragment進(jìn)行管理,進(jìn)行

  • 添加 add(),
  • 刪除 remove(),
  • 替換 replace()
  • 加入回退棧 addToBackStack()

操作完后必須調(diào)用commit()才會(huì)生效。

一次典型的操作:

Fragment newFragment = new BaseFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
//替換成新的Fragment
transaction.replace(R.id.fragment_container, newFragment);
//把當(dāng)前的FragmentTransaction加入到回退棧
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();

transaction.addToBackStack(null);的作用是把當(dāng)前FragmentTransaction加入到回退棧,當(dāng)執(zhí)行此代碼并commit后,則點(diǎn)擊回退鍵Activity不會(huì)直接結(jié)束,而是返回到Activity界面

有幾個(gè)點(diǎn)需要注意:

  • 一個(gè)FragmentTransaction實(shí)例只能commit一次,當(dāng)重復(fù)commit時(shí)會(huì)直接拋出異常。
  • 當(dāng)調(diào)用addToBackStack();方法時(shí),會(huì)把當(dāng)前FragmentTransaction實(shí)例存入回退棧,可以把Fragment所依賴的Activity看做棧的根。
  • addToBackStack(String name)中的參數(shù)name可以為null,主要作用是標(biāo)識(shí)此FragmentTransaction實(shí)例,用來做區(qū)分。
  • add()和replace()的區(qū)別。add()會(huì)添加新的Fragment實(shí)例進(jìn)去,不會(huì)對(duì)之前的Fragment實(shí)例進(jìn)行銷毀,后面添加的會(huì)覆蓋前面添加的,不過如果add相同的Fragment實(shí)例,則會(huì)拋出異常。replace()會(huì)先把對(duì)應(yīng)布局上所有的Fragment實(shí)例銷毀掉,然后再把新的Fragment實(shí)例添加進(jìn)去。
Fragment的返回棧,當(dāng)前的FragmentTransaction實(shí)例調(diào)用addToBackStack()并commit就會(huì)入棧

返回的話會(huì)從棧頂開始返回,最上層的會(huì)銷毀

Fragment之間,以及和Activity間的數(shù)據(jù)交互

一個(gè)Fragment的實(shí)例是和它所綁定的Activity實(shí)例緊緊關(guān)聯(lián)的。Fragment可以通過getActivity()獲取它所關(guān)聯(lián)綁定的Activity。

和Activity交互

  • Activity可以通過Fragment的構(gòu)造方法把數(shù)據(jù)傳遞給
  • 可通過getActivity()獲取到Activity的實(shí)例
  • 通過回調(diào)接口可以把Fragment的事件結(jié)果傳遞給Activity

Fragment間的交互

  • Fragment間的交互可以通過Activity作中轉(zhuǎn),
  • 可以直接在Fragment通過getFragmentManager()獲取到FragmentManager

官網(wǎng)例子

寬屏布局 res/layout-land/fragment_layout.xml,用于大屏幕設(shè)備,比如平板電腦等:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

手機(jī)布局 res/layout/fragment_layout.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>

TitlesFragment 的實(shí)現(xiàn)

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

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

        // 添加文章列表
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // 檢查是否上圖左邊的那種布局(寬屏模式)
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (mDualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(mCurCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /**
     * 如果是寬屏模式的話,則直接在右邊的Fragment更新內(nèi)容
     * 如果不是寬屏模式,則跳轉(zhuǎn)到新的Activity進(jìn)行展示
     */
    void showDetails(int index) {
        mCurCheckPosition = index;

        if (mDualPane) {
            getListView().setItemChecked(index, true);

            DetailsFragment details = (DetailsFragment)
                    getSupportFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {               
                details = DetailsFragment.newInstance(index);
              
                FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                if (index == 0) {
                    ft.replace(R.id.details, details);
                } else {
                    ft.replace(R.id.a_item, details);
                }
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

DetailsFragment 的實(shí)現(xiàn):

public static class DetailsFragment extends Fragment {
  
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();
       
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed.  Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

如果是手機(jī)的情況,跳轉(zhuǎn)到新的Activity:

public static class DetailsActivity extends FragmentActivity {

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

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容