前几天我们分享了 Fragment 的基本使用,包括 Fragment 的概念、Fragment 设计原理、如何创建 Fragment、在 Fragment 中模拟返回栈、Fragment 的生命周期以及 Fragment 和 Activity 之间的通信等相关的内容,如果你对 Fragment 的基本使用还没有一个详细的了解的话,可以先阅读  Android Fragment 完全解析(上),今天我们来分享关于 Fragment 更多的内容


Android FragmentTransaction 用法 android fragment原理_android



一、Fragment 常用 API


1)如何获取 FragmentManager


如果我们引用的是 android.app.Fragment,那么我们就要用 getFragmentManager() 来获取 FragmentManager 实例

如果我们引用的是 android.support.v4.app.Fragment 下的 Fragment,那么我们就要用 getSupportFragmentManager() 来获取 FragmentManager 实例


2)如何开启事务


       通过上面获取到的 FragmentManager 实例引用来调用 beginTransaction() 方法来开启一个事务,当这个事务开启以后我们就可以保证对相应的 Fragment 进行一系列方法的操作


//调用 getSupportFragmentManager() 方法获取 FragmentManager
        FragmentManager fragmentManager = getSupportFragmentManager();
        //通过 beginTransaction() 方法开启一个事务
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();




3)操作 Fragment 相关的方法


fragmentTransaction.add() : 向 Activity 中添加一个 Activity

fragmentTransaction.remove() : 从 Acitivity 中移除一个 Fragment,如果这个被移除的 Fragment 没有添加到返回栈,那么这个 Fragment 将会被销毁

fragmentTransaction.replace() : 使用另一个 Fragment 替换当前的,实际上就是 remove() 和 add() 的合体

fragmentTransaction.hide() : 隐藏当前的 Fragment,仅仅是设置为不可见,并没有被销毁

fragmentTransaction.show() : 显示之前隐藏的 Fragment

fragmentTransaction.detach() : 会将 View 从 UI 中删除,和 remove() 不同,此时 Fragment 的状态依然由 FragmentManager 维护

fragmentTransaction.attach() : 重建 View 视图

fragmentTransaction.commit() : 提交一个事务


       上面这些方法基本就是操作 Fragment 的所有常用方式了,经常使用 Fragment,在使用这些方法的时候,也要特别注意,要搞清楚这些方法的详细用法,那个会销毁视图,那个会销毁实例,那个仅仅是隐藏等这些属性


二、Fragment Arguments


       我们来描述一个简单的应用场景,例如我们跳转到一个页面需要通过 Intent 传递数据到下一个 Activity 的 Fragment 中,那么一般来说我们通常会用以下方法来获取从上个界面传递过来的值:

public class ContentFragment extends Fragment {
    private static final String TAG = "ContentFragment";
    private String mArgument;
    private static String ARGUMENT = "data";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
	//获取上个界面传递过来的数据
        mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_content, container, false);
        Log.e(TAG, "onCreateView");
        return view;
    }
}




       如上所示,当然这样也没有什么不对,只是这样写我们就把这个 Fragment 完全写死了,我们直接在 onCreate() 中拿到宿主 Activity 的实例,然后通过 Activity 的 getIntent() 拿到 Intent,功能上我们是实现了,但这样写存在很大的问题,就是把 Fragment 与当前 Activity 完全绑定,Fragment 将无法复用,而我们又经常需要复用的场景,所以说我们这样写就相当于把 Fragment 完全写死了,不能达到复用的目的

我们使用 arguments 来创建 Fragment:


public class ContentFragment extends Fragment {
    private static final String TAG = "ContentFragment";
    private String mArgument;
    private static String ARGUMENT = "data";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = getArguments();
        // mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);
        if (bundle != null)
            mArgument = bundle.getString(ARGUMENT);
    }

    /**
     * 传入需要的参数,设置给 arguments
     *
     * @param argument
     * @return
     */
    public static ContentFragment newInstance(String argument) {
        Bundle bundle = new Bundle();
        bundle.putString(ARGUMENT, argument);
        ContentFragment contentFragment = new ContentFragment();
        contentFragment.setArguments(bundle);
        return contentFragment;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_content, container, false);
        Log.e(TAG, "onCreateView");
        return view;
    }
}



       这里我们给 Fragment 添加了 newInstance() 方法,并传入需要的参数,设置到 Bundle 中,然后 setArguments ( bundle )  然后在 onCreate 中进性获取,这样就完成了 Fragment 和 Acitivity 之间的解耦,达到复用,这里需要注意的是,setArguments() 方法必须在 Fragment 创建以后,添加给 Activity 前完成,千万不要首先调用了 add 然后设置 arguments


三、Fragment 的 startActivityForResult


       场景:两个 Fragment,一个展示文章列表的 Fragment(ListTitleFragment),一个展示对应列表详细信息 Fragment(ContentFragment),并且都有其各自的宿主 Activity,分别对应于 ListTitleActivity 和 ContentActivity,点击列表 Fragment 中的列表项,传入相应的参数,去 ContentFragment 详细信息页展示详细信息,在详细信息页面进行操作,并在点击 Back 键时返回到上一页,也就是说我们希望能够返回参数到上一页,那么我们肯定要使用 Fragment.startActivityForResult() 方法,在 Fragment 中存在 startActivityForResult() 和 onActivityResult() 方法,但是没有 setResult() 方法用于设置返回的 Intent,这样我们就需要通过拿到宿主的实例来设置,也就是通过 getActivity().setResult() 这样的方式来设置,接下来我们来看代码实例


1)ListTitleFragment 中的代码:


public class ListTitleFragment extends ListFragment {
    public static final int REQUEST_DETAIL = 0x110;
    private List<String> mTitles = Arrays.asList("Hello", "World", "Android");
    private int mCurrentPos;
    private ArrayAdapter<String> mAdapter;


    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setListAdapter(mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mTitles));
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        mCurrentPos = position;
        Intent intent = new Intent(getActivity(), ContentActivity.class);
        intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position));
        startActivityForResult(intent, REQUEST_DETAIL);
    }


    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.e("TAG", "onActivityResult");
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_DETAIL) {
            mTitles.set(mCurrentPos, mTitles.get(mCurrentPos) + " -- " + data.getStringExtra(ContentFragment.RESPONSE));
            mAdapter.notifyDataSetChanged();
        }
    }
}


2)ContentFragment 中的代码


public class ContentFragment extends Fragment {

    private static final String TAG = "ContentFragment";
    private String mArgument;
    public static final String ARGUMENT = "argument";
    public static final String RESPONSE = "response";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
            mArgument = bundle.getString(ARGUMENT);
            Intent intent = new Intent();
            intent.putExtra(RESPONSE, "good");
            getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);
        }
    }

    public static ContentFragment newInstance(String argument) {
        Bundle bundle = new Bundle();
        bundle.putString(ARGUMENT, argument);
        ContentFragment contentFragment = new ContentFragment();
        contentFragment.setArguments(bundle);
        return contentFragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Random random = new Random();
        TextView tv = new TextView(getActivity());
        tv.setText(mArgument);
        tv.setGravity(Gravity.CENTER);
        tv.setBackgroundColor(Color.argb(random.nextInt(100), random.nextInt(255), random.nextInt(255), random.nextInt(255)));
        return tv;
    }
}



       你好这是两个 Fragment 中的代码,可以看到,我们在 ListTitleFragment 中的 onListItemClick 使用 startActivityForResult() 跳转到目标 Activity,在目标 Activity 的 Fragment ( ContentFragment ) 中获取参数,然后调用 getActivity().setResult ( ListTitleFragment.REQUEST_DETAIL,intent ) 进行设置返回的数据,最后在 ListTitleFragment 的 onActivityResult() 拿到返回的数据进行显示,接下来我们来看两个宿主 Activity:


ListTitleFragment 的宿主 ListTitleActivity:


public class ListTitleActivity extends AppCompatActivity {

    private ListTitleFragment mListFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single_fragment);
        //获取 FragmentManager 实例
        FragmentManager fragmentManager = getSupportFragmentManager();
        //通过 fragmentManager.findFragmentById()方法 获取 ListTitleFragment 实例
        mListFragment = (ListTitleFragment) fragmentManager.findFragmentById(R.id.id_fragment_container);

        if (mListFragment == null) {
            //创建 ListTitleFragment 实例
            mListFragment = new ListTitleFragment();
            //通过 beginTransaction() 开启事务,添加 ListTitleFragment 实例,并提交
            fragmentManager.beginTransaction()
                    .add(R.id.id_fragment_container, mListFragment)
                    .commit();
        }
    }
}



ContentFragment 的宿主 ContentActivity:


public class ContentActivity extends AppCompatActivity {

    private ContentFragment mContentFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single_fragment);
        //获取 FragmentManager 实例
        FragmentManager fragmentManager = getSupportFragmentManager();
        //通过 fragmentManager.findFragmentById()方法 获取 ContentFragment 实例
        mContentFragment = (ContentFragment) fragmentManager.findFragmentById(R.id.id_fragment_container);

        if (mContentFragment == null) {
            String title = getIntent().getStringExtra(ContentFragment.ARGUMENT);
            //复用 ContentFragment 实例
            mContentFragment = ContentFragment.newInstance(title);
            //通过 beginTransaction() 开启事务,添加 ContentFragment 实例,并提交
            fragmentManager.beginTransaction()
                    .add(R.id.id_fragment_container, mContentFragment)
                    .commit();
        }
    }
}



这两个 Activity 中的代码非常相似,并且引用了相同的布局文件 activity_single_fragment:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/id_fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


</RelativeLayout>



       这里贴出 Activity 中的代码主要是想进行抽取,因为在我们的项目中,使用 Fragment,会有大量重用的代码,我们可以抽取出一个 Activity 来对我们的代码进行托管,以达到复用,接下来我们来抽取我们的 SingleFragmentActivity 如下:


public abstract class SingleFragmentActivity extends AppCompatActivity {

    protected abstract Fragment createFragment();

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single_fragment);
        //获取 FragmentManager 实例
        FragmentManager fragmentManager = getSupportFragmentManager();
        //通过 fragmentManager.findFragmentById()方法 获取 Fragment 实例
        Fragment fragment = fragmentManager.findFragmentById(R.id.id_fragment_container);

        if (fragment == null) {
            //创建 Fragment 实例
            fragment = createFragment();
            //通过 beginTransaction() 开启事务,添加 Fragment 实例,并提交
            fragmentManager.beginTransaction()
                    .add(R.id.id_fragment_container, fragment)
                    .commit();
        }
    }
}



那么有了 SingleFragmentActivity 我们的 ListTitleActivity 和 ContentActivity 就可以变为:


ListTitleActivity:


public class ListTitleActivity extends SingleFragmentActivity   {

    private ListTitleFragment mListFragment;

    @Override
    protected Fragment createFragment() {
        mListFragment = new ListTitleFragment();
        return mListFragment;
    }
}


ContentActivity:


public class ContentActivity extends SingleFragmentActivity {

    private ContentFragment mContentFragment;

    @Override
    protected Fragment createFragment() {
        String title = getIntent().getStringExtra(ContentFragment.ARGUMENT);
        mContentFragment = ContentFragment.newInstance(title);
        return mContentFragment;
    }
}



       这样我们的 ListTitleActivity 和 ContentActivity 中的代码是不是少了很多,重复的代码我们基本都抽取了出去,这种思想想必我们是可以借鉴的


四、FragmentPagerAdapter 和 FragmentStatePagerAdapter


       这两个类在我们结合 ViewPager 使用时经常用到,在面试的时候也经常被问起,相信大家都比较熟悉,那么这两个类有什么具体的区别呢?

主要区别就在对于 Fragment 是否销毁


FragmentPagertAdapter :对于不再需要的 Fragment,选择调用 detach() 方法,仅销毁视图,并不会销毁 Fragment 的实例,适合较少的 Fragment 使用以保存一些内存,对系统内存不会有太大的影响


FragmentStatePagerAdapter:会销毁不再需要的 Fragment,当前事务提交后,会彻底的将 Fragment 从当前的 Activity 的 FragmentManager 中移除,state 标明,销毁时,会将其 onSaveInstanceState ( Bundle outState ) 中的 bundle 信息保存下来,当用户切换回来,可以通过该 bundle 恢复生成新的 Fragment,也就是说,我们可以在 onSaveInstanceState ( Bundle outState ) 中的 bundle 方法中保存一些数据,在 onCreate() 中进行恢复


       如上所说,使用 FragmentStatePagerAdapter 更节省内存,当销毁新建也需要时间,一般情况下,如果使用的 Fragment 比较少,则使用 FragmentPagertAdapter ,如果使用 ViewPager 展示数量比较多时,建议使用 FragmentStatepagerAdapter


今天就写到这里,如有错误请指出


参考:洋神博客关于 Fragment 的分析