3.2 Fragment的用法与通信大全

点此进入:从零快速构建APP系列目录导图
点此进入:UI编程系列目录导图
点此进入:四大组件系列目录导图
点此进入:数据网络和线程系列目录导图

本节例程下载地址:WillFlowFragment

究竟要如何使用碎片才能充分地利用平板屏幕的空间呢?假如我们正在开发一个新闻应用,其中一个界面使用 ListView 展示了一组新闻的标题,当点击了其中一个标题,就打开另一个界面显示新闻的详细内容。如果是在手机中设计,我们可以将新闻标题列表放在一个活动中,将新闻的详细内容放在另一个活动中。

一、静态创建 Fragment

这是使用Fragment最简单的一种方式,把Fragment当成普通的控件,直接写在Activity的布局文件中就可以了。

静态创建 Fragment 的步骤:

1、继承Fragment,重写onCreateView决定Fragemnt的布局
2、在Activity中声明此Fragment,就当和普通的View一样

下面我们用一个例子来展示静态创建 Fragment 的过程。

新建一个左侧碎片布局 left_fragment.xml,代码如下所示:
<RelativeLayout 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"
        android:layout_marginTop="96dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

这个布局非常简单,只放置了一个按钮,并让它水平居中显示。

然后新建右侧碎片布局 right_fragment.xml,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fc6243"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textColor="#4352fc"
        android:text="这个是右侧的Fragment"
        android:textSize="22sp"
        android:layout_marginTop="96dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/textView" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="236dp"
        android:layout_height="236dp"
        android:layout_marginTop="40dp"
        app:srcCompat="@drawable/willflow"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

可以看到,我们将这个布局的背景色设置成浅红色,并放置了一个 TextView 用于显示一段文本。

接着新建一个 LeftFragment 类,继承自 Fragment,然后在关键的生命周期方法中插入代码(就和在处理 Activity 时一样)。注意:创建 Fragment 时,必须重写 onCreateView() 回调方法来定义布局。事实上,这是唯一一个为使 Fragment 运行起来需要重写的回调方法。

LeftFragment的代码如下所示:
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;
    }
}

这里仅仅是重写了 Fragment 的 onCreateView()方法,然后在这个方法中通过 LayoutInflater的 inflate()方法将刚才定义的 left_fragment 布局动态加载进来,整个方法再简单明不过了。

接着我们用同样的方法新建 RightFragment,代码如下:
public class RightFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.right_fragment, container, false);
        return view;
    }
}

基本上代码都是相同的,相信已经没有必要再做什么解释了。

接下来修改 activity_main.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">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.wgh.willflowfragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <fragment
        android:id="@+id/right_fragment"
        android:name="com.wgh.willflowfragment.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
</LinearLayout>

可以看到,我们使用了标签在布局中添加碎片,其中指定的大多数属性我们都是熟悉的,只不过这里还需要通过 android:name 属性来显式指明要添加的碎片类名,注意一定要将类的包名也加上。

编译运行看效果:

注意: 当通过 XML 布局文件的方式将 Fragment 添加进 Activity 时,Fragment 是不能被动态移除的。如果想要在用户交互的时候把 Fragment 切入与切出,必须在 Activity 启动后,再将 Fragment 添加进 Activity,我们接下来学习这种方法。

二、动态创建 Fragment

在刚才的例子当中,我们已经学会了在布局文件中添加Fragment的方法,不过Fragment真正的强大之处在于,它可以在程序运行时动态地添加到Activity当中。根据具体情况来动态地添加Fragment,我们就可以将程序界面定制得更加多样化。

新建 another_right_fragment.xml,代码如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffaa00"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="33dp"
        android:text="这个是另一个右侧的Fragment"
        android:textColor="#cb43fc"
        android:textSize="20sp" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="136dp"
        android:layout_height="136dp"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="24dp"
        android:src="@mipmap/ic_launcher" />

</RelativeLayout>

这个布局文件的代码和 right_fragment.xml 中的代码基本相同,只是将背景色改成了橘黄色,并将显示的文字改了改。

然后新建 AnotherRightFragment 作为另一个右侧碎片,代码如下所示:
public class AnotherRightFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.another_right_fragment, container, false);
        return view;
    }
}

代码同样非常简单,在 onCreateView() 方法中加载了刚刚创建的 another_right_fragment 布局。这样我们就准备好了另一个Fragment,接下来看一下如何将它动态地添加到活动当中。

修改 activity_main.xml,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.wgh.willflowfragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <FrameLayout
        android:id="@+id/right_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1">

        <fragment
            android:id="@+id/right_fragment"
            android:name="com.wgh.willflowfragment.AnotherRightFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
</LinearLayout>

可以看到,现在将右侧Fragment放在了一个 FrameLayout 中,还记得这个布局吗?在之前我们学过,这是 Android 中最简单的一种布局,它没有任何的定位方式,所有的控件都会摆放在布局的左上角。由于这里仅需要在布局里放入一个Fragment ,因此非常适合使用 FrameLayout 帧布局。之后我们将在代码中替换 FrameLayout 里的内容,从而实现动态添加Fragment 的功能。

修改MainActivity 中的代码,如下所示:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button_lf);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AnotherRightFragment fragment = new AnotherRightFragment();
                FragmentManager fragmentManager = getFragmentManager();
                FragmentTransaction transaction = fragmentManager.beginTransaction();
                transaction.replace(R.id.right_layout, fragment);
                transaction.commit();
            }
        });
    }

可以看到,首先我们给左侧Fragment 中的按钮注册了一个点击事件,然后将动态添加Fragment 的逻辑都放在了点击事件里进行。

这样就完成了在活动中动态添加Fragment的功能,接下来运行看效果:

动态添加Fragment主要分为五步:
  1. 创建待添加的Fragment实例。
  2. 获取到 FragmentManager,在Activity中可以直接调用 getFragmentManager() 方法得到。
  3. 开启一个事务,通过调用 beginTransaction() 方法开启。
  4. 向容器内加入Fragment,一般使用 replace() 方法实现,需要传入容器的 id 和待添加的Fragment实例。
  5. 提交事务,调用 commit()方法来完成。

三、Fragment 的通信

1、与Activity通信

方法一:findViewById()

尽管 Fragment 是作为独立于 Activity 的对象实现,并且可在多个 Activity 内使用,但 Fragment 的给定实例会直接绑定到包含它的 Activity。

具体地说,Fragment 可以通过 getActivity() 访问 Activity 实例,并轻松地执行在 Activity 布局中查找视图等任务。

View listView = getActivity().findViewById(R.id.listView);

同样地,我们的 Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通过从 FragmentManager 获取对 Fragment 的引用来调用Fragment中的方法。例如:

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
方法二:创建对 Activity 的事件回调

在某些情况下,我们可能需要通过 Fragment 与 Activity 共享事件。执行此操作的一个好方法是,在Fragment内定义一个回调接口,并要求宿主 Activity 实现它。 当 Activity 通过该接口收到回调时,可以根据需要与布局中的其他片段共享这些信息。

例如,如果一个新闻应用的 Activity 有两个Fragment:一个用于显示文章列表(Fragment A),另一个用于显示文章(Fragment B)— 那么Fragment A 必须在列表项被选定后告知 Activity,以便它告知Fragment B 显示该文章。

在下面的例子中,OnArticleSelectedListener 接口在 FragmentA 内声明:

public static class FragmentA extends ListFragment {
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
}

然后,该Fragment的宿主 Activity 会实现 OnArticleSelectedListener 接口并替代 onArticleSelected(),将来自Fragment A 的事件通知Fragment B。为确保宿主 Activity 实现此接口,Fragment A 的 onAttach() 回调方法(系统在向 Activity 添加Fragment时调用的方法)会通过转换传递到 onAttach() 中的 Activity 来实例化 OnArticleSelectedListener 的实例:

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

如果 Activity 未实现接口,则Fragment会引发 ClassCastException。实现时,mListener 成员会保留对 Activity 的 OnArticleSelectedListener 实现的引用,以便Fragment A 可以通过调用 OnArticleSelectedListener 接口定义的方法与 Activity 共享事件。例如,如果Fragment A 是ListFragment的一个扩展,则用户每次点击列表项时,系统都会调用片段中的 onListItemClick(),然后该方法会调用 onArticleSelected() 以与 Activity 共享事件:

    public static class FragmentA extends ListFragment {
        OnArticleSelectedListener mListener;
        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
            mListener.onArticleSelected(noteUri);
        }
    }

传递到 onListItemClick()的 id 参数是被点击项的行 ID,即 Activity(或其他片段)用来从应用的 ContentProvider获取文章的 ID。

2、与其它Fragment通信

为了重用 Fragment UI 组件,我们应该把每个 Fragment 都构建成完全自包含的、模块化的组件,即定义它们自己的布局与行为。一旦我们定义了这些可重用的 Fragment,我们就可以通过应用程序逻辑让它们关联到 Activity,以实现整体的复合 UI。

通常 Fragment 之间可能会需要交互,比如基于用户事件的内容变更。所有 Fragment 之间的交互应通过与之关联的 Activity 来完成,也就是说两个 Fragment 之间不应直接交互。

(1)定义接口

为了让 Fragment 与包含它的 Activity 进行交互,可以在 Fragment 类中定义一个接口,并在 Activity 中实现。该 Fragment 在它的 onAttach() 方法生命周期中获取该接口的实现,然后调用接口的方法,以便与 Activity 进行交互。

以下是 Fragment 与 Activity 交互的例子:
public class HeadlinesFragment extends ListFragment {
    OnHeadlineSelectedListener mCallback;

    // 容器 Activity 必须实现该接口
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

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

        // 确认容器 Activity 已实现该回调接口。否则,抛出异常
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(
             activity.toString() + " 必须实现这个接口!");
        }
    }
}

现在 Fragment 可以通过调用 mCallback(OnHeadlineSelectedListener 接口的实例)的 onArticleSelected() 方法(也可以是其它方法)与 Activity 进行消息传递。

例如当用户点击列表条目时,Fragment 中的下面的方法将被调用:
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // 向宿主 Activity 传送事件
        mCallback.onArticleSelected(position);
    }

##### (2)实现接口
为了接收回调事件,宿主 Activity 必须实现在 Fragment 中定义的接口。

例如,下面的 Activity 实现了上面例子中的接口:

public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{
    public void onArticleSelected(int position) {
        // 在这里做点什么...
    }
}
(3)向 Fragment 传递消息

宿主 Activity 通过 findFragmentById() 获取Fragment 的实例,然后直接调用 Fragment 的 public 方法向 Fragment 传递消息。

例如,假设上面所示的 Activity 可能包含另一个 Fragment,该 Fragment 用于展示从上面的回调方法中返回的指定的数据。在这种情况下,Activity 可以把从回调方法中接收到的信息传递到这个展示数据的 Fragment。

public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{

    public void onArticleSelected(int position) {

        ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            articleFrag.updateArticleView(position);
        } else {
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);

            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);
            transaction.commit();
        }
    }
}

这里面涉及到了返回栈的概念,我们接下来给大家简单介绍下。

在碎片中模拟返回栈:

上面的代码中我们成功实现了向Activity中动态添加Fragment的功能,不过通过点击按钮添加了一个Fragment之后,这时按下 Back 键程序就会直接退出而不是返回。如果这里我
们想模仿类似于返回栈的效果,就需要将一个事务添加到返回栈中。修改 MainActivity 中的代码如下即可:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button_lf);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AnotherRightFragment fragment = new AnotherRightFragment();
                FragmentManager fragmentManager = getFragmentManager();
                FragmentTransaction transaction = fragmentManager.beginTransaction();
                transaction.replace(R.id.right_layout, fragment);
                transaction.addToBackStack(null);
                transaction.commit();
            }
        });
    }
添加返回栈后的效果:

点此进入:GitHub开源项目“爱阅”。“爱阅”专注于收集优质的文章、站点、教程,与大家共分享。下面是“爱阅”的效果图:


联系方式:

简书:WillFlow
CSDN:WillFlow
微信公众号:WillFlow

微信公众号:WillFlow

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值