构建灵活的界面

在设计支持各种屏幕尺寸的应用时,您可以在不同的布局配置中重复使用 Fragment,以根据可用的屏幕空间优化用户体验。

例如,在手机设备上,由于采用单窗格界面,因此可能一次只显示一个 Fragment 比较合适。相反,在平板电脑上,由于屏幕尺寸较大,因此您可能想要设置并排显示的 Fragment,以向用户显示更多信息。

图 1. 同一 Activity 的两个 Fragment,采用不同的配置显示在不同屏幕尺寸的设备上。在较大的屏幕上,两个 Fragment 同屏并排显示,但在手机设备上,同屏仅显示一个 Fragment,因此用户必须通过切换屏幕进行浏览。

利用 FragmentManager 类提供的方法,您可以在运行时为 Activity 添加、移除和替换 Fragment,从而营造出动态的用户体验。

如需详细了解如何实现 Fragment,请参阅以下资源。

在运行时为 Activity 添加 Fragment

您可以在 Activity 运行时为其添加 Fragment,而不用像上一课中介绍的那样,使用 <fragment> 元素在布局文件中为 Activity 定义 Fragment。如果您计划在 Activity 的生命周期内更改 Fragment,就需要采用这种方法。

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

如果您的 Activity 允许移除和替换 Fragment,应在 Activity 的 onCreate() 方法执行期间为其添加初始 Fragment。

在处理 Fragment 时(尤其是在运行时添加 Fragment 时),需遵循的一个重要原则是,您的 Activity 布局必须包含一个可以插入 Fragment 的容器 View

下面是上一课所示布局的替代布局,该布局一次只显示一个 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 内,使用支持库 API 调用 getSupportFragmentManager() 以获取 FragmentManager。然后,调用 beginTransaction() 以创建 FragmentTransaction,并调用 add() 以添加 Fragment。

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

例如,下面展示了如何为先前的布局添加 Fragment:

Kotlin

    import android.os.Bundle
    import android.support.v4.app.FragmentActivity

    class MainActivity : FragmentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.news_articles)

            // Check that the activity is using the layout version with
            // the fragment_container FrameLayout
            if (findViewById(R.id.fragment_container) != null) {

                // However, if we're being restored from a previous state,
                // then we don't need to do anything and should return or else
                // we could end up with overlapping fragments.
                if (savedInstanceState != null) {
                    return;
                }

                // Create a new Fragment to be placed in the activity layout
                val firstFragment = HeadlinesFragment()

                // In case this activity was started with special instructions from an
                // Intent, pass the Intent's extras to the fragment as arguments
                firstFragment.arguments = intent.extras

                // Add the fragment to the 'fragment_container' FrameLayout
                supportFragmentManager.beginTransaction()
                        .add(R.id.fragment_container, firstFragment).commit()
            }
        }
    }
    

Java

    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);

            // Check that the activity is using the layout version with
            // the fragment_container FrameLayout
            if (findViewById(R.id.fragment_container) != null) {

                // However, if we're being restored from a previous state,
                // then we don't need to do anything and should return or else
                // we could end up with overlapping fragments.
                if (savedInstanceState != null) {
                    return;
                }

                // Create a new Fragment to be placed in the activity layout
                HeadlinesFragment firstFragment = new HeadlinesFragment();

                // In case this activity was started with special instructions from an
                // Intent, pass the Intent's extras to the fragment as arguments
                firstFragment.setArguments(getIntent().getExtras());

                // Add the fragment to the 'fragment_container' FrameLayout
                getSupportFragmentManager().beginTransaction()
                        .add(R.id.fragment_container, firstFragment).commit();
            }
        }
    }
    

由于该 Fragment 是在运行时被添加到 FrameLayout 容器的,而不是利用 <fragment> 元素在 Activity 布局中进行定义,因此可以从 Activity 中移除该 Fragment,并将其替换为其他 Fragment。

替换 Fragment

替换 Fragment 的过程与添加 Fragment 类似,但需要调用 replace() 方法,而非 add()

请注意,当您执行替换或移除 Fragment 等 Fragment 事务时,通常最好让用户能够回退并“撤消”更改。要让用户能够回退所执行的 Fragment 事务,您必须先调用 addToBackStack(),然后再提交 FragmentTransaction

注意:当您移除或替换一个 Fragment 并向返回栈添加相应事务时,系统会停止(而非销毁)移除的 Fragment。如果用户执行回退操作进行 Fragment 恢复,该 Fragment 将重新启动。如果您不向返回栈添加相应事务,则系统会在您移除或替换 Fragment 时将其销毁。

替换 Fragment 的示例:

Kotlin

    // Create fragment and give it an argument specifying the article it should show
    val newFragment = ArticleFragment()
    Bundle args = Bundle()
    args.putInt(ArticleFragment.ARG_POSITION, position)
    newFragment.arguments = args

    val transaction = supportFragmentManager.beginTransaction().apply {
      // Replace whatever is in the fragment_container view with this fragment,
      // and add the transaction to the back stack so the user can navigate back
      replace(R.id.fragment_container, newFragment)
      addToBackStack(null)
    }

    // Commit the transaction
    transaction.commit();
    

Java

    // Create fragment and give it an argument specifying the article it should show
    ArticleFragment newFragment = new ArticleFragment();
    Bundle args = new Bundle();
    args.putInt(ArticleFragment.ARG_POSITION, position);
    newFragment.setArguments(args);

    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    // Replace whatever is in the fragment_container view with this fragment,
    // and add the transaction to the back stack so the user can navigate back
    transaction.replace(R.id.fragment_container, newFragment);
    transaction.addToBackStack(null);

    // Commit the transaction
    transaction.commit();
    

addToBackStack() 方法采用一个可选的字符串参数,用于为事务指定一个唯一的名称。除非您计划使用 FragmentManager.BackStackEntry API 执行高级 Fragment 操作,否则不需要该名称。