Android提供一个JAR文件的支持库,允许你在比较低的版本中使用一些新版本的API。例如,支持库提供了碎片API让你在Android1.6中可以使用。
这个课程教你怎么设置支持库,使用碎片创建一个动态程序UI。
使用支持库建立你的工程
- 使用SDK管理器下载支持库。
- 在工程更目录创建libs文件夹。
- 把JAR文件复制到libs目录中。
比如,API级别4的支持库的位置是:<sdk>/extras/android/support/v4/android-support-v4.jar - 修改你的清单文件,设置最低版本API级别为4,目标版本级别为最新:
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="15" />
引用支持库API
支持库包含的各种API要么被已经被添加到最新的android版本中,要么更本不存在于平台中,而仅仅为低版本开发特别特征提供额外的支持。
你可以在
android.support.v4.*找到平台文档中支持库的API参考文档。
警告:确保你不是在老版本中偶尔使用新的API,确定你从android.support.v4.app包中引用Fragment类和相关API。
import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; ...
当使用支持库创建一个主碎片activity,你必须继承FragmentActivity类,而不是继承传统的Activity类。下一个课程有更多介绍。
你可以认为碎片是部分activity的模块化。它有自己的生命周期,接受自己输入事件,可以在activity运行时被添加和删除(类似一个子activity,你可以在不同的activity中复用)。这个课程展示了怎么使用支持库扩展Fragment类,让Android1.6这样的老版本也能兼容你的程序。
提示:如果你的最小API级别是11或者更高,你就不需要使用支持库,可以直接使用框架构建Fragment类和相关API。这个课程关注的是使用支持库中的API,和那些直接包含在平台中的版本,这些API使用特别的包名和稍微不同的API名称。
创建一个碎片类
创建一个碎片需要扩展Fragment类,在程序中覆盖关键生命周期方法,类似于使用Activity类。
一个不同点是,你需要在onCreateView()回调中定义布局,事实上,这是让碎片运行起来唯一一个使用的回调。例如,下面是一个简单碎片,指定了它自己的布局:
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) { // 给碎片填充布局 return inflater.inflate(R.layout.article_view, container, false); } }
和activity一样,一个fragment需要实现其他生命周期回调,允许你管理它从activity被添加或者删除时的状态,像activity转换她的生命周期状态一样。例如,当activity的onPause()函数被调用时,activity中所有的碎片也都会接收一个onPause()调用。
更多关于碎片生命周期函数的信息,可以参考这里:
Fragments
使用XML添加一个碎片到activity中
虽然碎片是可重用,模块化的UI组件,每个Fragment类的实例都必须关联一个父类FragmentActivity。你可以定义每个碎片在你的activity布局XML文件中来实现这个联系。
提示:FragmentActivity是一个特别的activity,支持库提供它来管理API级别11以下的碎片。如果你支持的最低版本是11或者更高,你可以直接使用Activity。
下面是一个布局文件的实例,在设备是large屏幕(在目录名中指定了large限定符)时,添加两个碎片到一个activity中。
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文件中为activity添加碎片时,你不可以在运行时移除碎片。如果你打算在用户交互时换入和换出碎片,你必须在activity第一启动时添加碎片,请看下一课。
当设计你的程序去支持大部分屏幕尺寸时,你可以在不同的布局文件中复用你的碎片,在不同的屏幕空间中优化你的用户体验。
例如,手机中可能一次只适合显示一个碎片,相反的,在大屏幕设备中,你可能想在一个界面中挨着显示很多个碎片。
图解:一个activity在不同屏幕尺寸的显示效果。在大屏幕中,两个碎片紧挨着显示,在手机中,一次只显示一个碎片。
FragmentManager类提供一些函数,让你可以在程序运行时添加,删除和替换碎片,从而产生动态显示效果。
运行中添加一个碎片
上一个教程中,我们在XML文件中添加碎片,不同的是,你现在可以在activity运行期间添加一个碎片,在activity生命周期中改变碎片是非常有必要的。
要实现碎片的添加和删除,你需要使用FragmentManager去创建一个FragmentTransaction,它提供API去添加,删除,替换和执行其他碎片转换。
如果你想允许碎片被移除和替换,你需要在activity的onCreate()函数中初始化碎片。
一个重要的规定是:要操作碎片,尤其是运行中添加碎片,碎片必须有一个view容器,这个容器包含了碎片的布局。
下面这个布局和上一个课程的有所不同,它一次只显示一个碎片。为了能用其他碎片替换这一个碎片,activity布局需要包含一个空的FrameLayout碎片容器。
需要提醒的是,下面的文件名和上一课的一样,但是目录没有large限定符,所以,这个布局是当设备屏幕尺寸小于large的时候才会被应用,因为屏幕太小,不够同时显示多个碎片。
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中,调用getSupportFragmentManager()去取得一个FragmentManager。然后调用beginTransaction()去创建一个FragmentTransaction,再调用add()去添加一个碎片。
你可以使用FragmentTransaction去处理多个碎片,当你确认好要改变碎片时,你必须调用commit()。
例如,下面演示了怎么添加一个碎片:
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使用的布局中是否有碎片容器 if (findViewById(R.id.fragment_container) != null) { // 如果我们只是恢复先前的状态, // 那么我们什么都不用做,只是返回或者我们可以覆盖先前的碎片 if (savedInstanceState != null) { return; } // 创建一个碎片实例 HeadlinesFragment firstFragment = new HeadlinesFragment(); // 使用特别的指令从一个Intent开始一个activity, // 把Intent中附加的数据做为碎片的参数。 firstFragment.setArguments(getIntent().getExtras()); // 添加碎片到碎片容器中。 getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, firstFragment).commit(); } } }
因为随便是在运行中被添加到FrameLayout容器中的,所以activity可以用一个不同的碎片替换它,或者是删除它。
用另外一个碎片替换当前碎片
替换一个碎片的步骤和添加一样简单,只需要把add改为replace就可以了。
记住,当你进行碎片操作时,比如替换或者删除,你需要让用户可以返回和撤销改变。为了实现这些,你需要在提交碎片操作前调用addToBackStack()。
提示:当你删除或者替换一个碎片,并且添加了这些操作在后退堆栈,碎片移除会被停止(没有被销毁),如果用户想返回恢复碎片,它就会被重新开启。如果没有加入后退堆栈,这个碎片就会在替换或者删除的时候被销毁。
替换的例子:
// 创建一个碎片,传递一个参数给它,指定显示的内容。 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();
addToBackStack()函数需要传递一个字符串参数,为操作指定一个独一无二的名称,这个名称不是必须的,除非你计划开始使用FragmentManager.BackStackEntryAPI执行高级的碎片操作。
了能重复使用碎片UI组件,你需要创建一个完全独立,模块化的组件,这个组件可以定义自己的样式和行为。一旦你定义了一个可重用的碎片,你可以使用一个activity关联它们,通过程序逻辑链接它们成为一个完整的组合UI。
为了实现碎片和activity的通信,你可以在碎片类中定义一个接口,然后在activity中实现这个接口。碎片可以在onAttach()生命周期函数中取得接口的实现,然后调用接口方法实现通信。
为了从碎片中接收事件回调,拥有这个碎片的activity必须实现定义在碎片类中的接口。
通过findFragmentById()取得Fragment实例,然后activity就可以直接调用碎片的公共函数发送信息给这个fragment。
通常你希望碎片间可以通信,比如基于用户事件改变内容。所有碎片间的通信都是通过activity实现,两个碎片不能直接通信。
定义一个接口
为了实现碎片和activity的通信,你可以在碎片类中定义一个接口,然后在activity中实现这个接口。碎片可以在onAttach()生命周期函数中取得接口的实现,然后调用接口方法实现通信。
这里是一个碎片和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() + " must implement OnHeadlineSelectedListener"); } } ... }
现在碎片可以使用OnHeadloneSelectedListener接口的mCallback实例调用onArticleSelected()函数(或者接口中其他函数)发送信息给activity了。
例如,当用户点击list选项时,碎片中的下面这个方法会被调用。碎片使用回调接口发送事件给父类activity。
@Override public void onListItemClick(ListView l, View v, int position, long id) { // Send the event to the host activity mCallback.onArticleSelected(position); }
实现接口
为了从碎片中接收事件回调,拥有这个碎片的activity必须实现定义在碎片类中的接口。
例如,下面的代码实现了上面例子中定义的接口:
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(Uri articleUri) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article } }
发送一个信息给碎片
通过findFragmentById()取得Fragment实例,然后activity就可以直接调用碎片的公共函数发送信息给这个fragment。
例如,上面的函数让activity显示了一个碎片选项的数据,activity也可以通过信息接收函数去显示其他碎片数据:
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(); } } }