使用Fragment建立动态UI
为了在Android上为用户提供动态的、多窗口的交互体验,我们需要将UI组件和Activity操作封装成模块进行使用,使得我们可以在activity中对这些模块进行切入切出操作。可以用Fragment来创建这些模块,Fragment就像一个嵌套的activity,拥有自己的布局(layout)并管理自己的生命周期。接收自己的输入事件,可以在acvitity运行过程中添加或者移除(有点像"子activity",可以在不同的activity里面重复使用)。一个fragment定义了自己的布局后,它可以在activity中与其他的fragment生成不同的组合,从而为不同的屏幕尺寸生成不同的布局(一个小的屏幕一次也许只能一个fragment,大的屏幕则可以显示更多)。
一 . 创建一个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(LayoutInflaterinflater, ViewGroup container,
Bundle savedInstanceState){
// Inflate the layout for this fragment
return inflater.inflate(R.layout.article_view,container,false);
}
}
就像activity一样,当fragment从activity添加或者移除、当activity生命周期发生变化时,fragment通过生命周期回调函数管理其状态。例如,当activity的onPause()被调用时,它里面的所有fragment的onPause()方法也会被触发。
二 .用XML将Fragment添加到Activity
fragments是可重用的,模块化的UI组件,每个Fragment的实例都必须与一个FragmentActivity关联。我们可以在activity的XML布局文件中定义每一个fragment来实现这种关联。FragmentActivity是Support Library提供的一个特殊activity ,用于处理API11版本以下的fragment。如果我们APP中的最低版本大于等于11,则可以使用普通的Activity。
<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:id="@+id/headlines_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment
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(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
}
}
如果用的是 v7 appcompat library,activity应该改为继承ActionBarActivity,ActionBarActivity是FragmentActivity的一个子类。当通过XML布局文件的方式将Fragment添加进activity时,Fragment是不能被动态移除的。如果想要在用户交互的时候把fragment切入与切出,必须在activity启动后,再将fragment添加进activity。
三 .建立灵活动态的UI
比如,一个手持设备可能适合一次只有一个fragment的单面板用户交互。而在更大屏幕尺寸的平板电脑上,我们可能更想要两个fragment并排在一起,用来向用户展示更多信息。两个fragments,在同一个activity不同屏幕尺寸中用不同的配置来展示。在大屏幕上,两个fragment被并排放置,在手持设备上,一次只放置一个fragment,所以在用户导航中,两个fragment必须进行替换。FragmentManager类为在activity运行时对fragment进行添加,移除,替换等操作提供了方法,来实现动态的用户体验
四 .在Activity运行时动态添加Fragment
比起用<fragment>标签在activity的布局文件中定义fragment,我们还可以在activity运行时动态添加fragment,如果打算在activity的生命周期内替换fragment,这是必须的。为了执行fragment的增加或者移除操作,必须通过FragmentManager创建一个FragmentTransaction对象,FragmentTransaction提供了用来增加、移除、替换以及其它一些操作的APIS。如果我们的activity允许fragment移除或者替换,我们应该在activity的onCreate()方法中添加初始化fragment(s)。
运用fragment(尤其是那些在运行时添加的)的一个很重要的规则就是在布局中必须有一个容器View,fragment的layout将会放在这个view里面。在activity中,用Support Library APIs调用 getSupportFragmentManager()方法获取FragmentManager对象,然后调用 beginTransaction() 方法创建一个FragmentTransaction对象,然后调用add()方法添加一个fragment.可以使用同一个 FragmentTransaction进行多次fragment事务。完成这些变化操作,准备开始执行改变时,必须调用commit()方法。
下例显示了如何添加一个fragment到之前的layout中
importandroid.os.Bundle;
importandroid.support.v4.app.FragmentActivity;
publicclassMainActivityextendsFragmentActivity {
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
if (findViewById(R.id.fragment_container) !=null) {
if (savedInstanceState !=null) {
return;
}
HeadlinesFragment firstFragment =newHeadlinesFragment();
firstFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
}
五 .替换Fragment
替换fragment的过程类似于添加过程,只需要将add()方法替换为 replace()方法。记住在执行fragment事务时,如移除或者替换,我们经常要适当地让用户可以向后导航与"撤销"这次改变。为了让用户向后导航fragment事务,我们必须在FragmentTransaction提交前调用addToBackStack()方法。
Note:当移除或者替换一个fragment并把它放入返回栈中时,被移除的fragment的生命周期是stopped(不是destoryed).当用户返回重新恢复这个fragment,它的生命周期是restarts。如果没有把fragment放入返回栈中,那么当它被移除或者替换时,其生命周期是destoryed。
下面是一个fragment替换的例子
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()方法提供了一个可选的String参数为事务指定了一个唯一的名字。除非打算用FragmentManager.BackStackEntry APIs来进行一些高级的fragments操作,这个名字不是必须的。
六 . Frgament 之间的交互
通常fragment之间可能会需要交互,比如基于用户事件改变fragment的内容。所有fragment之间的交互需要通过他们关联的activity,两个fragment之间不应该直接交互。
1 . 定义一个接口
为了让Fragment与activity交互可以在Fragment中定义一个接口并在Activity中实现,Fragment在它们的生命周期的onAttach()方法中获取接口的实现然后调用接口的方法与Activity交互。
public class TestFragment extends ListFragment { OnHeadlineSelected mCallBack; Int position; public interface OnHeadlineSelected{ public void onArticleSelected(int position); } public void onAttach(Activity activity){ super.onAttach(activity); mCallBack = (OnHeadlineSelected)activity; } public void getPosition(){ this.position = position; } }
现在Fragment就可以通过OnHeadlineSelectedListener 接口实例mCallBack中的OnArticleSelected()方法与Activity传递消息。
比如在Fragment中点击ListView的一个条目时调用OnArticleSelected()方法Fragment通过回调接口来传递事件给父Activity。
2 . 在Activity中实现接口
public class TestActivity extends Activity implements TestFragment.OnHeadlineSelected{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onArticleSelected(int position) { /** * doing something */ } }
3 . 传消息给Fragment
在Activity中获取Fragment实例然后直接调用getPosition()方法向Fragment传递消息。
假定上面的Activity中包含另外一个Fragment这个Fragment用来展示从上面的回调方法中返回的指定的数据,在这种情况下Activity可以把回调方法中接收到的消息传递给这个展示数据的Fragment。
public class TestActivity extends FragmentActivity implements TestFragment.OnHeadlineSelected{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment); TestFragment fragment = (TestFragment)getSupportFragmentManager().findFragmentById(R.id.fragment); if(fragment != null) { fragment.setPosition(0); } else { TestFragment testFragment = new TestFragment(); Bundle bundle = new Bundle(); bundle.putInt("position", 0); testFragment.setArguments(bundle); android.support.v4.app.FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.fragment, testFragment); transaction.commit(); } } @Override public void onArticleSelected(int position) { /** * doing something */ } }