使用Fragment创建动态用户界面(翻译)

使用Fragment创建动态用户界面

在Android上创建动态的并且多窗格的用户界面,你需要使用模块封装UI组件和Activity的行为,并且这些模块你可以将它们换入和换出你的Activity中。你可以使用Fragment类来创建这些模块,它表现的就像是嵌套的Activity,可以定义自己的布局并管理自己的生命周期。

当一个Fragment可以指定它自己的布局时,它也可以在一个Activity中和其他的Fragment组合在一起进行配置,从而为不同的屏幕大小修改你的布局配置(比如在小的屏幕上可能一次只显示一个Fragment,但在一个大的屏幕上可以显示两个或者更多)。

这个课程告诉你如何使用Fragment创建一个动态的用户体验并且为不同屏幕大小的设备优化你的app的用户体验。这些支持运行最老Android1.6版本系统的设备。

使用Android支持库

Android支持库提供一个带有API库的JAR文件,这些API库允许你的app运行在更早版本的Android上时,可以使用一些最近的Android API。举个例子,支持库提供了一个版本的Fragment API,使你可以使用Android1.6(API level 4)及以上版本才能使用的Fragment API。

这一课告诉你如何设置app来使用支持库,从而可以使用Fragment来创建动态app用户界面。

设置你的工程使用支持库

设置你的工程:

  1. 使用SDK Manager下载Android支持库
  2. 在你的Android工程的顶级层次创建一个libs目录
  3. 定位你想使用的库的jar文件,并将它拷贝到libs目录。举个例子,支持API level 4及以上版本的支持库是<sdk>/extras/android/support/v4/android-support-v4.jar
  4. 更新你的manifest文件,设置最小API级别为4,目标API级别为最新发布的版本。
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="15" />

使用Fragment创建动态用户界面(翻译) 

 

导入支持库API

支持库包含大量的API,要么是最近版本Android增加的要么是在平台上完全不存在的,它们只是在你开发特殊的应用程序特性时提供额外的支持。

你可以在平台文档库(android.support.v4.*)中找到所有的API支持库的指导文档。

警告:为了确保你不会在老的系统版本上意外的使用新的API,请确定你是从android.supoort.v4.app包中导入Fragment类和相关的API。

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
...

 当使用Android支持库时,创建一个使用Fragment的Activity类时,你必须继承FragmentActivity类而不是传统的Activity类,你将会在下一课看到一些关于Fragment和Activity的示例代码。

 创建一个Fragment

你可以认为Fragment是Activity的一个单元段,它拥有自己的生命周期,接收它自己的输入事件,可以在Activity运行的时候被增加和删除(就像是一种子Activity,你可以在不同的Activity中重用它)。这一课将告诉你如何使用Android支持库继承Fragment类,因此你的app可以仍然兼容运行像Android1.6这么旧的系统的设备。

注意:如果你因为其他原因决定你的app要求的最低的API level为11的话,你就不需要使用Android支持库了,可以使用框架自带的Fragment类及相关的API。请注意这一课重点关注使用来自支持库的API,它使用一个特殊的包签名,并且有的时候API的名字也和平台自带的略有不同。

创建一个Fragment类

为了创建一个Fragment,需要继承Fragment类,然后重写一些关键的生命周期方法来插入你自己的app的逻辑,就类似你处理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) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.article_view, container, false);
    }
}

 就像一个Activity,一个Fragment应该实现其他的生命周期方法,从而允许你管理它的状态随着它被从一个Activity增加或删除,就像Activity在它的生命周期状态之间转换一样。例如,当一个Activity的onPause()方法被调用,Activity中的每个Fragment都会收到一个onPause()回调。

更多的关于Fragment生命周期和回调方法的信息可以在Fragment开发者指南里获得。

通过XML将一个Fragment添加到Activity中

虽然Fragment是可重用的模块化的UI组件,但是每个Fragment实例必须和一个父组件FragmentActivity关联在一起。你可以通过在Activity的布局xml文件中定义每一个Fragment来达到这个关联的目录。

注意:FragmentActivity是一个在支持库中提供的特殊的Activity,来在版本低于API level 11的系统上支持Fragment。如果你支持的最低的系统版本是API level 11或更高,则你可以使用一个普通的Activity。

这里有一个布局文件的例子,当设备屏幕被认为是大的时候,它添加两个Fragment到Activity中(通过目录名的large限定词来指定)。

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

 注意:当你通过在XML布局文件中定义Fragment的方式将一个Fragment添加到一个Activity中时,你不能在运行时移除这个Fragment。如果你计划在用户交互过程中换入和换出你的Fragment,你必须在Activity首次启动时添加Fragment到Activity中,就像下一课所示。

创建一个灵活的用户界面

当设计你的应用程序使其支持大范围的屏幕大小时,你能在不同的布局配置中重用你的Fragment,从而在可用的屏幕空间的基础上优化用户体验。

举个例子,在手持设备上,每次只为单个窗格用户界面显示一个Fragment可能是合适的。相反的,你可能想要在平板上设置并排的Fragment从而向用户显示更多的信息,因为平板上有一个更宽的屏幕。

 FragmentManager类提供方法允许你在运行时为Activity增加、移除和替换Fragment,从而可以创建一个动态的体验。

使用Fragment创建动态用户界面(翻译)
 

在运行时为Activity增加一个Fragment

与其在布局文件中为Activity定义Fragment,就像前面的课程展示的<fragment>元素一样,你可以在Activity运行期间为Activity增加Fragment。如果你计划在Activity的生命周期中更改Fragment的话,这很重要。

为了执行一个增加或移除Fragment的事务,你必须使用FragmentManager来创建一个FragmentTransaction,这个类提供API来增加、移除、替换和执行其他Fragment事务。

如果你的Activity允许Fragment被移除或替换的,你应该在Activity的onCreate()方法期间增加初始Fragment到这个Activity上。

处理Fragment时有一个重要的原则,尤其是那些你需要在运行增加的Fragment,那就是在布局中Fragment必须有一个容器视图,Fragment的布局将会在这个视图中。

下面这个布局是前面的课程中展示的一个可选布局,每次只展示一个Fragment。为了能够使用另一个Fragment替换这个,Activity的布局中包括一个空的FrameLayout用作Fragment的容器。

注意:这个文件的文件名和前一课中的布局文件是一样的,但是布局目录不包括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。

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 an instance of ExampleFragment
            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的过程和增加一个很像,只是要求调用replace()方法,而不是add()方法。

始终在大脑中记住,当你执行Fragment事务时,比如替换或者删除一个Fragment,允许用户导航返回并撤销这个改变通常是合适的。为了允许用户通过Fragment事务导航回来,你必须在commit FragmentTransaction之前调用addToBackStack()方法。

注意:当你移除或替换一个Fragment并把这个事务添加到返回栈中时,移除的Fragment是被停止了(不是销毁)。如果用户导航回来恢复这个Fragment,它重新启动了。如果你没有将这个事务添加到返回栈中,则这个Fragment在被移除或替换时被销毁了。

这里有一个使用另一个Fragment替换的例子:

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

 addBackStack()方法使用一个可选的字符串参数,用于为这个指定一个唯一的名字。这个名字不需要,除非你要使用FragmentManager.BackStackEntry API执行高级Fragment操作

和其他Fragment通信

为了重用Fragment UI组件,你应该将每个Fragment构建成完全独立的、模块化的组件,定义它们自己的布局和行为。一旦你已经定义好这些可重用的Fragment,你就能将它们和Activity关联在一起,并将它们和应用程序逻辑联系在一起从而实现完全合成的用户界面。

通常,你会想要一个Fragment和另一个通信,例如基于用户时间改变内容。所有的Fragment到Fragment之间的通信都是通过关联的Activity完成的。两个Fragment应该从不直接通信。

定义一个接口

为了让一个Fragment的通信取决于它的Activity,你可以在Fragment类中定义一个接口然后在Activity中实现它。Fragment在它的onAttach()声明周期方法期间获得这个接口的实现,从而可以通过调用接口方法来与Activity通信。

这里有一个Fragment同Activity通信的例子:

public class HeadlinesFragment extends ListFragment {
     OnHeadlineSelectedListener mCallback;

      // Container Activity must implement this interface
     public interface OnHeadlineSelectedListener {
         public void onArticleSelected(int position);
     }

     @Override
     public void onAttach(Activity activity) {
         super.onAttach(activity);
         
         // This makes sure that the container activity has implemented
         // the callback interface. If not, it throws an exception
         try {
             mCallback = (OnHeadlineSelectedListener) activity;
         } catch (ClassCastException e) {
             throw new ClassCastException(activity.toString()
                     + " must implement OnHeadlineSelectedListener");
         }
     }
     
     ... 
}

 现在Fragment可以通过使用OnHeadlineSelectedListener接口的mCallback实例调用onArticleSelected()方法(或者接口中的其他方法),发送消息给Activity。

例如,当用户点击一个列表项目时,Fragment中的如下方法被调用。Fragment使用回调接口来发布事件给父Activity。

@Override
     public void onListItemClick(ListView l, View v, int position, long id) {
         // Send the event to the host activity
         mCallback.onArticleSelected(position);
     }

 实现接口

为了接收从Fragment过来的事件回调,作为Fragment的宿主的Activity必须实现Fragment类中定义的接口。

例如,下面这个Activity实现了上面例子中Fragment定义的接口:

public static class MainActivity extends Activity
         implements HeadlinesFragment.OnHeadlineSelectedListener{
     ...
     
     public void onArticleSelected(int position) {
         // The user selected the headline of an article from the HeadlinesFragment
         // Do something here to display that article
     }
}

发送消息给Fragment

宿主Activity使用findFragmentById()函数获取Fragment的实例后,可以直接调用Fragment的公共方法发送消息给Fragment。

举个例子,设想上面所示的Activity可能包含另一个Fragment用于显示上面的回调函数返回的数据所指定的列表项。在这种情况下,Activity可以传递在回调函数中收到的信息给另一个将要显示这个列表项的Fragment。

public static class MainActivity extends Activity
         implements HeadlinesFragment.OnHeadlineSelectedListener{
     ...
 
     public void onArticleSelected(int position) {
         // The user selected the headline of an article from the HeadlinesFragment
         // Do something here to display that article
 
         ArticleFragment articleFrag = (ArticleFragment) 
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);
 
         if (articleFrag != null) {
             // If article frag is available, we're in two-pane layout...
 
             // Call a method in the ArticleFragment to update its content
             articleFrag.updateArticleView(position);
         } else {
             // Otherwise, we're in the one-pane layout and must swap frags...
 
             // Create fragment and give it an argument for the selected article
             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();
         }
     }
 
}

相关推荐