Activity中使用Fragment笔记

本文详细介绍了Activity中添加Fragment的三种方式,包括XML布局、动态添加和使用TabLayout+ViewPager。讨论了Fragment内部数据的保存与恢复,特别是在ViewPager中延时加载数据的策略。此外,还提到了在Fragment中使用子Fragment的问题,以及如何在Fragment中正确获取Activity。文章强调了不同场景下选择合适Fragment管理方式的重要性,以及在不同Android版本中处理onActivityResult回调的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 Activity中添加Fragment方式

(1)Xml布局文件添加,如下
<fragment
    android:id="@+id/fragment"
    android:name="xxx.xxx.MyFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
(2)View中动态添加
Fragment fragment = getSupportFragmentManager().findFragmentByTag("tag");
if(fragment == null){
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    fragment = MyFragment.newInstance();
    transaction.add(R.id.fragment_container, fragment, "tag");
    transaction.commit();
}

Fragment fragment = null;
if(savedInstanceState == null){
    fragment = MyFragment.newInstance();
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    transaction.add(R.id.fragment_container, fragment, "tag");
    transaction.commit();
}else{
    fragment = getSupportFragmentManager().findFragmentByTag("tag");
}

之所以在添加Fragment前判断是否有存在相同的Fragment,是因为在内存不足或横竖屏切换(如未在android:configChanges设置)时,会保存其内的Fragment状态信息。当Activity被创建显示时,Fragment会被自动恢复(恢复Fragment和状态的代码在Fragment Activity的onCreate方法中)。如果不判断,则添加多个相同的Fragment。
在添加Fragment时,如果需要向其传递一些初始化的参数,则可使用Bundle来传递。在Fragment内使用getArguments()获取参数。代码如下:

//------ 传递参数 ------
Bundle data = new Bundle();
//除了基本数据类型的值,还可以传递序列化和实现Parcelable类型的数据
data.putString(key, value);
...
fragment.setArguments(data);

//------ 获取参数 ------
Bundle data = getArguments();
if(data != null){
    //获取参数
    String value = data.getString(key);
    ...
}
(3)使用TabLayout和ViewPager添加

在布局文件添加:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="40dp" />

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_below="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

给ViewPager设置Fragment适配器和TabLayout绑定ViewPager:

TabLayout table_layout = (TabLayout) findViewById(R.id.tab_layout);
ViewPager view_pager = (ViewPager) findViewById(R.id.view_pager);
//设置自定义的Fragment适配器
view_pager.setAdapter(new MyFragAdapter(getSupportFragmentManager()));
//TabLayout绑定ViewPager
table_layout.setupWithViewPager(view_pager);
table_layout.setTabMode(TabLayout.MODE_FIXED);

自定义的Fragment适配器必须要继承FragmentStatePagerAdapter或FragmentPagerAdapter,而这两个类则继承了android.support.v4.view.PagerAdapter。FragmentStatePagerAdapter与FragmentPagerAdapter区别则体现在Fragment的添加,移除和状态保存这三个方面。FragmentStatePagerAdapter添加Fragment时会缓存到mFragments集合中,并恢复其状态(如果有保存状态),而当不可见时则会先使用Fragment.SavedState来保存其状态信息,将其在mFragments的缓存置为null,然后直接从FragmentManager中remove掉。而FragmentPagerAdapter直接使用FragmentManager管理Fragment缓存,当不可见时,只会调用detach将其与Activity分离。
FragmentStatePagerAdapter的instantiateItem和destroyItem方法代码(使用…省略部分代码):

@Override
public Object instantiateItem(ViewGroup container, int position) {
    ...
    //本地缓存有(非FragmentManager缓存),则直接返回
    if (mFragments.size() > position) {
         Fragment f = mFragments.get(position);
         if (f != null) {
             return f;
         }
    }
    ...
    //创建实例
    Fragment fragment = getItem(position);
    ...
    if (mSavedState.size() > position) {
        Fragment.SavedState fss = mSavedState.get(position);
        //恢复保存的状态信息
        if (fss != null) {
            fragment.setInitialSavedState(fss);
         }
    }
    //增加本地缓存
    while (mFragments.size() <= position) {
          mFragments.add(null);
    }
    ...
    //添加到本地缓存
    mFragments.set(position, fragment);
    mCurTransaction.add(container.getId(), fragment);
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    ...
    //增加状态缓存
    while (mSavedState.size() <= position) {
          mSavedState.add(null);
    }
    //判断是否保存Fragment状态信息
    mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
    //清除Fragment对象缓存
    mFragments.set(position, null);
    //从FragmentManager中移除
    mCurTransaction.remove(fragment);
}
...
@Override
public Parcelable saveState() {
    Bundle state = null;
    //保存状态信息
    ...
    return state;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {
    if (state != null) {
        //恢复状态信息
        ...
     }
}

FragmentPagerAdapter的instantiateItem和destroyItem方法代码:

@Override
public Object instantiateItem(ViewGroup container, int position) {
    ...
    final long itemId = getItemId(position);
    String name = makeFragmentName(container.getId(), itemId);
    //获取FragmentManager缓存
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        //attach到当前Activity
        mCurTransaction.attach(fragment);
    } else {
        //创建实例
        fragment = getItem(position);
        mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
    mCurTransaction = mFragmentManager.beginTransaction();
    }
    mCurTransaction.detach((Fragment)object);
}
...
@Override
public Parcelable saveState() {
    //默认未做状态信息保存
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {
    //默认未恢复状态信息
}

由这几个方法对比可知:

FragmentStatePagerAdapter适用于管理大数量Fragemnt,如新闻页面,根据不同的频道显示对应的新闻信息,占用内存较少,保存状态信息有利于再次快速回显数据。
FragmentPagerAdapter使用少量的固定的Tab页面,所有被显示的Fragment都会保存在内存中,这使得切换页面时显示更快,同时也会占用一定的内存。

在使用ViewPager作为Fragment容器时,需要根据显示Fragmet的数量来使用Adapter,以免占用过多的内存或切换页面时内容显示缓慢。

二 Fragment中使用Fragment

Fragment中添加子Fragment的方式与Activity中添加方式基本相同,只是获取FragmentManager对象的方法不同。Activity中使用getSupportFragmentManager()方法获取,Fragment中需要使用getChildFragmentManager(),而不是调用getFragmentManager()。

Fragment的getFragmentManager()代码注释:

Return the FragmentManager for interacting with fragments associated with this fragment’s activity

获取与Fragment关联的Activity交互的FragmentManager。

Fragment的getFragmentManager()代码注释:

Return a private FragmentManager for placing and managing Fragments inside of this Fragment.

获取Fragment内一个管理子Fragment的私有的FragmentManager。

子Fragment使用startActivityForResult()方法在低版本时onActivityResult得不到执行完成的回调问题。
先看看android 4.4源码中的FragmentActivity的onActivityResult代码:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    mFragments.noteStateNotSaved();
    int index = requestCode>>16;
    if (index != 0) {
    index--;
    ...
    //mFragments为FragmentManagerImpl实例,通过getSupportFragmentManager()获取的即该实例。
    Fragment frag = mFragments.mActive.get(index);
    if (frag == null) {
        ...
    } else {
        frag.onActivityResult(requestCode&0xffff, resultCode, data);
    }
    return;
    }
    super.onActivityResult(requestCode, resultCode, data);
}

通过以上注释可知:
FragmentActivity的onActivityResult方法只查找了通过getSupportFragmentManager()关联的Fragment,而通过getFragmentManager()与Fragment关联的子Fragment则直接被忽略了,从而导致在子Fragment无法收到回调。

这个问题在23.2.0以下的support-v4包中都存在,23.2.0版本才修复了这个Bug,改用以上版本则可避免。或着在自定义的Activity基类中重写此方法,自己实现对子Fragment的onActivityResult回调。建议还是使用高版本的v4包,低版本的Fragment中问题实在是太多了。

三 Fragment内部数据保存和恢复

在onSaveInstanceState()保存Fragment数据,自定义的业务类尽量实现Parcelable接口(建议不使用Serializable)。代码如下:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    //保存数据
    outState.putString(key, value);
}

在Fragment被调用onSaveInstanceState方法后,如果有操作DialogFragment,需使用commitAllowingStateLoss()提交,使用commit()则会抛出异常。

在onViewStateRestored()恢复数据并更新UI。

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);
    if(savedInstanceState != null){
        //恢复数据
        String value = savedInstanceState.getString(key);
        ...
    }
}

四 ViewPager中Fragment延时加载数据

自定义一个Fragment基类:

public abstract class BaseFragment extends Fragment {
    /**
     * 是否已加载数据
     */
    protected boolean hasLoadData = false;

    @Override
    public void onResume() {
        super.onResume();
        if (getUserVisibleHint() && !hasLoadData) {
            //加载数据
            loadData(true);
            hasLoadData = true;
        }
    }
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if(getUserVisibleHint() && !hasLoadData){
            //加载数据
            loadData(true);
            hasLoadData = true;
        }
    }

    /**
     * 加载数据
     * @param isRefresh true为刷新,false为加载更多
     */
    public abstract void loadData(boolean isRefresh);
}

子中实现数据加载,在onSaveInstanceState方法中保存已经加载的数据,onViewStateRestored方法中恢复数据并将数据更新到UI。这样既可以避免加载未显示的Fragment,又可以在Fragment被重新显示时无需加载即可快速恢复数据并显示。

五 Fragment获取Activity

Fragment中获取FragmentActivity的getActivity方法代码:

final public FragmentActivity getActivity() {
        return mHost == null ? null : (FragmentActivity) mHost.getActivity();
}

Fragment是通过属性mHost(FragmentHostCallbackde实例)关联FragmentActivity,该属性没有限定标识符,即给其赋值的类在同一包名中。既然管理Fragment的是FragmentManager,那么给mHost赋值的操作应该在其方法内。而FragmentManager为抽象类,子类为FragmentManagerImpl,则赋值操在其方法内。通过查看Fragment添加和显示理财,可知具体设置mHost值是在moveToState方法中:

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
    ...
    switch (f.mState) {
            case Fragment.INITIALIZING: 
                if (f.mSavedFragmentState != null) {
                    ...
                    //恢复保存的状态信息
                 }
                 //设置mHost
                 f.mHost = mHost;
                 //
                 f.mParentFragment = mParent;
                 //
                 f.mFragmentManager = mParent != null ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
                 f.mCalled = false;
                 //执行Fragment的onAttach方法
                 f.onAttach(mHost.getContext());
                 ...
            case Fragment.CREATED:
                if (newState < Fragment.CREATED) {
                    if (f.mAnimatingAway != null) {
                        ...
                    } else {
                        if (!f.mRetaining) {
                              f.performDestroy();
                        }
                        f.mCalled = false;
                        //执行Fragment的onDetach
                        f.onDetach();
                        ...
                        //如果不需要保活此Fragment
                        if (!keepActive) {
                           if (!f.mRetaining) {
                                makeInactive(f);
                            } else {
                                //将Fragment中的mHost置为null
                                f.mHost = null;
                                f.mParentFragment = null;
                                f.mFragmentManager = null;
                                f.mChildFragmentManager = null;
                            }
                  }
         }
    }

在onAttach之后才可以调用getActivity获取到FragmentActivity对象,在onDetach之后一般情况下是获取不到值。

请尊重博主劳动成果,转载请标明原文链接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值