为了在 Android 上为用户提供动态的、多窗口的交互体验,需要将 UI 组件和 Activity 操作封装成模块进行使用,这样我们就可以在 Activity 中对这些模块进行切入切出操作。可以用 Fragment 创建这些模块,Fragment 就像一个嵌套的 Activity,拥有自己的布局(Layout)并管理自己的生命周期。

Fragment 定义了自己的布局后,它可以在 Activity 中与其他 Fragment 生成不同的组合,从而为不同的屏幕尺寸生成不同的布局(小屏幕一次也许只能显示一个 Fragment,大屏幕则可以显示更多)。

本章将展示如何用 Fragment 创建动态界面,并在不同屏幕尺寸的设备上优化 APP 的用户体验。本章内容支持 Android 1.6 以上的设备。

创建 Fragment

可以把 Fragment 想象成 Activity 的模块,它拥有自己的生命周期、接收输入事件,可以在 Acvitity 运行过程中添加或者移除(有点像“子 Activity”,可以在不同的 Activity 里重复使用)。这一课教我们将学习继承 Support Library 中的 Fragment,使 APP 在 Android 1.6 这样的低版本上仍能保持兼容。

在开始之前,必须在项目中先引用 Support Library。如果你从未使用过 Support Library,可根据文档 设置 Support Library 在项目中使用 v4 库。当然,也可以使用包含 APP Bar 的 v7 appcompat 库。该库兼容 Android 2.1 (API level 7),同时也包含了 Fragment API。

创建 Fragment 类

首先从 Fragment 继承并创建 Fragment,然后在关键的生命周期方法中插入代码(就和在处理 Activity 时一样)。

其中一个区别是:创建 Fragment 时,必须重写 onCreateView() 回调方法来定义布局。事实上,这是唯一一个为使 Fragment 运行起来需要重写的回调方法。比如,下面是一个自定义布局的示例 Fragment:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.ViewGroup; public class ArticleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 拉伸该 Fragment 的布局
return inflater.inflate(R.layout.article_view, container, false);
}
}

和 Activity 一样,当 Fragment 从 Activity 添加或者移除、或 Activity 生命周期发生变化时,Fragment 通过生命周期回调函数管理其状态。例如,当 Activity 的 onPause() 被调用时,它内部所有 Fragment 的 onPause() 方法也会被触发。

更多关于 Fragment 的声明周期和回调方法,详见 Fragments 开发指南.

用 XML 将 Fragment 添加到 Activity

Fragments 是可重用的、模块化的 UI 组件。每个 Fragment 实例都必须与一个 FragmentActivity 关联。我们可以在 Activity 的 XML 布局文件中逐个定义 Fragment 来实现这种关联。

注: FragmentActivity 是 Support Library 提供的一种特殊 Activity,用于处理 API 11 版本以下的 Fragment。如果我们 APP 中的最低版本大于等于 11,则可以使用普通的 Activity

以下是一个 XML 布局的例子:当屏幕被认为是 "large"(用目录名称中的 large 字符来区分)时,它在布局中增加了两个 Fragment。

res/layout-large/news_articles.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"> <fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="@+id/headlines_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" /> <fragment android:name="com.example.android.fragments.ArticleFragment"
android:id="@+id/article_fragment"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" /> </LinearLayout>

提示: 更多关于不同屏幕尺寸创建不同布局的信息,请阅读 兼容不同屏幕尺寸

然后将这个布局文件用到 Activity 中。

import android.os.Bundle;
import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
}
}

如果使用 v7 appcompat 库,Activity 应该改为继承自 AppCompatActivity,AppCompatActivity 是 FragmentActivity 的子类(更多关于这方面的内容,请阅读 添加 App Bar)。

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

建立灵活动态的 UI

在设计支持各种屏幕尺寸的应用时,你可以在不同的布局配置中重复使用 Fragment,以便根据相应的屏幕空间提供更出色的用户体验。

例如,一次只显示一个 Fragment 可能就很适合手机这种单窗格界面,但在平板电脑上,你可能需要设置并列的 Fragment,因为平板电脑的屏幕尺寸较宽阔,可向用户显示更多信息。

图1: 两个 Fragment,显示在不同尺寸屏幕上同一 Activity 的不同配置中。在较宽阔的屏幕上,两个 Fragment 可并列显示;在手机上,一次只能显示一个 Fragment,因此必须在用户导航时更换 Fragment。

利用 FragmentManager 类提供的方法,你可以在运行时添加、移除和替换 Activity 中的 Fragment,以便为用户提供一种动态体验。

在运行时向 Activity 添加 Fragment

你可以在 Activity 运行时向其添加 Fragment,而不用像 上一课 中介绍的那样,使用 <fragment> 元素在布局文件中为 Activity 定义 Fragment。如果你打算在 Activity 运行周期内更改 Fragment,就必须这样做。

要执行添加或移除 Fragment 等事务,你必须使用 FragmentManager 创建一个 FragmentTransaction,后者可提供用于执行添加、移除、替换以及其他 Fragment 事务的 API。

如果 Activity 中的 Fragment 可以移除和替换,你应在调用 Activity 的 onCreate() 方法期间为 Activity 添加初始 Fragment(s)。

在处理 Fragment(特别是在运行时添加的 Fragment)时,请谨记以下重要规则:必须在布局中为 Fragment 提供 View 容器,以便保存 Fragment 的布局。

下面是 上一课 所示布局的替代布局,这种布局一次只会显示一个 Fragment。要用一个 Fragment 替换另一个 Fragment,Activity 的布局中需要包含一个作为 Fragment 容器的空 FrameLayout

请注意,该文件名与上一课中布局文件的名称相同,但布局目录没有 large 这一限定符。因此,此布局会在设备屏幕小于“large”的情况下使用,原因是尺寸较小的屏幕不适合同时显示两个 Fragment。

res/layout/news_articles.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

在 Activity 中,用 Support Library API 调用 getSupportFragmentManager() 以获取 FragmentManager,然后调用 beginTransaction() 创建 FragmentTransaction,然后调用 add() 添加 Fragment。

你可以使用同一个 FragmentTransaction 对 Activity 执行多 Fragment 事务。当你准备好进行更改时,必须调用 commit()

例如,下面介绍了如何为上述布局添加 Fragment:

import android.os.Bundle;
import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles); // 确认 Activity 使用的布局版本包含 fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null) { // 不过,如果我们要从先前的状态还原,则无需执行任何操作而应返回,否则
// 就会得到重叠的 Fragment。
if (savedInstanceState != null) {
return;
} // 创建一个要放入 Activity 布局中的新 Fragment
HeadlinesFragment firstFragment = new HeadlinesFragment(); // 如果此 Activity 是通过 Intent 发出的特殊指令来启动的,
// 请将该 Intent 的 extras 以参数形式传递给该 Fragment
firstFragment.setArguments(getIntent().getExtras()); // 将该 Fragment 添加到“fragment_container” FrameLayout 中
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
}

由于该 Fragment 已在运行时添加到 FrameLayout 容器中,而不是在 Activity 布局中通过 <fragment> 元素进行定义,因此该 Activity 可以移除和替换这个 Fragment。

用一个 Fragment 替换另一个 Fragment

替换 Fragment 的步骤与添加 Fragment 的步骤相似,但需要调用 replace() 方法,而非 add()

请注意,当你执行替换或移除 Fragment 等 Fragment 事务时,最好能让用户向后导航和“撤消”所做更改。要通过 Fragment 事务允许用户向后导航,你必须调用 addToBackStack(),然后再执行 FragmentTransaction

注: 当你移除或替换 Fragment 并向返回堆栈添加事务时,已移除的 Fragment 会停止(而不是销毁)。如果用户向后导航,还原该 Fragment,它会重新启动。如果你没有向返回堆栈添加事务,那么该 Fragment 在移除或替换时就会被销毁。

替换 Fragment 的示例:

// 创建 Fragment 并为其添加一个参数,用来指定应显示的文章
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // 将 fragment_container View 中的内容替换为此 Fragment,
// 然后将该事务添加到返回堆栈,以便用户可以向后导航
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null); // 执行事务
transaction.commit();

addToBackStack() 方法可接受可选的字符串参数,来为事务指定独一无二的名称。除非你打算使用 FragmentManager.BackStackEntry API 执行高级 Fragment 操作,否则无需使用此名称。

与其他 Fragment 交互

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

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

定义接口

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

以下是 Fragment 与 Activity 交互的例子:

public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback; // 容器 Activity 必须实现该接口
// (译注:“容器 Activity”意即“包含该 Fragment 的 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()
+ " must implement OnHeadlineSelectedListener");
}
} ...
}

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

例如,当用户点击列表条目时,Fragment 中的下面的方法将被调用。Fragment 用回调接口将事件传递给父 Activity。

    @Override
public void onListItemClick(ListView l, View v, int position, long id) {
// 向宿主 Activity 传送事件
mCallback.onArticleSelected(position);
}

实现接口

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

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

public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
... public void onArticleSelected(int position) {
// 用户从 HeadlinesFragment 选择了一篇文章的标题
// 在这里做点什么,以显示该文章
}
}

向 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) {
// 用户从 HeadlinesFragment 选择了一篇文章的标题
// 在这里做点什么,以显示该文章 ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.article_fragment); if (articleFrag != null) {
// 若 articleFrag 有效,则表示我们正在处理两格布局(two-pane layout)…… // 调用 ArticleFragment 的方法,以更新其内容
articleFrag.updateArticleView(position);
} else {
// 否则,我们正在处理单格布局(one-pane layout)。此时需要 swap frags... // 创建 Fragment,向其传递包含被选文章的参数
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // 无论 fragment_container 视图里是什么,用该 Fragment 替换它。并将
// 该事务添加至回栈,以便用户可以往回导航(译注:回栈,即 Back Stack。
// 在有多个 Activity 的 APP 中,将这些 Activity 按创建次序组织起来的
// 栈,称为回栈)
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null); // 执行事务
transaction.commit();
}
}
}
 

9) 十分钟学会android--使用Fragment建立动态UI的更多相关文章

  1. Android学习路径(22)应用Fragment建立动态UI——构建一个灵活UI

    当你设计你的应用来支持多个屏幕尺寸.你能够基于可用的屏幕空间通过在不同的布局上重用fragment来优化用户体验. 比如,在一个手机上.使用单面板(一次仅仅显示一个fragment)的用户体验更加合适 ...

  2. Android学习路径(23)应用Fragment建立动态UI——Fragment之间的通信

    为了要重用Fragment的UI组件.你应该为它们每个都构建一个完整独立的,模块化的组件来定义他自身的布局和行为. 一旦你定义了这些可重用的Fragments.你能够通过activity关联它们同一时 ...

  3. PHP学习过程_Symfony_(3)_整理_十分钟学会Symfony

    这篇文章主要介绍了Symfony学习十分钟入门教程,详细介绍了Symfony的安装配置,项目初始化,建立Bundle,设计实体,添加约束,增删改查等基本操作技巧,需要的朋友可以参考下 (此文章已被多人 ...

  4. Android - 用Fragments实现动态UI - 创建Fragment

    你可以把fragment当作activity中的一个活动模块,它有自己的生命周期,自己接收输入消息,可以在activity运行的时候添加和删除(就像可以在其他activity中重用的"子ac ...

  5. Android - 用Fragments实现动态UI - 创建灵活的UI

    当设计程序来支持各种不一样的屏幕尺寸时,可以在不同的布局中重用fragment来根据可用的屏幕大小来优化用户体验. 例如,在手机上可能使用一个fragment来使用单窗口用户体验比较合适.但是,你可能 ...

  6. Android - 用Fragments实现动态UI - 使用Android Support Library

    Android Support Library提供了一个带有API库的JAR文件来让你可以在使用最新的Android API的同时也也已在早期版本的Android上运行.例如,Support Libr ...

  7. 十分钟学会 tmux

    tmux 是一款终端复用命令行工具,一般用于 Terminal 的窗口管理.在 macOS 下,使用 iTerm2 能应付绝大多数窗口管理的需求. 如上图所示,iTerm2 能新建多个标签页(快捷键 ...

  8. 快速入门:十分钟学会Python

    初试牛刀 假设你希望学习Python这门语言,却苦于找不到一个简短而全面的入门教程.那么本教程将花费十分钟的时间带你走入Python的大门.本文的内容介于教程(Toturial)和速查手册(Cheat ...

  9. Python十分钟学会

    初试牛刀 假设你希望学习Python这门语言,却苦于找不到一个简短而全面的入门教程.那么本教程将花费十分钟的时间带你走入Python的大门.本文的内容介于教程(Toturial)和速查手册(Cheat ...

随机推荐

  1. charles修改下行参数

    1.charles抓包修改下行参数: 想要修改的手机展示展示信息页: 2.charle设置断点,在请求接口上一个上设置断点 菜单proxy->Breadkpoints Settings设置打开断 ...

  2. vue 强制刷新组件

    <component v-if="hackReset"></component> 2 3 4 this.hackReset = false this.$ne ...

  3. hdu 3001(三进制状压)

    题目 解法 看到这道题,我们就会想到旅行商问题.但是这里每一个点可以经过最多两次,所以我们用三进制表示就好了. 代码 #include <iostream> #include <cs ...

  4. 2.IntelliJ IDEA 下载破解(2017)

    1.首先,我找到了 IntelliJ IDEA的官网:www.jetbrains.com 然后找到下载的地方,选择自己电脑所匹配的下载安装包,这里我们选择收费版的下载,因为免费版的功能并没有收费版的强 ...

  5. jquery源码分析(二)——架构设计

    要学习一个库首先的理清它整体架构: 1.jQuery源码大致架构如下:(基于 jQuery 1.11 版本,共计8829行源码)(21,94)                定义了一些变量和函数jQu ...

  6. [bzoj2631]tree_LCT

    tree bzoj-2631 题目大意:给定一个n个点的树,每个点的初始权值为1,支持:删边加边(这两个操作同时进行,保证操作之后还是一棵树),路径加,路径乘,查询路径和. 注释:$1\le n,q\ ...

  7. spring-cloud-feign 使用@RequetParam报错QueryMap parameter must be a Map: class java.lang.String

    这里使用spring-cloud-start-feign: 1.2.2 REALEASE版本,依赖管理器版本是 Camden.SR2 出错的原因是@RequestParam的value为empty时, ...

  8. Android----获得当前应用包名、当前activity的名字、推断是否是系统桌面、service类

    1.获得当前应用包名 public static String getRunningActivityName(Context context) { ActivityManager activityMa ...

  9. OpenCV打开摄像头失败

    <span style="font-family: Arial, Helvetica, sans-serif;">#include <stdio.h>< ...

  10. [ Javascript ] 内存泄露以及循环引用解析

    内存泄露 在javascript中,我们非常少去关注内存的管理. 我们创建变量,使用变量,浏览器关注这些底层的细节都显得非常正常. 可是当应用程序变得越来越复杂而且ajax化之后,或者用户在一个页面停 ...