Android Fragment

  1. Fragment是什么?和Activity的联系?生命周期如何?⭐⭐⭐⭐⭐⭐

  1. Activity和Fragment之间如何通讯?Fragment和Fragment之间如何通讯?⭐⭐⭐⭐⭐

  1. Fragment的回退栈了解吗?⭐⭐⭐⭐

  1. Fragment的使用方式⭐⭐⭐

  1. 你有遇到过哪些关于Fragment的问题,如何处理的?⭐⭐⭐

目录

  • 1、什么是Fragment

  • 2、Fragment的生命周期

  • 3、Fragment的使用方式

  • 3.1 静态使用

  • 3.2 动态使用

  • 3.3 getFragmentManager(),getSupportFragmentManager(),getChildFragmentManager()之间的区别

  • 4、通讯

  • 4.1 Fragment和Actvity的通讯

  • 4.2 Fragment和Fragment的通讯

  • 5、Fragment的回退栈

  • 6、Fragment状态保存

  • 7、你有遇到过哪些关于Fragment的问题,如何处理的?

  • 7.1 getActivity()空指针:

  • 7.2 Fragment视图重叠

1、什么是Fragment

刚开始学习Activity的时候,一个界面就是一个Activity。那么,如果想在一个Activity界面上镶嵌另一个界面如何做呢?Fragment翻译为“片段,破片”,可以理解为“显示在Activity中的Activity”,为解决Android碎片化而生。Fragment可以作为一个Activity界面中独立的子界面,拥有自己的生命周期,也可以接受用户的触摸事件。我们可以在一个Activity界面上添加多个Fragment子界面,并且每个Fragment都可以动态的添加、删除、替换,从而使得安卓界面开发具有更强大的灵活性。

比如微信首页有“微信”、“通讯录”、“发现”、“我”,这4个子界面就是4个Fragment。我们在设计Fragment的时候,需要考虑模块化和可复用化。最后,总结一下使用Fragment的优势:

  • 可以把一个Activity分为多个可复用的组件,使得UI开发更加有灵活性,之前一个Activity如果需要多个布局的话,就需要设置多个布局文件,又麻烦,性能也不高;

  • 每个Fragment都是独立的个体,可以动态的添加、删除、替换等,同时也可以同一个Fragment供多个Activity使用;

  • 与Activity切换相比,Fragment属于轻量切换,Fragment的出现,解决了Activity之间切换不流畅的问题;

  • 与View相比,View也可以实现在一个Activity上部署几个子界面,但View不能通过startActivityForResult()方法(现在建议使用:registerForActivityResult()方法)接收到返回结果,而且View通常更关注视图的实现;

2、Fragment的生命周期

Fragment的生命周期是Fragment面试题里最常问的,就是和Activity生命周期的比较,同时学习Fragment的生命周期最好也是结合Activity的生命周期来学习,因为Fragment是依附于Activity存在的,所以它的生命周期也受到Activity的生命周期影响。下图是网上找到的一张非常全面的图。

结合我们熟悉的Activity生命周期来学习:

  • Activity::onCreate()

  • Fragment::onAttach()

  • *Fragment::onCreate()

  • *Fragment::onCreateView()

  • *Fragment::onActivityCreated()

  • Activity::onStart()

  • *Fragment::onStart()

  • Activity::onResume()

  • *Fragment::onResume()

  • Activity::onPause()

  • *Fragment::onPause()

  • Activity::onStop()

  • *Fragment::onStop()

  • Activity::onDestroy()

  • *Fragment::onDestroyView()

  • *Fragment::onDestroy()

  • *Fragment::onDetach()

Activity一般有创建-开始-继续-暂停-停止-销毁共六大阶段,Fragment同样也经历了这六大阶段。从上面可以看到最大的差别在于创建-销毁这两个阶段,多出了以下5个方法(上面加粗的5个方法):

  1. onAttach(Activity):当Fragment与Activity发生关联时调用,即将Fragment绑定到Activity时,并且在这个方法可以通过getArguments()方法获取传入该Fragment的参数;

  1. onCreateView(LayoutInflater, ViewGroup, Bundle):创建该Fragment的视图时调用;

  1. onActivityCreated(Bundle):当Activity的onCreated()方法返回时调用;

  1. onDestroyView():对应onCreateView()方法,当该Fragment的视图被移除时调用;

  1. onDetach():对应onAttach()方法,当Fragment与Activity取消关联时调用;

  • 除了onCreateView(),如果重写了其他的方法,则必须调用父类对于该方法的实现;

  • Activity每一个生命周期都会引发Fragment调用同样的回调,如Activity收到onStop()后,里面的每个Fragment都会收到onStop(),同理,Fragment的onResume()也是在Activity的onResume()之后调用。但是onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()都是在Activity的onStart()中调用的;

3、Fragment的使用方式

Fragment的使用方式包括静态使用和动态使用。其中,静态是直接在xml布局文件中声明Fragment,动态则是使用代码来动态实现。

3.1 静态使用

步骤:

  • 创建一个继承Fragment的自定义XrFragment类,重新onCreateView()方法,在该方法中设置对应的xml布局文件;

public class XrFragment extends Fragment {
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        /*
        * 参数1:布局xml文件的ID
        * 参数2:容器,被inflate的布局的父ViewGroup
        * 参数3:是否将这个生成的View添加到这个容器中去
        * 作用是将布局文件封装在一个View对象中,并填充到此Fragment中
        * */
        View v = inflater.inflate(R.layout.xr_fragment, container, false);
        return v;
    }
}
  • 在Activity的布局文件直接声明该自定义XrFragment类即可。

<LinearLayout 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"
    android:orientation="vertical">
    
    <fragment
        android:id="@+id/xr_fragment"  // 在这里声明
        android:name="com.example.XrFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

3.2 动态使用

一个Activity可以有多个Fragment,如果我们需要设置两个按键,按下按键可以打开对应的Fragment可以这么做:

// 先创建两个自定义Fragment类
public class XrFragment1 extends Fragment {
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.xr_fragment_1, container, false);
        return v;
    }
}

public class XrFragment2 extends Fragment {
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.xr_fragment_2, container, false);
        return v;
    }
}

接着在Activity中将上述两个自定义Fragment绑定两个按键即可:

public class XuruiActivity extends AppCompatActivity {

    private Button bt_1;
    private Button bt_2;
    private FragmentManager manager;
    private XrFragment1 fragment1;
    private XrFragment2 fragment2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bt_1 = (Button) findViewById(R.id.bt_1);
        bt_2 = (Button) findViewById(R.id.bt_2);

        fragment1 = new XrFragment1();
        fragment2 = new XrFragment2();

        //1:初始化FragmentManager对象
        manager = getSupportFragmentManager();

        //2:使用FragmentManager对象用来开启一个Fragment事务
        FragmentTransaction transaction = manager.beginTransaction();

        //3:默认显示fragment1
        transaction.add(R.id.myframelayout, fragment1).commit();

        //对bt_1设置监听
        bt_1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentTransaction transaction = manager.beginTransaction();
                transaction.replace(R.id.myframelayout, fragment1).commit(); // 4
            }
        });

        //对bt_2设置监听
        bt_2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentTransaction transaction = manager.beginTransaction();
                transaction.replace(R.id.myframelayout, fragment2).commit();
            }
        });
    }
}

R.id.myframelayout就是一个FrameLayout,作为Fragment的容器,上述自定义的Fragment可通过该myframelayout在Activity中显示。为了更好的管理多个Fragment,可以通过[注释1]获取Fragment管理对象,在[注释2]使用FragmentManager对象用来开启一个FragmentTransaction(事务),FragmentTransaction常用的方法有:

  • add():向Activity中添加一个Fragment

  • remove(): 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈见下文),这个Fragment实例将会被销毁

  • replace():使用新的Fragment替换当前的的Fragment,其实就是remove()和add()的结合

  • hide(): 隐藏当前的Fragment,但不会销毁

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

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

  • attach():重建view视图,附加到UI上并显示

  • ransatcion.commit():提交事务,上述add/replace/hide/show方法都需要执行commit()后才生效

通常情况下,建议使用show()和hide(),避免Fragment重复加载。

3.3 getFragmentManager,getSupportFragmentManager,getChildFragmentManager之间的区别?

为了管理Fragment,需要获取Fragment管理对象:FragmentManager,其中:

  • getFragmentManager():Activity可以通过该方法获取Activity类里面的Fragment管理器,Fragment里不能用;

  • getSupportFragmentManager():Activity可以通过该方法获取FragmentActivity类里面的Fragment管理器,用于管理这个Activity里面的所有一级Fragment。和getFragmentManager()作用确实是一样的作用,因为,Android3.0版本之前是没有Fragment这个概念,因此3.0版本以前的不可以直接调用 getFragmentManager(),因此3.0版本以前的可以调用getSupportFragmentManager()间接获取FragmentManager。而3.0以后的版本则两个方法任选一个即可,所以建议都调用getSupportFragmentManager()。同时,getSupportFragmentManager()还可以在Fragment中使用,但在Fragment中使用时,是获取的父类Fragment的FragmentManager,如果没有父类,则获取该Fragment所属Activity的FragmentManager;

  • getChildFragmentManager():如果在Fragment里面还需要继续嵌套Fragment,则需要通过该方法在Fragment里面获取FragmentManager

Fragment1 fragment1 = new Fragment1();
FragmentManager fragmentManager = fragment1.getChildFragmentManager(); val Fragment2 fragment2 = new Fragment2();
                            
fragmentManager.beginTransaction().add(R.id.my_framelayout, fragment2, "newFragment2").commit()

4、通讯

4.1 Fragment和Actvity的通讯

Fragment依附于Activity,两者的通讯可以有以下方式:

  • 在Activity有对应的Fragment的引用,则直接通过该引用就可以访问Fragment里面的public方法,如果没有Fragment引用,则可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例对其进行操作,因为每个Fragment都有唯一的ID(比如用android:id属性或者android:tag属性提供唯一标识);

  • 在Fragment里可以通过getActivity()来获取当前绑定的Activity的实例,并对其进行操作;

  • 接口方式:Activity里继承一个接口,并在Fragment里实现;

  • 广播/文件:这是通用的Android跨进程通讯方式,用于Fragment和Activity通讯自然可以;

  • Bundle:在Activity中建一个Bundle,将需要传的值存入Bundle,并通过Fragment的setArguments(bundle)传到Fragment,最后在Fragment中,用getArguments()接收。

  • registerForActivityResult():可以在Fragment里调用registerForActivityResult()来启动另一个Activity,并返回一些数据回来Fragment。比如在myFragment启动编辑名字的Actvity,编辑完成后把编辑完的名字返回给myFragment:

//EditNameActivity
val intent = Intent()
intent.putExtra(myData.EDIT_NAME, name.text.toString()) //传入修改后的名字
setResult(Activity.RESULT_OK, intent)
finish()

//myFragment
private val startEditNameActivityLauncher: ActivityResultLauncher<Intent> =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
        if (it.resultCode == Activity.RESULT_OK) {
            val editName = it.data?.getStringExtra(myData.EDIT_NAME) //获取EditNameActivity返回的姓名
            if (!editName.isNullOrEmpty()) {
                myViewModel.setUserName(editName)
            }
        }
        
    }
    
startEditNameActivityLauncher.launch(Intent(requireContext(),EditNameActivity::class.java))

4.2 Fragment和Fragment的通讯

首先,不建议Fragment之间直接通讯,最好是借助Activity为中介。那么,如果一定要在Fragment1打开Fragment2后,从Fragment2返回一些数据回去Fragment1要怎么做呢? 只要在Fragment1打开Fragment2的时候,多执行一句:

fragment2.setTargetFragment(Fragment1.this, REQUEST_CODE);

然后在Fragment1类里面实现:

@Override  
   public void onActivityResult(int requestCode, int resultCode, Intent data) {  
       super.onActivityResult(requestCode, resultCode, data);  
       if(resultCode != Activity.RESULT_OK){  
           return;  
       }else{  
           int rlt = data.getIntExtra("xr",0);   //获取数据
           //处理数据...
       }  
   }

接着,进入Fragment2的代码,直接调用以下代码即可:

Intent intent = new Intent();  
intent.putExtra("xr", 100);      
getTargetFragment().onActivityResult(Fragment1.REQUEST_CODE,resultOk,intent);  
       

5、Fragment的回退栈

Activity有任务栈,Fragment也有回退栈(Back Stack)。比如现在ActivityA先后启动了FragmentA、FragmentB,此时在FragmentB按后退键,会直接退回桌面。如果我们希望能退到FragmentA的话,需要执行

addToBackStack(String tag):标记本次的回滚操作
 Fragment newFragment = new FragmentA();
 FragmentTransaction transaction = getFragmentManager().beginTransaction();
 
 transaction.replace(R.id.Fragment_container, newFragment);
 transaction.addToBackStack(null);

 transaction.commit();

此时,在FragmentB按下后退键,就会跳回FragmentA,再按后退键才最后退回桌面。更进一步,如果回退栈里有4个Fragment:Fragment1到Fragment4,如果想在Fragment4的界面按后退键直接回到Fragment1,可以执行:

  • popBackStack(int id, int flags):其中id表示提交变更时commit()的返回值。

  • popBackStack(String name, int flags):其中name是addToBackStack(String tag)中的tag值

通过上面两个方法就可以指定回到某个特定的Fragment,并且根据第二个参数flags的不同,有两种情况:

  • 0:表示除了指定的Fragment所在的这一层之上的所有层都退出栈;

  • FragmentManager.POP_BACK_STACK_INCLUSIVE(inclusive):表示连同指定Fragment所在层以及之上的所有层都一起退出。

6、Fragment状态保存

Fragment状态保存入口:

1、Activity的状态保存, 在Activity的onSaveInstanceState()里, 调用了FragmentManger的saveAllState()方法, 其中会对mActive中各个Fragment的实例状态和View状态分别进行保存.

2、FragmentManager还提供了public方法: saveFragmentInstanceState(), 可以对单个Fragment进行状态保存, 这是提供给我们用的。

3、FragmentManager的moveToState()方法中, 当状态回退到ACTIVITY_CREATED, 会调用saveFragmentViewState()方法, 保存View的状态.

7、你有遇到过哪些关于Fragment的问题,如何处理的?

7.1 getActivity()空指针:

这种情况一般发生在在异步任务里调用getActivity(),此时宿主Activity可能已经销毁了,因此引发空指针问题。最简单的方法就是在引用getActivity()时做个判空判断,同时可以根据情况考虑用获取Context来代替Activity,建议在onAttach()方法中将Context强制转为Activity这种方式来代替直接调用getActivity()。反正不要把Fragment事务放在异步线程的回调中或者AsyncTask的onPostExecute()。

7.2 Fragment视图重叠

如果我们在onCreate()方法加载Fragment时,特别是当Activity重建的时候,没有判断saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),就直接加载布局,可能会导致重复加载了同一个Fragment布局,因此建议加上判断。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值