Fragment详解

Fragment生命周期

Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。官网这张图很好的说明了两者生命周期的关系:


image.png
  • onAttach(Activity)
    当Fragment与Activity发生关联时调用。
  • onCreateView(LayoutInflater, ViewGroup,Bundle)
    创建该Fragment的视图
  • onActivityCreated(Bundle)
    当Activity的onCreate方法返回时调用
  • onDestoryView()
    与onCreateView想对应,当该Fragment的视图被移除时调用
  • onDetach()
    与onAttach相对应,当Fragment与Activity关联被取消时调用。

Fragment使用

静态使用

把Fragment当成普通的控件,直接写在Activity的布局文件中. 一般比较少用,因为这样的fragment是静态的不能替换的。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <fragment
        android:id="@+id/id_fragment_title"
        android:name="com.demo.wong.TitleFragment"
        android:layout_width="match_parent"
        android:layout_height="45dp" />
 
    <fragment
        android:layout_below="@id/id_fragment_title"
        android:id="@+id/id_fragment_content"
        android:name="com.demo.wong..ContentFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
 
</RelativeLayout>
动态使用

为了动态使用Fragment,我们修改一下Actvity的布局文件,中间使用一个FrameLayout来放我们的动态添加的Fragment。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <fragment
        android:id="@+id/id_fragment_title"
        android:name="com.demo.wong.TitleFragment"
        android:layout_width="match_parent"
        android:layout_height="45dp" />

    <FrameLayout
        android:id="@+id/id_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/id_fragment_title" />
 
</RelativeLayout>
@Override
    public void onClick(View v)
    {
        FragmentManager fm = getFragmentManager();
        // 开启Fragment事务
        FragmentTransaction transaction = fm.beginTransaction();
 
        switch (v.getId())
        {
        case R.id.tab_bottom_weixin:
            if (mWeixin == null)
            {
                mWeixin = new ContentFragment();
            }
            // 使用当前Fragment的布局替代id_content的控件
            transaction.replace(R.id.id_content, mWeixin);
            break;
        case R.id.tab_bottom_friend:
            if (mFriend == null)
            {
                mFriend = new FriendFragment();
            }
            transaction.replace(R.id.id_content, mFriend);
            break;
        }
        // transaction.addToBackStack();
        // 事务提交
        transaction.commit();
    }

Fragment常用的三个类:

1、android.app.Fragment 主要用于定义Fragment

2、android.app.FragmentManager 主要用于在Activity中操作Fragment

3、android.app.FragmentTransaction 保证一些列Fragment操作的原子性.

  • 获取FragmentManage的方式:getFragmentManager()
  • 主要的操作都是FragmentTransaction的方法:
    FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务

transaction.add() //往Activity添加一个Fragment

transaction.remove() //从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这个Fragment实例将会被销毁。

transaction.replace() //使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~

transaction.hide() //隐藏当前的Fragment,仅仅是设为不可见,并不会销毁

transaction.show() //显示之前隐藏的Fragment

transaction.detach() //会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。

remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。

注意:使用Fragment的过程中可能会遇到Activity状态不一致:State loss这样的错误。主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用。

管理Fragment回退栈

类似与Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。

把一个Fragment事务添加到回退栈很简单,只需在事务提交前transaction.addToBackStack(String)就好。

假如从FragmentOne跳到FragmentTwo时,addToBackStack,那么FragmentOne则会加入回退栈,当从FragmentTwo返回时会先回到FragmentOne

FragmentTwo fTwo = new FragmentTwo();
        FragmentManager fm = getFragmentManager();
        FragmentTransaction transaction = fm.beginTransaction();
        transaction.replace(R.id.id_content, fTwo, "TWO");
        transaction.addToBackStack(null); //添加到当前回退栈
        transaction.commit();

Fragment如何与Activity交互

因为所有的Fragment都是依附于Activity的,所以通信起来并不复杂,大概归纳为:

Activity调用Fragment

  1. 如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法

  2. 如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。

Fragment调用Activity

  1. 在Fragment中可以通过getActivity()得到当前绑定的Activity的实例,然后进行操作。
  2. 如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。

由于要考虑Fragment的复用性,必须降低Fragment与Activity的耦合。Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。
假如我们需要从一个fragment跳到另一个fragment,我们可以通过接口的方式来实现,通过activity去做跳转。

public class FragmentOne extends Fragment implements OnClickListener
{
    private FTwoBtnClickListener fTwoBtnClickListener ;
    
    public interface FTwoBtnClickListener
    {
        void onFTwoBtnClick();
    }
    //设置回调接口
    public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener)
    {
        this.fTwoBtnClickListener = fTwoBtnClickListener;
    }
    @Override
    public void onClick(View v)
    {
        if(fTwoBtnClickListener != null)
        {
            fTwoBtnClickListener.onFTwoBtnClick();
        }
    }

}

public class MainActivity extends Activity implements FTwoBtnClickListener
{
/**
     * FragmentOne 按钮点击时的回调
     */
    @Override
    public void onFTwoBtnClick()
    {
        if (mFTwo == null)
        {
            mFTwo = new FragmentTwo();
 
        }
        FragmentManager fm = getFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.hide(mFOne);
        tx.add(R.id.id_content, mFTwo, "Two");
        tx.addToBackStack(null);
        tx.commit();
    }
}

Fragment使用技巧

  1. 非空判断
public class MainActivity extends FragmentActivity
{
    
    private ContentFragment mContentFragment  ; 
 
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        FragmentManager fm = getSupportFragmentManager();
        mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container);
        
        if(mContentFragment == null )
        {
            mContentFragment = new ContentFragment();
            fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();
        }
 
    }
 
}

为什么要非空判断:
当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。

  1. 使用arguments来创建Fragment
public class ContentFragment extends Fragment
{
 
    private String mArgument;
    public static final String ARGUMENT = "argument";
 
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        Bundle bundle = getArguments();
        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;
    }

这种方式主要是为了Fragment和Activity间的解耦。

  1. Fragment的startActivityForResult
    考虑这种需求:从一个FragmentOne跳到另一个FragmentTwo,需要从FragmentTwo返回FragmentOne带回参数。
    在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是呢,没有setResult()方法,用于设置返回的intent。这样我们就需要通过调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent)。也说明了一个问题:fragment能够从Activity中接收返回结果,但是其自设无法产生返回结果,只有Activity拥有返回结果。
public class ListTitleFragment extends ListFragment
{
 
    public static final int REQUEST_DETAIL = 0x110;

    @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();
        }
    }
}

public class ContentFragment extends Fragment
{
 
    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); //发送数据
        }
 
    }
}

Fragment中add与replace的区别

  • add不会重新初始化fragment,replace每次都会。所以如果在fragment生命周期内获取获取数据,使用replace会重复获取;
  • 添加相同的fragment时,replace不会有任何变化,add会报IllegalStateException异常;
  • replace先remove掉相同id的所有fragment,然后在add当前的这个fragment,而add是覆盖前一个fragment。所以如果使用add一般会伴随hide()和show(),避免布局重叠;
  • 使用add,如果应用放在后台,或以其他方式被系统销毁,再打开时,hide()中引用的fragment会销毁,所以依然会出现布局重叠bug,可以使用replace或使用add时,添加一个tag参数;(add前先判断是否已存在)

getFragmentManager、getSupportFragmentManager 、getChildFragmentManager之间的区别?

  • getFragmentManager()所得到的是所在fragment 的父容器的管理器,
    getChildFragmentManager()所得到的是在fragment 里面子容器的管理器,
    如果是fragment嵌套fragment,那么就需要利用getChildFragmentManager();
  • 因为Fragment是3.0 Android系统API版本才出现的组件,所以3.0以上系统可以直接调用getFragmentManager()来获取FragmentManager()对象,而3.0以下则需要调用getSupportFragmentManager() 来间接获取;

FragmentPagerAdapter与FragmentStatePagerAdapter的区别与使用场景

  • 相同点 :二者都继承PagerAdapter
  • 不同点 :FragmentPagerAdapter的每个Fragment会持久的保存在FragmentManager中,只要用户可以返回到页面中,它都不会被销毁。因此适用于那些数据相对静态的页,Fragment数量也比较少的那种;
    FragmentStatePagerAdapter只保留当前页面,当页面不可见时,该Fragment就会被消除,释放其资源。因此适用于那些数据动态性较大、占用内存较多,多Fragment的情况;

参考链接:
https://blog.csdn.net/lmj623565791/article/details/37970961

https://blog.csdn.net/lmj623565791/article/details/37992017

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342