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

自己好久不写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);
}
..........

你可能感兴趣的:(Android支持库中Fragment的使用及参数传递)