要在android上创建一个动态的多窗口用户界面,你需要封装UI组件和activity行为到模块中,这样你就能放入activity或者从activity中拿出. 你可以用Fragment类来创造这些模块,这些module有点像一个嵌套的activity,在它里面你也能定义它自己的布局和管理它自己的生命周期.
当一个fragment制定了它的布局,它可以与其他的fragment进行组合放在activity中来修改在不同屏幕尺寸下的布局配置(小屏幕一次只显示一个fragment,大屏幕显示两个或更多的fragment).
这个类向你展示了怎么样用fragment创建一个动态的用户体验和怎么样对不同的屏幕尺寸的设备最优化你的app的用户体验,fragment最低支持android1.6版本.
创建一个Fragment
你可以把fragment看作一个activity的一个拥有自己的生命周期,自己的输入事件的模块区,并且你可以增加到activity或者从activity中移出(可以说像是一个“子activity”,你可以在不同的activity中来使用它). 这节课我们可以学会怎样用Support Library继承Fragment类,这样你的app就能在最低android1.6版本的系统上运行.
注意: 如果你决定你的app的最低SDK版本为11或者以上,你不需要使用Support Library,而直接使用对应的API中框架里的的Fragment类,但是要注意这里用的是Support Library(用的是指定的包签名的版本)中的Fragment,所以可能会与SDK中的版本API名称稍微有点不同.
在开始之前,你必须在你的android项目中使用Support Library,如果你以前没用过Support Library, 根据Support Library的安装文档来给你的项目添加V4 library支持. 然而 ,你也能使用v7 appcompat library来在你的activity中包含actionbar,它与Android2.1(API版本为7)是适配的,它也包含Fragment的API.
创建一个Fragment类
创建一个fragment,继承Fragment类,重写生命周期方法来插入你的app逻辑,与你所认识的activity十分类似.
当创建fragment时你必须使用onCreateView回调来定义你的布局(Activity中是onCreate()).事实上,这是唯一的一个你需要重写使得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在它的生命周期过渡时能管理它的状态,例如:当activity的onPause()方法被调用,activity中的任何fragment也会调用它们的onPause().
更多关于fragment生命周期和回调方法在fragment开发指导上是可用的.
使用xml把Fragment加入到Activity
由于fragment是可以复用的,模块化的UI组件,每一个fragment的实例必须与父FragmentActivity相关联,你能通过在你的activity布局xml文件中定义每一个fragment来把它们关联起来.
注意:FragmentActivity是一个在Support Library中提供的一个特殊的Activity,专门用来处理系统API低于11的系统中的fragment. 如果系统最低的版本为11或者更高,那么你可以使用正常的Activity.
这里是一个添加了两个fragment到一个activity中的布局文件(这里考虑的是大屏幕“large”,具体资源文件夹为res/layout-large/.).
<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 library,你的activity应该继承ActionBarActivity,它是FragmentActivity的一个子类(更多信息查看ActionBar).
注意:当你通过在布局xml文件中添加一个fragment到activity,你不能在运行时移除该fragment.如果你想在与用户交互的时候把你的fragment拿出或者放入,你必须在activity首次启动的时候把fragment添加到activity.下面我们会讲到.
构造一个灵活的UI
当设计你的app支持大量的屏幕尺寸的时候,你应该在不同的布局配置中复用你的fragment来使的根据可用的屏幕空间最优化用户体验.
例如,在手机设备对一个用户界面一次只展示一个fragment会比较合适.相反的,你也许想要在更宽的设备上使用多个fragment来展示更多的信息给用户.
FragmentManager类提供了方法允许你在为了创造动态体验在运行时增加fragment,移除fragment或者替代fragment到activity.
在运行时增加一个fragment到activity
不在activity布局文件重定义fragment,直接在运行是添加一个fragment到activity,这在当你计划在activity生命周期中改变fragment时是很有必要的
为了增加或者移除fragment,你必须使用FragmentManager 类来获得一个FragmentTransaction,FragmentTransaction有现成的API增加,移除,替换或者执行其他的fragment事务.
如果你的activity允许fragment被移除或者替换,你应该在activity的onCreate方法中增加一些最初的fragment.
处理fragment的时候要记住一条准则.当在运行是添加一个fragment,你的activity必须包含一个vie容器,在这个容器里面你能插入fragment.
以下的layout是和先前的layout一样只在同一时刻显示一个fragment,为了用另一个fragment替换掉这一个,这个activity必须布局必须包含一个空的FrameLayout作为Fragment容器.
注意到这里文件名和上面的一样,但是资源文件夹没有large属性,所以这个布局文件是为在同一时刻只能显示一个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 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已经在运行时添加到了FramLayout容器中,而不是在activity的布局文件定义,所以这个activity可以移除fragment或者用另外一个来替换它.
用另一个Fragment来替换这个Fragment
替换一个fragment和添加一个是很相似的,但是需要调用replace()而不是add()方法.
我们要记住当你执行fragment事务时,比如替换或者移除fragment,通常允许用户撤销操作都是很好的用户体验. 为了允许用户回退,你必须在提交事务之前调用addToBackStack()方法.
注意:当你移除或者替代fragmtn并把这些事务添加到返回栈,被移除的fragment将会处于stopped状态(没有被销毁)。如果用户回退来复原fragment,fragmnent会调用自身的restart方法,如果你没有把这些事务添加到返回栈,则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();
addToBackStack() 方法有一个可选的string参数指定了事务的唯一的名称. 这个名称除非你计划使用 FragmentManager.BackStackEntry API执行更进一步的fragment运算否则是不需要的.
与其他的Fragment进行通信
为了复用Fragment UI组件,你应该做好每一个fragment使得其是自包含的模块组件,使得它有自己的布局和行为,一旦你已经定义了这些可以复用的Fragments,你可以把它们同时放在一个Activity并用应用逻辑来连接他们来理解并控制整个混合的UI.
你将会常常想要一Fragment和另一个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可以传递信息到activity通过使用OnHeadlineSelectedListener 接口中的mCallback实例来调用onArticleSelected()方法(或者接口中的其他方法).
例如,fragment中以下的方法在用户点击list的子item时会被调用,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实现了上面例子中的接口
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
fragment所在的activity可以通过findFragmentById()方法获取fragment实例,然后调用fragment中的方法来传递消息到fragment.
例如, 想像上面的activity也许包含另一个fragment,这个fragment用来显示由上面的回调方法返回的数据对应的item.在这种情况下,activity能传递从回调方法收到的信息到另外一个将要显示item的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();
}
}
}