Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android必備知識---掌握Fragment(一)

Android必備知識---掌握Fragment(一)

編輯:關於Android編程

Fragment是Android API中的一個類,它代表Activity中的一部分界面;你可以在一個Activity界面中使用多個Fragment,或者在多個Activity中重用某一個Fragment。本文將會介紹Fragment的基本使用、Fragment參數傳遞、與宿主Activity交互、以及Fragment與Toolbar繼承。


基本使用

靜態使用Fragment

第一、編寫類ExampleFragment繼承自Fragment

public class ExampleFragment extends Fragment
{
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

onCreateView()為Fragment生命周期中的一個方法,當第一次在Fragment上繪制UI時,系統回調這個方法。

下圖為Fragment生命周期

這裡寫圖片描述

第二、R.layout.example_fragment為ExampleFragment需要繪制UI的布局文件,這裡很簡單,是一個TextView。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">

第三、在Activity的布局文件中使用這個ExampleFragment



    

有個需要注意的地方:每個fragment都需要一個唯一的標識。系統在資源緊缺或者橫屏、豎屏切換都會發生宿主Activity重構,那麼在重構的過程中會去恢復宿主Activity所管理的Fragment隊列,這個過程需要用到每個Fragment的唯一標識。

有三種方法為fragment設置唯一標識:

通過android:id屬性為fragment指定唯一ID 通過android:tag屬性為fragment指定唯一字符串標識 若上述兩種都未指定,則該fragment的標識為其父容器控件的ID

OK, 當系統加載Activiy的Layout視圖時,同時加載Fragment綁定的視圖,並回調Fragment的onCreateView()方法,系統將fragment標簽替換為onCreateView()方法返回的view。


動態添加Fragment

首先,Activity的Layout文件修改如下:



這裡給LinearLayout添加了一個ID,用作Fragment的容器ID。

下面是Activity動態添加Fragment代碼

public class MainActivity extends AppCompatActivity
{

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

        FragmentManager fm = getFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        ExampleFragment fragment = new ExampleFragment();
        ft.add(R.id.fragment_container, fragment);
        ft.commit();
    }
}

FragmentManager:

為了在Activity中管理Fragment,我們需要FragmentManager實例,通過getFragmentManager()方法獲取。可以通過FragmentManager完成如下操作:

調用findFragmentById()方法獲取由Activity管轄的綁定了UI的Fragment實例;調用findFragmentByTag()方法獲取由Activity管轄的未綁定UI的Fragment實例; 調用popBackStack()方法將Fragment從後退棧中彈出; 調用addOnBackStackChangedListener()注冊監聽器,用於監聽後退棧的變化;

FragmentTransaction:

Fragment最大的好處就是可以動態的添加, 刪除, 替換等操作。每一組向Activity提交的變化稱為事務,也就是FragmentTransaction對應的一些API。

FragmentTransaction常用API有:add(), remove(), replace()(就是先remove再add),最後為了使事務在Activity生效,需調用commit()方法。

在調用commit()方法之前,可以調用addToBackStack() 方法將事務添加到宿主Activity管轄Fragment後退棧中。這樣用戶通過點擊後退鍵對Fragment進行導航。比如,使用remove()方法移除了Fragment,如果沒有執行addToBackStack()方法,那麼這個Fragment實例就會被銷毀,用戶無法通過後退鍵導航到這個Fragment。

回退棧舉例如下:

 //創建一個新的Fragment,並開啟事務
ExampleFragment fragment = new ExampleFragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();

//將容器中替換成當前fragment
//並把當前事務添加到回退棧
ft.replace(R.id.fragment_container, fragment);
ft.addToBackStack(null);

//提交事務
ft.commit();

防止Activity重構導致多個Fragment重疊:

Activity重構時,系統會自動重建該Activity所管轄的Fragment。如果按照上面的代碼,當Activity發生重構時,多個Fragment就會發生重疊。

需要對代碼做如下改進:

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

    FragmentManager fm = getFragmentManager();
    //沒有為Fragment明確指明ID或者Tag,系統使用fragment容器的LayoutID來唯一標識Fragment
    ExampleFragment fragment = (ExampleFragment) fm.findFragmentById(R.id.fragment_container);
    //非空判斷,防止出現多個Fragment重疊
    if(fragment == null) {
        FragmentTransaction ft = fm.beginTransaction();
        fragment = new ExampleFragment();
        ft.add(R.id.fragment_container, fragment);
        ft.commit();
    }
}

Fragment傳遞參數

在啟動Activity的時候,通過Intent可以給Activity傳遞參數。啟動Fragment,如何傳遞參數呢?

通過Fragment.setArguments(Bundle)設置參數,通過Bundle bundle = getArguments()獲取參數;

下面看代碼:

public class ExampleFragment extends Fragment
{

    public static final String KEY = "ARGUMENT";

    private String mArg;

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

        //獲取參數
        Bundle bundle = getArguments();
        if(bundle != null) {
            mArg = bundle.getString(KEY);
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View contentView = inflater.inflate(R.layout.example_fragment, container, false);
        TextView tv = (TextView) contentView.findViewById(R.id.tv);
        tv.setText(mArg);
        return contentView;
    }

    public static ExampleFragment createFragment(String argument)
    {
        ExampleFragment fragment = new ExampleFragment();
        Bundle args = new Bundle();
        args.putString(KEY, argument);
        //為fragment傳遞參數
        fragment.setArguments(args);
        return fragment;
    }
}

靜態方法createFragment(String argument)用來在外部創建Fragment實例,在方法中首先創建了一個Bundle對象,將需要傳遞的參數存儲到Bundle對象中,最後通過fragment.setArguments()方法將Bundle對象傳遞給Fragment。

在ExampleFragment 的onCreate()回調函數中使用getArguments()獲取Bundle對象,之後通過Bundle對象獲取參數內容。

下面是Activity中的代碼:

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

    FragmentManager fm = getFragmentManager();
    ExampleFragment fragment = (ExampleFragment) fm.findFragmentById(R.id.fragment_container);
    if(fragment == null) {
        FragmentTransaction ft = fm.beginTransaction();
        //給Fragment傳遞參數
        ft.add(R.id.fragment_container, ExampleFragment.createFragment("argument"));
        ft.commit();
    }
}

注意:setArguments()必須在添加給Activity之前完成。也就是說必須在ft.add()方法之前調用Fragment的setArguments()方法。


Fragment回傳數據

向上一個Activity回傳數據通過startActivityForResult(intent, requestCode)方法,以及onActivityResult()回調實現。向上一個Fragment回傳數據也是通過類似的方法實現。

新的場景:

依然新聞的例子,ArticleListFragment中是新聞列表,ArticleContentFragment中是新聞內容。兩個Fragment分別在兩個Activity中。需求:當我們從ArticleContentFragment返回到ArticleListFragment時,需要帶上數據。

先貼上效果圖:

這裡寫圖片描述

下面分析代碼:

首先是列表Acitivity

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

    FragmentManager fm = getFragmentManager();
    mListFragment = (ArticleListFragment) fm.findFragmentById(R.id.fragment_container);
    if(mListFragment == null) {
        mListFragment = new ArticleListFragment();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.fragment_container, mListFragment);
        ft.commit();
    }
}

就是簡單的把ArticleListFragment添加到Activity容器當中。

下面是新聞列表Fragment的代碼:

public class ArticleListFragment extends ListFragment
{
    public static final int REQUESTCODE = 0x110;

    private List mTitles = Arrays.asList("item1", "item2", "item3");
    private ArrayAdapter mAdapter;
    private int mCurPos;

    @Override
    public void onActivityCreated(Bundle savedInstanceState)
    {
        super.onActivityCreated(savedInstanceState);
        mAdapter = new ArrayAdapter(getActivity(),
                android.R.layout.simple_list_item_1, mTitles);
        setListAdapter(mAdapter);
    }

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

        //構建Intent,以帶返回結果的方式啟動新聞內容的Activity
        Intent intent = new Intent(getActivity(), ContentActivity.class);
        intent.putExtra(ArticleContentFragment.KEY, mTitles.get(position));
        startActivityForResult(intent, REQUESTCODE);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == REQUESTCODE)
        {
            mTitles.set(mCurPos, mTitles.get(mCurPos) + "---" + data.getStringExtra(ArticleContentFragment.RESPONSE));
            mAdapter.notifyDataSetChanged();
        }
    }
}

ArticleListFragment 繼承自ListFragment,在onActivityCreated()回調函數中設置了adapter,在列表項的點擊回調函數onListItemClick()中使用startActivityForResult()來啟動新的Activity,當從下一個Activity返回到當前Activity就回調onActivityResult()方法。

新聞內容的Activity代碼:

protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_content);

    Intent intent = getIntent();

    FragmentManager fm = getFragmentManager();
    mContentFragment = (ArticleContentFragment) fm.findFragmentById(R.id.fragment_container);
    if(mContentFragment == null) {
        mContentFragment = ArticleContentFragment.createFragment(intent.getStringExtra(ArticleContentFragment.KEY));
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.fragment_container, mContentFragment);
        ft.commit();
    }
}

下面是新聞內容Fragment代碼:

public class ArticleContentFragment extends Fragment
{

    public static final String KEY = "KEY";
    public static final String RESPONSE = "RESPONSE";

    private String mArg;

    private TextView mTv;

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

        //獲取參數
        Bundle bundle = getArguments();
        if(bundle != null) {
            mArg = bundle.getString(KEY);

            Intent intent = new Intent();
            intent.putExtra(RESPONSE, "good");
            //Fragment本身沒有setResult()方法,必須通過宿主Activity實現
            getActivity().setResult(ArticleListFragment.REQUESTCODE, intent);
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View contentView = inflater.inflate(R.layout.example_fragment, container, false);
        mTv = (TextView) contentView.findViewById(R.id.tv);
        mTv.setText(mArg);
        return contentView;
    }

    public static ArticleContentFragment createFragment(String argument)
    {
        ArticleContentFragment fragment = new ArticleContentFragment();
        if(argument != null) {
            Bundle args = new Bundle();
            args.putString(KEY, argument);
            //為fragment傳遞參數
            fragment.setArguments(args);
        }
        return fragment;
    }
}

就一個地方需要注意,第24行。由於Fragment本身沒有setResult()方法,所以我們需要通過getActivity().setResult()來設置返回數據。這也就是說:Fragment能夠從Activity接收返回結果,但Fragment自身無法產生返回結果,需要借助宿主Activity實現。


與宿主Activity通信

Fragment可以通過getActivity()方法獲取宿主Activity的對象引用,通過該引用,可以調用Activity中的findViewById()方法獲得布局中的視圖控件。

如下所示:

TextView tv = (TextView)getActivity().findViewById(R.id.tv);

類似地,也可以在Activity中獲取Fragment實例:

FragmentManager fm = getFragmentManager();
ExampleFragment fragment = (ExampleFragment) fm.findFragmentById(R.id.fragment_container);

由於Fragment和宿主Activity都可以獲取對方的實例對象,就可訪問到對方內部所有的public類型的方法。

新的場景:

宿主Activity需要對Fragment中的UI控件的事件進行響應。好的做法就是在Fragment中定義添加回調接口,讓宿主Activity去實現這個接口。

舉例來說:一個新聞應用的Activity包含兩個Fragment,Fragment A展示新聞標題,Fragment B顯示新聞內容。Fragment A必須告訴Activity它的列表項何時被點擊,這樣Activity可以控制Fragment B的顯示內容。

下面給出示例代碼:

public class FragmentA extends ListFragment
{
    ...
    //宿主Activity必須實現這個接口
    public interface OnArticleSelectedListener
    {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

接著在宿主Activity中實現這個接口,重寫onArticleSelected()方法,並通知FragmentB來自於FragmentA的點擊事件。為了保證宿主Activity實現該接口,需要在FragmentA中的onAttach()回調方法中做如下工作(當Fragment添加至Activity,系統回調onAttach()方法):

public class FragmentA extends ListFragment
{
    private OnArticleSelectedListener mActivity;
    ...
    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        try {
            mActivity = (OnArticleSelectedListener)activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() +
                    "must implement OnArticleSelectedListener");
        }
    }
    ...
}

這樣FragmentA的成員變量mActivity持有了實現OnArticleSelectedListener 接口的引用,也就是FragmentA可以向宿主Activity傳遞點擊事件,宿主Activity重寫了onArticleSelected()方法,在該方法中執行具體邏輯操作,比如:控制FragmentB顯示被點擊的列表項所對應的新聞內容。

為了直觀,寫了一個簡單的demo

這裡寫圖片描述

這個demo的代碼就不貼了。從gif圖中可以看出,宿主Activity有上下兩個Fragment:上面的Fragment稱為FragmentA,下面的叫FragmentB。FragmentA的UI布局包含兩個Button,FragmentB的UI布局包含一個TextView。點擊FragmentA中的Button,FragmentB中的TextView的內容發生變化。

這個Demo就是按照上面新聞場景進行編寫。宿主Activity負責維系兩個Fragment的通信,宿主Activity起到一個橋梁的作用。FragmentA中的按鈕點擊之後,並不會直接操作FragmentB中的TextView,而是通過接口回調的方式傳遞給宿主Activity,宿主Activity根據FragmentA中的點擊事件去改變FragmentB中的內容。


與Toolbar集成

如果對Toolbar的用法還不清楚的,請點擊Android常用UI之Toolbar 。所以關於Toolbar的細節就不講了,下面主要講一下Fragment與Toolbar的集成。

其實,在Fragment中使用Toolbar的方式幾乎和在Activity中使用是一致的。在Fragment也有onCreateOptionsMenu()回調和onOptionsItemSelected()回調,但是在Fragment中需要使能菜單功能,通過setHasOptionsMenu(true);方法實現。

下面看代碼,首先是Activity的布局文件:



    

        

        <framelayout android:id="@+id/fragment_container" android:layout_height="match_parent" android:layout_width="match_parent">

    </framelayout>

添加了v7包中的Toolbar布局;下面的FrameLayout是Fragment的容器。

下面看styles.xml



    

OK,在onCreate()回調中調用setHasOptionsMenu(true)來在Fragment中使能菜單,通過onCreateOptionsMenu()回調來加載菜單資源(res/menu/menu_main.xml),通過onOptionsItemSelected(),來響應菜單項點擊事件。

最後別忘了在manifest文件中使用android:theme=”@style/AppTheme.NoActionBar”主題,目的是阻止系統使用自帶的ActionBar。

最後,貼運行效果圖:

這裡寫圖片描述


好的,本篇文章到這就結束了,總算寫完了~~~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved