从多Activity到多Fragment的转变 | 青训营笔记

165 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第一天

本文用于记录初入安卓的我对于整个应用在设计上从全部使用Activity少Activity与多Fragment结合的转变过程

首先我是通过阅读网上流传的“神书”《第一行代码》入门安卓开发,由于书籍对fragment的介绍相对较少,以至于我在阅读完整本书之后不记得有fragment这个东西了。

在把书看完了之后,我就面临一个需求:实现一个底部导航栏(当时并不知道有这么一个叫法,毕竟刚入门嘛)。再结合当时脑子全是四大组件,fragment啥的早已不在脑中。所以最后一上来就是先建四个Activity,再设计一个公用的底部控件,每个Activity都引入一个底部控件,在切换的时候销毁当前Activity,创建一个新的目标Activity,并初始化底部控件状态。(由于当时的源码找不着了,所以在这里说一下当时的一个思路)

有过这种做法的同学应该能够想象我最后的崩溃程度,接下来说说当时设计面临的问题。一是:四个Activity中每个Activity都有一个底部控件,而底部控件有四个点击事件,这样就需要写十六个onClickListener,虽然可以通过复用减少次数,但是总归相当麻烦。二是:每点击一下就会创建一个新的Activity并且销毁一个新的Activity,这开销不可谓不大。

对于全部都是Activity的设计,不仅导航及其困难,其性能也相当糟糕。所以现在一般采用少量Activity控制多数Fragment,甚至单Activity多Fragment的方法。

为什么会大量使用Fragment来实现页面,这就不得不说一下Fragment了。

什么是Fragment?

Fragment表示应用界面中可重复使用的一部分。Fragment 定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。Fragment 不能独立存在,而是必须由 Activity 或另一个 Fragment 托管。Fragment 的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构。

Fragment用来做什么?

我认为Fragment就是用来将同一个页面里面有着不同功能的部分解藕的。Android官方提供了这么一种最佳实践,避免让你自己定义一套框架来分离这些功能。

Fragment有什么优点?

首先,Fragment拥有自己的生命周期,让你能够更好地根据不同的生命周期来控制其中的具体逻辑;

其次,Fragment是官方支持,这一方面说明它会随着Android的版本更新增加功能,同时越来越稳定;另一方面则是说明它是一个全局通用的“组件”,你可以将不同的Fragment拼装成一个页面,也可以随时将他们搬走,很方便地让它们在另一个页面当中生效;

还有,Fragment很“轻”,你可以使用它来实现“单Activity架构”;

最后,正如Fragment诞生的目的,它能够完美地为你的App中的不同功能进行解耦,提供良好的灵活性和可复用性。

转载自:(如何优雅地使用Fragment)juejin.cn/post/700770…

对于如何使用Fragment,可以看看我小组成员的另一篇笔记:juejin.cn/post/712766…

说完Fragment的一些优点之后,对于改造我最初的设计也并不困难,接下来说说如何改造。相信对jetpack熟悉的同学,必然知道bottom navigation,我最开始的那个需求就是一个底部导航栏,可惜当时并不知道这个叫底部导航栏,不然可能靠搜索引擎都可以找到解决方案。

但是在最近的青训营的项目中,我并没有采用bottom navigation来实现我们的导航栏,因为利用官方提供的组件来做自定义,还是相对较为困难(可能是我比较菜)。所以最后我选择了一个Activity通过navigation控制三个Fragment的方式,对于底部的导航栏由Activity定义并管理。

底部导航栏的UI

image.png

Activity的实现代码

public class MainActivity extends AppCompatActivity {

    private final HashMap<Integer, MotionLayout> map = new HashMap<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // 主页面底部导航控件与id的映射
        // 如果需要添加底部导航控件,只需要在这里添加一个映射
        map.put(R.id.userFragment, binding.bnUser.mlUser);
        map.put(R.id.addFragment, binding.bnAdd.mlAdd);
        map.put(R.id.listFragment, binding.bnList.mlList);

        NavHostFragment navHostFragment =
                (NavHostFragment) getSupportFragmentManager()
                .findFragmentById(R.id.fragmentContainerView);
        if (navHostFragment == null) {
            return;
        }
        NavController navController = navHostFragment.getNavController();

        for (Map.Entry<Integer, MotionLayout> entry :
                map.entrySet()) {
            entry.getValue().setOnClickListener(v -> {
                //弹出当前fragment,确保每次只有一个Fragment
                navController.popBackStack();
                //压入点击的fragment
                navController.navigate(entry.getKey());
            });
        }

        navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
            int currDestinationId = destination.getId();

            for (Map.Entry<Integer, MotionLayout> entry :
                    map.entrySet()) {
                entry.getValue().setProgress(0f);
                //当前fragment的导航不能再次点击,其他的导航可以点击
                if (entry.getKey() == currDestinationId) {
                    entry.getValue().setClickable(false);
                    //点击的导航开始动画
                    entry.getValue().transitionToEnd();
                } else {
                    entry.getValue().setClickable(true);
                }
            }
        });
    }
}

为什么我没有选择使用bottom navigation来实现,主要是我想加点动画效果。关于加了什么动画,我在之后的笔记中再做分享,这篇笔记先记录到在这里。

ps:这个底部导航栏的实现是学习这位Up主的,有兴趣的同学可以看看,不过他是使用kt实现的,我这个可以说是一个java版本的实现。www.bilibili.com/video/BV1nV…