Android支持库中Fragment的使用及参数传递

来源:互联网 发布:梦幻花园无法加载 网络 编辑:程序博客网 时间:2024/04/30 03:35

自己好久不写APK的界面部分了,平时的工作主要集中在APK的控制层和模型层,
或者干脆就深入到Android框架及更底层的部分了,因此许多界面的知识都有些遗忘了。

这次主要结合Android权威指南中的例子,回忆一下以前的知识,以博客的方式记录一下,
Android中使用android.support.v4.app.Fragment的基本方式。

一、Fragment的使用
我们知道Fragment的生命周期由嵌入的Activity管理。
Activity托管Fragment主要有如下两种方式:
1、在Activity的布局中添加fragment;
2、在Activity的代码中加入fragment。

第一种方式就是使用布局fragment。这种方式简单但不够灵活。
在Activity的布局中添加fragment,就等同于将fragment及其视图与activity的视图绑定在一起,
并且在activity的生命周期过程中,无法切换fragment视图。

第二种方式比较复杂,但也是唯一可以在运行时控制fragment的方式。
我们可以自行决定何时添加fragment、移除fragment等。

因此,为了追求真正灵活的UI设计,就必须以第二种方式,即以代码的方式添加fragment。
接下来,我们就看看以代码添加fragment的步骤。
考虑到兼容性,我们主要使用的是android支持库中的fragment,即android.support.v4.app.Fragment等相关类。

Fragment终究是需要嵌入到activity中,因此首先需要定义Activity的布局,示例如下:

<?xml version="1.0" encoding="utf-8"?><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"></FrameLayout>

示例Activity的布局及其简单,就是定义一个FrameLayout,作为Fragment布局的容器。

对应的Activity代码如下:

//定义一个抽象的父类public abstract class SingleFragmentActivity extends FragmentActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_fragment);        //FragmentManager管理fragment队列等        FragmentManager fm = getSupportFragmentManager();        //R.id.fragment_container是fragment容器的id        //先从Fragment队列中查找是否有容器id对应的fragment        Fragment fragment = fm.findFragmentById(R.id.fragment_container);        if (fragment == null) {            //由子类实现,创建Fragment            fragment = createFragment();            //创建一个FragmentTransaction(fragment事务)            fm.beginTransaction()                    //告诉fm, fragment应该放置的位置,同时用容器id作为fragment的标识符                    //当需要向Activity添加多个fragment时,通常要分别为每个fragment创建具有不同资源ID的容器                    .add(R.id.fragment_container, fragment)                    .commit();        }    }    protected abstract Fragment createFragment();}

从上面的代码可以看出,FragmentManager首先利用findFragmentById接口,
利用容器视图的资源ID,在fragment队列中查找对应的fragment。
当利用容器视图的资源ID找不到对应的fragment时,才创建新的fragment。

这是因为,当设备旋转或回收内存时,Android可能销毁当前的Activity。
当Activity被销毁时,它的FragmentManager会将fragment队列保存下来。

当Activity重建时,会再次调用其onCreate方法。
此时新的FragmentManager会首先获取保存的队列,然后重建fragment队列,从而恢复到原来的状态。

在SingleFragmentActivity的基础上,使用Fragment就变得很容易了。示例如下:

public class CrimeActivity extends SingleFragmentActivity {    private static final String EXTRA_CRIME_ID =            "stark.a.is.zhang.criminalintentapp.crime_id";    @Override    //子类只需要实现createFragment方法即可    protected Fragment createFragment() {        ...............        return CrimeFragment.newInstance(crimeId);    }    ...........}

看看CrimeFragment的实现:

public class CrimeFragment extends Fragment{    ...........    public static CrimeFragment newInstance(UUID crimeId) {        ..............        CrimeFragment crimeFragment = new CrimeFragment();        ..............        return crimeFragment;    }    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        .............    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState) {        //加载Fragment自己的视图        View v = inflater.inflate(R.layout.fragment_crime, container, false);        .............        return v;    }}

Activity的FragmentManager负责调用队列中fragment的生命周期方法。
Activity添加fragment供FragmentManager管理时,Fragment的onAttach、onCreate以及onCreateView方法会被调用。
托管Activity的onCreate方法执行后,Fragment的onActivityCreated方法也会被调用。

FragementManager将保证fragment与Activity的运行状态保持一致。
例如,在运行态的Activity中添加Fragment时,FragmentManager将立即调用Fragment的
onAttach、onCreate、onCreateView、onActivityCreated、onStart和onResume方法,使Fragment的运行状态“追赶”上Activity。
一旦fragment的状态与Activity保持了一致,FragmentManager就会接受操作系统的指示,
按照Activity的状态,调用Fragment的其他生命周期方法。

以上是Fragment的基本用法,接下来看看Fragment之间的参数传递。

二、Fragment通过Activity进行参数传递
Fragment可以利用startActivity的方式启动另一个Activity,并在Intent中携带额外的信息。
假设另一个Activity,也是由Fragment来完成界面的布局,那么目的Fragment可以从对应的托管Activity获取传递的参数。
例如:

public class CrimeFragment extends Fragment {    .......    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        //从托管Activity中获取Intent,然后再获取额外信息        UUID crimeId = (UUID) getActivity().getIntent().getSerializable(ARG_CRIME_ID);        ................    }}

这种做法比较简单,但破坏了Fragment的封装性。
Fragment与特定的Activity绑定了,不再是可复用的构建单元。

针对上述问题,一种比较好的解决方案是使用Fragment的argument。
每个fragment实例都可以附带一个Bundle对象。
该Bundle对象包含键值对,一个键值对就是一个argument。
要附加argument给fragment,需要调用Fragment.setArguments方法,
且必须在fragment创建后、添加到activity前完成。

对于上面代码中例子,可以在Fragment的构造函数中,完成argument的设置:

public class CrimeActivity extends SingleFragmentActivity {    .................    @Override    //父类在将fragment加入到FragmentManager前,先调用该方法构造Fragment    protected Fragment createFragment() {        //获取启动的Intent,从中得到消息        UUID crimeId = (UUID) getIntent().getSerializableExtra(CrimeActivity.EXTRA_CRIME_ID);        //调用Fragment提供的接口,设置arguments        return CrimeFragment.newInstance(crimeId);    }    .....}

再看看Fragment的完整实现:

public class CrimeFragment extends Fragment{    ...........    //个人觉得,CrimeFragment可以提供多种newInstance函数,以满足不同Activity的使用需求    //达到Fragment与Activity解耦的目的    public static CrimeFragment newInstance(UUID crimeId) {        //在Fragment创建时,创建bundle        Bundle args = new Bundle();        args.putSerializable(ARG_CRIME_ID, crimeId);        CrimeFragment crimeFragment = new CrimeFragment();        //利用fragment的setArguments方法,将bundle设置到arguments中        crimeFragment.setArguments(args);        return crimeFragment;    }    @Override    //fragment被Activity加入到FragmentManager后,onCreate函数被调用    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        //利用getArguments函数,得到Bundle对象,于是可以获取之前存入的数据        //如果被多个Activity使用,那么获取参数时就需要对应的判断了        UUID crimeId = (UUID) getArguments().getSerializable(ARG_CRIME_ID);        ................    }}

通过上述的方式,可以一定程度上实现Fragment与托管Activity的解耦的目的。

在本文的最后,稍微提一点:
Fragment可以覆盖Activity的startActivityForResult方法和onActivityResult方法,于是可以处理启动Activity后的返回值。

不过Fragment没有定义setResult方法,因此目的Fragment如果需要设置返回结果,
需要调用托管Activity的setResult方法,即在Fragment中,需要调用类似如下代码:

...........getActivity.seResult(Activity.RESULT_OK, data);...........

三、同一个Activity托管Fragment之间的参数传递
源Fragment向目的Fragment传递参数时,与上文一样,仍然需要利用Fragment的arguments。
不过,由于是同一个Activity托管的两个Fragment,因此可以将它们之间的通信关系确定下来。
如下面的示例代码所示:

.............mDateButton.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View view) {        FragmentManager fm = getFragmentManager();        //目的Fragment为DatePickerFragment,在newInstance时,源Fragment将参数写入到其arguments中        DatePickerFragment dialog = DatePickerFragment.newInstance(mCrime.getDate());        //调用目的Fragment的setTargetFragment方法,此时就将源、目的Fragment之间的关系确定下来了        //REQUEST_DATE与startActivityForResult中的request_code一致        dialog.setTargetFragment(CrimeFragment.this, REQUEST_DATE);        //显示目的Fragment        dialog.show(fm, DIALOG_DATE);    }});...............

源Fragment和目的Fragment之间的关系确定后,目的Fragment就可以通过如下方式,将消息发送给源Fragment:

...............private void sendResult(int resultCode, Date date) {     //判断是否有绑定的源Fragment     if (getTargetFragment() == null) {         return;     }     Intent intent = new Intent();     intent.putExtra(EXTRA_DATE, date);    //直接调用源Fragment的onActivityResult返回结果     getTargetFragment().onActivityResult(            getTargetRequestCode(), resultCode, intent);}..........
0 0
原创粉丝点击