9) 十分钟学会android--使用Fragment建立动态UI

为了在 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();
        }
    }
}
 
时间: 2024-10-05 09:06:26

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

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

当你设计你的应用来支持多个屏幕尺寸.你能够基于可用的屏幕空间通过在不同的布局上重用fragment来优化用户体验. 比如,在一个手机上.使用单面板(一次仅仅显示一个fragment)的用户体验更加合适.For example, on a handset device it might be appropriate to display just one fragment at a time for a single-pane user interface. 相反,你可能希望在一个可以展示很多其它

十分钟学会Markdown(younghz原创)

younghz原创,转载请注明:http://blog.csdn.net/u012150179/article/details/26503779 原内容及代码托管在GitHub上,并持续更新,欢迎交流:https://github.com/younghz/Markdown 主要内容 MARKDOWN是什么? 谁发明可这么个牛X的东西? 为什么要使用它? 怎么使用? 都谁在用?没人用的东西我可不用. 感觉有意思?趁热打铁,推荐几个工具. 正文 1. MARKDOWN是什么? MARKDOWN是一种

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

这篇文章主要介绍了Symfony学习十分钟入门教程,详细介绍了Symfony的安装配置,项目初始化,建立Bundle,设计实体,添加约束,增删改查等基本操作技巧,需要的朋友可以参考下 (此文章已被多人复制转载,原文为我们老板写的"十分钟学会Symfony",不过GItHub已经清理了,所以今天我重新整理一下,不过基本不变) Symfony是一个强大的基于PHP的Web开发框架,在这里我们用十分钟的时间来做一个简单的增删改查的程序, 任何不熟悉Symfony的人都可以通过这个教程完成自己

Android学习路线(二十)运用Fragment构建动态UI

要在Android系统上创建一个动态或者多面板的用户界面,你需要将UI组件以及activity行为封装成模块,让它能够在你的activity中灵活地切换显示与隐藏.你可以使用Fragment类来创建这些模块,它们能够表现得有些像嵌套的activity,它们定义着自己的布局,管理自己的生命周期. 当一个fragment指定了它自己的布局,它可以在activity中和其他的fragment配置为不同的组合,这样就能够为不同的屏幕尺寸来修改你的布局配置(在小屏幕上一次展现一个fragment,而在大屏

Android学习路线(二十二)运用Fragment构建动态UI——构建一个灵活的UI

先占个位置,下次翻译 :p When designing your application to support a wide range of screen sizes, you can reuse your fragments in different layout configurations to optimize the user experience based on the available screen space. For example, on a handset devi

十分钟学会Python的基本类型

一:起因 (1)说起学习Python的原因,上一篇blog已经提到过了:Python掐指一算不过是自己接触过的第四门大型语言(从接触到现在已经2周了)c/c++ ,Java,MATLAB(PS:应该还不算入门,只会简单的应用),之后就是python了. (2)c/c++ 到java的过渡是非常短暂的,但是过渡到MATLA就非常的不顺利(PS:就是上一篇的blog提到过的,从内心里抵触一门未曾谋面的语言),当时就已经感受到了开启一门新语言阻力非常大(有时可以用可遇不可求来形容). (3)从java

两分钟学会Android平台NDK编程(无须Eclipse和cygwin,可使用命令行打包多个so)

之前在进行cocos2dx开发时,已经详细介绍了如何将win32的c++代码移植到Android平台,当再次回顾时,发现一些基础的东西理解并不是很彻底,今天使用Android NDK提供的一个例子做一个简单的移植.在进行该demo前,请确认你已经配置了Android开发环境和安装了最新的Android NDK. 1.创建Android项目 创建一个Android项目 , 包名是com.example.hellojni,创建一个Activity作为程序进入的Acitivity,命名为HelloJn

Android学习路线(二十一)运用Fragment构建动态UI——创建一个Fragment

你可以把fragment看成是activity的模块化部分,它拥有自己的生命周期,接受它自己的输入事件,你可以在activity运行时添加或者删除它(有点像是一个"子activity",你可以在不同的activity中重用它).本课将向你展示如何使用Support Libaray继承 Fragment 类来让你的应用能够兼容正在运行Android 1.6的设备. 提示: 如果你决定你的应用需求的最低API级别是11或者更高,那么你不需要使用Support Library,你可以直接使用

Android学习路线(二十三)运用Fragment构建动态UI——Fragment间通讯

先占个位置,下次翻译 :p In order to reuse the Fragment UI components, you should build each as a completely self-contained, modular component that defines its own layout and behavior. Once you have defined these reusable Fragments, you can associate them with