Android必备知识---掌握Fragment(一)

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。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="TextView in Fragment"
        android:textSize="22sp"
        android:gravity="center"/>

</LinearLayout>

第三、在Activity的布局文件中使用这个ExampleFragment

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.czh.MainActivity">

    <fragment
        android:name="com.czh.ExampleFragment"
        android:id="@+id/FragmentOne"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

有个需要注意的地方:每个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
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.czh.MainActivity">

</LinearLayout>

这里给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<String> mTitles = Arrays.asList("item1", "item2", "item3");
    private ArrayAdapter<String> mAdapter;
    private int mCurPos;

    @Override
    public void onActivityCreated(Bundle savedInstanceState)
    {
        super.onActivityCreated(savedInstanceState);
        mAdapter = new ArrayAdapter<String>(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的布局文件:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.czh.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:theme="@style/AppTheme.AppBarOverlay"
            app:logo="@mipmap/ic_launcher"/>

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

    </LinearLayout>

</LinearLayout>

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

下面看styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
</resources>

下面是Activity的代码:

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

    //使用Toolbar
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    //添加Fragment到容器中
    FragmentManager fm = getFragmentManager();
    mToolbarFragment = (ToolbarFragment) fm.findFragmentById(R.id.fragment_container);
    if(mToolbarFragment == null) {
        mToolbarFragment = new ToolbarFragment();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.fragment_container, mToolbarFragment);
        ft.commit();
    }
}

下面是Fragment的代码:

public class ToolbarFragment extends Fragment
{

    private TextView mTv;

    private AppCompatActivity mActivity;

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

        //在Fragment中使用菜单,必须先使能
        setHasOptionsMenu(true);

        ActionBar ab =  mActivity.getSupportActionBar();
        ab.setTitle("Fragment");
        //添加Toolbar的导航按钮
        ab.setDisplayHomeAsUpEnabled(true);
    }

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);

        try {
            mActivity = (AppCompatActivity) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException("activity must be AppCompatActivity");
        }

    }

    @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("hello world!!!");
        return contentView;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
    {
        inflater.inflate(R.menu.menu_main, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
            case R.id.action_search:
                Toast.makeText(getActivity(), "search", Toast.LENGTH_SHORT).show();
                break;
            case R.id.action_setting:
                Toast.makeText(getActivity(), "setting", Toast.LENGTH_SHORT).show();
                break;
            case android.R.id.home:
                mActivity.finish();
                break;
        }
        return true;
    }
}

OK,在onCreate()回调中调用setHasOptionsMenu(true)来在Fragment中使能菜单,通过onCreateOptionsMenu()回调来加载菜单资源(res/menu/menu_main.xml),通过onOptionsItemSelected(),来响应菜单项点击事件。

最后别忘了在manifest文件中使用android:theme=”@style/AppTheme.NoActionBar”主题,目的是阻止系统使用自带的ActionBar。

最后,贴运行效果图:

这里写图片描述


好的,本篇文章到这就结束了,总算写完了~~~

参考文章:

官方文档:https://developer.android.com/guide/components/fragments.html
鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/42628537

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值