展开说说:Android Fragment完全解析-卷一

发布于:2024-04-23 ⋅ 阅读:(20) ⋅ 点赞:(0)

1、是什么

Fragment 中文意思是碎片,Android 3.0推出的一个系统组件,主打一个在应用界面中可模块化又可重复使用。

Fragment 它很独立,它可以定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。

Fragment 很粘人,它不能独立存在。它们必须由 activity 或其他 fragment 托管即fragment可以内嵌fragment使用同一 activity 或多个 activity 中可以使用同一 fragment 类的多个实例但是要注意解耦避免让一个 fragment 依赖另一个 fragment 或一个 fragment 操控另一个 fragment。

2、怎么用

Fragment一般是两种用法,使用管理器FragmentManager+事务FragmentTransaction和fragment搭配Viewpager。本文将分析第一种,后面一种会在下一篇文章分析。

activity 是放置应用的全局的界面元素,而Fragment 更适合定义和管理单个屏幕或部分屏幕的界面,可以更轻松地在运行时修改 activity 的外观。大部分应用的首页都采用了Fragment+底部切换选项按钮的布局。

Fragment类有这么样一个提示:

<li>Your activity must extend {@link FragmentActivity}
<li>You must call {@link FragmentActivity#getSupportFragmentManager} to get the
{@link FragmentManager}

也就是说 Fragment 必须嵌入 AndroidX FragmentActivity 中使用并获取管理器FragmentManager。AppCompatActivity 是FragmentActivity 的子类,因此如果我们的Activity是继承AppCompatActivity 就可以放心使用。

举个例子,一个Activity有四个底部选项卡,点击以后会展示四个不同Fragment页面,分别是FragmentHome 、FragmentApp、FragmentMsg、FragmentMy;一般来底部可能是个RadioGroup+RadioButton或者图片加文字的布局,我这里偷懒只写了四个button。

2.1通过新建一个类继承Fragment来创建一个fragment

定义一个自己的FragmentHome 前面说过Fragment也有自己的声明和布局展示,因此我在onCreateView方法使用inflate加载一个布局。类似的还有其他三个,分别是FragmentApp

FragmentMsgFragmentMy

public class FragmentHome extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater,  ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_home, null, false);
    Log.e(TAG, "onCreateView:     --" );
    return view;
}
}

2.2 将Fragment添加到Activity有两种方法,但是我们最常使用是第一种。

第一种:使用管理器FragmentManager和事务FragmentTransaction

activity布局应包含 一个容器组件来装载Fragment

<LinearLayout
    android:id="@+id/fragAct_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginBottom="@dimen/margin_50"
    android:visibility="gone"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    android:orientation="vertical" />

在Activity中将四个Fragment实例化,并创建FragmentManager

fragmentHome = new FragmentHome();
fragmentApp = new FragmentApp();
fragmentMsg = new FragmentMsg();
fragmentMy = new FragmentMy();
创建管理器FragmentManager:
FragmentManager manager = getSupportFragmentManager();

然后我们做增加、替换或者移除、显示、隐藏等都可以通过manager 获取事务来实现:

FragmentTransaction fragmentTransaction = manager.beginTransaction();

这里需要注意:fragmentTransaction只能提交一次无论是commit()或者commitNow(),超过一次会报错java.lang.IllegalStateException: commit already called

想要展示哪个fragment就通过事务的add或replace方法,添加当前fragment并放到前台展示。

先说add方法,一个简单的封装:add一个新的需要把其他的隐藏了否则可能会谍影重重:

private void addFragment(Fragment selectFragment, String fragmentTag) {
    //这里你要过滤FragmentManager的fragment列表
List<Fragment> fragments = manager.getFragments();
    for (Fragment fragment : fragments) {
        if (fragment.isAdded()) {
            Log.e(TAG, "initFragment: 已添加-先隐藏 " + fragment.getClass().getSimpleName() + "     " + fragmentHome.isAdded() + "    " + fragmentHome.isVisible());
            manager.beginTransaction().hide(fragment).commit();
        }
    }

    Fragment fragmentByTag = manager.findFragmentByTag(fragmentTag);
    if (fragmentByTag != null && fragmentByTag.isAdded()) {
        Log.e(TAG, "initFragment: 已经添加过了,别再添加-2 " + selectFragment.getClass().getSimpleName() + "     " + selectFragment.isAdded() + "    " + selectFragment.isVisible() + "   " + selectFragment.isHidden());
        manager.beginTransaction().show(fragmentByTag).commit();
    } else {
        if (selectFragment.isAdded()) {
            Log.e(TAG, "initFragment: 已经添加过了,别再添加-1 " + selectFragment.getClass().getSimpleName() + "     " + selectFragment.isAdded() + "    " + selectFragment.isVisible() + "   " + selectFragment.isHidden());
            manager.beginTransaction().show(selectFragment).commit();
        } else {
            manager.beginTransaction().add(R.id.fragAct_root, selectFragment, fragmentTag).show(selectFragment).commit();
            Log.e(TAG, "initFragment: 再次添加 fragment   +" + selectFragment.getClass().getSimpleName() + "     " + selectFragment.isAdded() + "    " + selectFragment.isVisible() + "   " + selectFragment.isHidden());
        }
    }
}

再说replace方法,如果你不用add,用了replace那一行代码就足够了

manager.beginTransaction().replace(R.id.fragAct_root, fragmentHome).commit();

第二种在Xml布局中写

<fragment
    android:id="@+id/fragAct_fragment"
    android:name="com.example.testdemo3.fragment.FragmentApp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

和普通组件一样使用在Activity中findviewbyid就可以了使用,这里注意fragment标签要小写、name属性对应的是你自定义fragment的全路径,后续fragment对象的使用方法同上面第一种。

3、常用方法

整体来看,fragment 应用界面中可模块化又可重复使用fragment 的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构并且可以方便将它增加、替换或者移除这些更改的记录会记录在由 activity 管理的返回堆栈中,以便撤消这些更改。

3.1 添加fragment

如需将 Fragment 添加到 FragmentManager通过事务调用 add()。此方法会收到用于存储此 Fragment 的容器的 ID,以及您要添加的 Fragment 的类名。添加的 Fragment 会转为 RESUMED 状态即最上层可以和用户交互的状态

3.2移除fragment

如需从宿主中移除 Fragment,调用 remove(),同时传入通过 findFragmentById() 或 findFragmentByTag() 从 Fragment 管理器检索到的 Fragment 实例。 如果 fragment 的视图之前已添加到容器中,则此时视图会从容器中移除。已移除的 fragment 会转为 DESTROYED 状态不可用。

3.3替换fragment

 replace() 将容器中现有的 fragment 替换为新 fragment 。调用 replace() 等同于对容器中的 Fragment 调用 remove() 后将新的 Fragment 添加到同一容器中就是系统帮你每次移除旧的并加入新的

3.4 提交

事务的提交操作为异步操作,调用 commit() 不会立即执行事务,而是会将事务调度为能在主界面线程上运行就在主界面线程上运行。如果使用 commitNow() 会立即在界面线程上运行 Fragment 事务,一般来说 commit() 即可。

4、避坑指南

4.1如果两次添加同一个Fragment对象,会报以下错误:
    java.lang.IllegalStateException: Fragment already added: FragmentHome{b2a5424 (a7835f58-5a56-49c9-b889-c0d8b8b17d8c) id=0x7f0800c5}  at androidx.fragment.app.FragmentManagerImpl.addFragment(FragmentManagerImpl.java:1379)
4.2如果同一个fragment使用了两个不同的tag进行add就会包如下错误:因此建议将TAG定义成常量
Caused by: java.lang.IllegalStateException: Can't change tag of fragment FragmentHome{595c6d5 (e4ff5b43-2336-422e-a219-008628a5c3b7) id=0x7f0800c5 fragmentHomeTag}: was fragmentHomeTag now FragmentHomeTag
        at androidx.fragment.app.FragmentTransaction.doAddOp(FragmentTransaction.java:172)

4.3 事务只能提交一次,无论是commit()或者commitNow(),超过一次会报错

java.lang.IllegalStateException: commit already called

4.4 横竖屏切换处理

我们在activity启动后应该自动展示一个fragment而不是一定要等用户主动触发再去加载展示对吧。如果你放到了onCreate中添加第一个fragment,那么在遇到横竖屏切换的话就会发现不论之前显示的个fragment切换以后都展示第一个fragment了,因为它重走生命周期了。其实呢可以这样避免:

//选出最近的fragment并展示,避免每次横竖屏切换就回到了第一个fragment
List<Fragment> fragments = manager.getFragments();
Fragment firstFragment = fragmentHome;
if (!fragments.isEmpty()) {
    firstFragment = fragments.get(fragments.size() - 1);
}
//上面2.2中写了这个方法
addFragment(firstFragment, firstFragment.getClass().getSimpleName() + "Tag");

横竖屏切换前的Fragment实例是怎样保存下来的呢?是的,是它。FragmentActivity的onSaveInstanceState方法。