2024年鸿蒙最新2021年了,如何优雅地使用Fragment?_fragment extends(2),前端面试基础题

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

            type = TYPE_CHANNEL;
            break;
    }
    return type;
}

// 此处省略三千行

}
复制代码


那么,关于2和3呢?稍有经验的人肯定能想到,我们应该把代表不同功能的部分分别解耦,把他们至少拆到不同的类里面去——我们可以用不同的View来代表不同的功能,但是考虑到MVP,MVVM等思想,直接用View来显然也是在给未来挖坑,那么我们就可以自定义一个容器来作为Controller或者Presenter,从而实现不同的功能……


那么,与其自己造轮子,我们何不用Fragment呢?假设我们不用Fragment,而是自定义了某个Container或者Presenter,那么我们就要在最外层的Fragment里面持有它们所有的引用,然后得在不同的生命周期里面,手动调用方法触发它们的生命周期(当然你也可以用JetPack的LifeCycle来更优雅地实现这一点)。至于这些不同的Container之间,如果我们希望他们数据互通,最坏的结果可能就是他们需要互相持有互相的引用,代码最后还是变得一团糟……


这里还是模拟一下伪代码(错误示范):



/**

  • 视频Tab
    */
    public class VideoFragment extends Fragment {

    private ContainerOrPresenter searchPresenter;
    private ContainerOrPresenter recentPresenter;
    private ContainerOrPresenter channelPresenter;
    private ContainerOrPresenter videoPresenter;

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    searchPresenter = new ContainerOrPresenter();
    recentPresenter = new ContainerOrPresenter();
    // 假设两个 Presenter 需要互相通信。
    searchPresenter.setRecentPresenter(recentPresenter);
    }

    @Override
    public void onResume() {
    super.onResume();
    // 模拟繁琐的生命周期控制
    searchPresenter.onResume(this);
    recentPresenter.onResume(this);
    channelPresenter.onResume(this);
    videoPresenter.onResume(this);
    }
    }

复制代码


这段代码的问题就在于我们维护了完全没必要维护的生命周期,为此不得不持有了多余的引用。


所以用Fragment吧!有官方来为我们维护它的生命周期,官方还提供了Fragment + ViewModel的最佳实践让我们实现不同部分之间的数据传递,我们能够不费吹灰之力地实现MVVM架构,而未来如果我们希望把一部分功能复用或者删除,简单地操作一下Fragment就可以。这就是Fragment之于解耦,于其自带生命周期的优势所在了。


那么,回到最开始的问题,这个页面在我们加入了Fragment之后,一个更好的实现方式可能是这样……


1. 顶部的两个Tab,ViewPager + Fragment。
2. 底下的滚动页面:NestedScrollView + 4个Fragment。


大概这样:


![我们无需知道最右边的Fragment到底哪个是哪个](https://img-blog.csdnimg.cn/img_convert/231ebc8b95ac7ac4ce8b52569b93a2be.webp?x-oss-process=image/format,png)


我们无需在意滚动页面里面的Fragment装的到底是什么,我们只需要简单的在外层Fragment里面根据需要,`new Fragment()`,然后 `add` 即可,不同的部分所要实现的不同功能,我们可以全部放在不同的Fragment里面,而不同的Fragment之间我们通过`ViewModel`来通信即可。


这个思路适合几乎所有的页面。


这里我们还是用简单的代码来表示这种实践: 首先,最外层代表“动态”的Fragment我们对其进行了一些改动, 主要是添加了ViewModel,以及子Fragment的创建方式有了一些差异(这里我们过后提) 具体都在注释里面了:



/**

  • B站的“动态”Tab
    /
    public class MomentsFragment extends Fragment {
    /
    *

    • 顶部视频和综合的Tab
      */
      private ViewPager tabPager;

    private MomentsViewModel momentsViewModel;
    private FragmentActivity hostActivity;

    @Override
    public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    hostActivity = (FragmentActivity) context;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 这里 of 方法传入的参数可以是Fragment也可以是Activity
    // 如果此 Fragment 需要和Activity或者和Activity下的其他Fragments通讯,建议传入Activity作为参数
    // 值得注意的是,传入Activity创建的ViewModel会一直存续到Activity销毁,所以注意内存泄露问题
    momentsViewModel = ViewModelProviders.of(hostActivity).get(MomentsViewModel.class);
    momentsViewModel.newMessageLiveData.observe(this, new Observer() {
    @Override
    public void onChanged(Integer integer) {
    // 这里模拟网络请求回传了“动态”tab右上角的新消息数量
    }
    });
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    // return inflater.inflate(res, container, false);
    return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    tabPager = view.findViewById(R.id.view_pager);
    // 这里传入的应该是 getChildFragmentManager,不做过多解释
    MomentsFragmentAdapter adapter = new MomentsFragmentAdapter(
    getChildFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    List childList = new ArrayList<>();
    // 视频 Tab,这里相比原版有一些改动,后面解释
    // 这里也能简单看出来,相比直接传入Fragment的实例,这样做其实做到了解耦
    // 我们无需关注Moments里面到底有什么,只要把对应页面的路径传入即可
    childList.add(Routes.ROUTE_VIDEO);
    // 综合 Tab
    childList.add(Routes.ROUTE_COMMON);
    adapter.setDataList(childList);
    tabPager.setAdapter(adapter);
    }

    @Override
    public void onResume() {
    super.onResume();
    // 这里模拟进行了一个网络请求
    momentsViewModel.requestNewMessage();
    }

    @Override
    public void onDestroy() {
    super.onDestroy();
    // 避免内存泄露
    momentsViewModel.newMessageLiveData.removeObservers(this);
    }
    }
    复制代码


严格来讲,“动态”这页的代码这样就差不多可以了。 如果还要完善细节,最多也就是修改一些UI相关的内容了(这里我们把Tab的新消息数量放在这里请求只是个模拟,实际情况下Tab可能在Fragment外层,所以没法这么做)。而后续相关逻辑的添加也会主要围绕着ViewModel,可以尽量避免对Fragment本身的改动。


顺便,这里我们提到了ARouter对于Fragment的解耦效果。下面我们简单展示ARouter+Fragment所能达到的效果。


![对Fragment的依赖变成了对String的](https://img-blog.csdnimg.cn/img_convert/9f22ac15901725e57797dfe9f53b098d.webp?x-oss-process=image/format,png)


可以看到,这下我们连具体的Fragment都不需要了,原本的实现方式我们虽然可以无需了解我们需要的Fragment是哪个,但是我们还是需要手动来 `new XXXFragment`,而在使用ARouter之后,我们就只需要不同Fragment对应的路径了。


形容一下就是:张三原本需要每天自己去找李四,现在有了邮局,张三把信放进邮箱就可以直接送信给李四了,而无需亲自跑一趟。


下面我们来继续展示“动态”主页的下一级,“动态-综合”


我们在通过Fragment实现具体细节之后,动态-综合这页本身作为一个容器,只需要把容器的内容填上即可,无需自己实现任何额外的逻辑,用代码标识就是:



/**

  • 动态-综合

  • 容器Fragment,用来装里面具体的 搜索,最近访问,频道,动态列表 等子模块
    */
    @Route(path = Routes.ROUTE_COMMON)
    public class CommonFragment extends Fragment {

    private CommonViewModel commonViewModel;
    private FragmentActivity hostActivity;

    @Override
    public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    hostActivity = (FragmentActivity) context;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 这里 of 方法传入的参数可以是Fragment也可以是Activity
    // 如果此 Fragment 需要和Activity或者和Activity下的其他Fragments通讯,建议传入Activity作为参数
    // 值得注意的是,传入Activity创建的ViewModel会一直存续到Activity销毁,所以注意内存泄露问题
    commonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // 假设这个LinearLayout包裹在NestedScrollView里面,假设它叫“ll_container”
    // 借用ARouter来创建Fragment后,我们只需要四个路径,此容器Fragment的工作就全部完成啦~
    FragmentManager manager = getChildFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.add(R.id.ll_container, createFragmentByPath(Routes.ROUTE_SEARCH), Routes.ROUTE_SEARCH);
    transaction.add(R.id.ll_container, createFragmentByPath(Routes.ROUTE_RECENT), Routes.ROUTE_RECENT);
    transaction.add(R.id.ll_container, createFragmentByPath(Routes.ROUTE_CHANNEL), Routes.ROUTE_CHANNEL);
    transaction.add(R.id.ll_container, createFragmentByPath(Routes.ROUTE_VIDEO_LIST), Routes.ROUTE_VIDEO_LIST);
    transaction.commit();
    }

    private Fragment createFragmentByPath(String path) {
    return (Fragment) ARouter.getInstance().build(path).navigation();
    }
    }
    复制代码


这里创建ViewModel是为了实现父Fragment和其所有子Fragment之间的通信,具体的后面再说。


然后这里我们实现一下频道Fragment和视频列表Fragment,上代码:



/**

  • 动态-综合页-频道
    */
    @Route(path = Routes.ROUTE_CHANNEL)
    public class ChannelFragment extends Fragment {

    private CommonViewModel commonViewModel;
    private RecyclerView channelRecyclerView;
    private RecyclerView.Adapter channelAdapter;

    private FragmentActivity hostActivity;

    @Override
    public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    hostActivity = (FragmentActivity) context;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    commonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
    commonViewModel.channelData.observe(this, new Observer<List>() {
    @Override
    public void onChanged(List channelData) {
    if (null != channelData) {
    // channelAdapter.setDataList(channelData);
    channelAdapter.notifyDataSetChanged();
    }
    }
    });
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // channelRecyclerView = view.findViewById(R.id.rv);
    // channelAdapter = new ChannelAdapter();
    channelRecyclerView.setLayoutManager(new GridLayoutManager(hostActivity, 2));
    channelRecyclerView.setAdapter(channelAdapter);
    }

    @Override
    public void onResume() {
    super.onResume();
    commonViewModel.requestChannel();
    }

    @Override
    public void onDestroy() {
    super.onDestroy();
    commonViewModel.channelData.removeObservers(this);
    }
    }
    复制代码



/**

  • 动态-综合页-视频列表
    */
    @Route(path = Routes.ROUTE_VIDEO_LIST)
    public class VideoListFragment extends Fragment {

    private CommonViewModel commonViewModel;
    private RecyclerView videoRecyclerView;
    private RecyclerView.Adapter videoAdapter;

    private FragmentActivity hostActivity;

    @Override
    public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    hostActivity = (FragmentActivity) context;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    commonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
    commonViewModel.channelData.observe(this, new Observer<List>() {
    @Override
    public void onChanged(List channelData) {
    if (null != channelData) {
    // channelAdapter.setDataList(channelData);
    videoAdapter.notifyDataSetChanged();
    }
    }
    });
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // videoRecyclerView = view.findViewById(R.id.rv);
    // videoAdapter = new VideoAdapter();
    videoRecyclerView.setLayoutManager(new LinearLayoutManager(hostActivity));
    videoRecyclerView.setAdapter(videoAdapter);
    }

    @Override
    public void onResume() {
    super.onResume();
    commonViewModel.requestVideo();
    }

    @Override
    public void onDestroy() {
    super.onDestroy();
    commonViewModel.videoData.removeObservers(this);
    }
    }
    复制代码


可以看到,我们从原本的一个大类负责所有的功能,逐渐变成了大类只做容器,功能细化拆分给不同的child fragment,从而提高灵活性和复用性。


这样做还有一个好处,那就是不同的页面现在可以交给不同的人来开发了,避免了冲突,提高了效率。


### Fragment和ARouter



> 
> 项目地址:[github.com/alibaba/ARo…]( )
> 
> 
> 


上面我们提到Fragment + ARouter是一个很好的实践。


关于ARouter这里不多说,没用过的人可以百度一下。


这本身是一个用来实现模块化中不同模块通信的路由框架,它将Android里面不同Activity之间的依赖关系实现了降维打击,简化成了不同的路由地址之间的关系,从而实现解耦。



**深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

![](https://img-blog.csdnimg.cn/direct/743b668910224b259a5ffe804fa6d0db.png)
![img](https://img-blog.csdnimg.cn/img_convert/2d723db997bf97d576458cb4de68850a.png)
![img](https://img-blog.csdnimg.cn/img_convert/39ff0f00846d8b02be32e8afaf703f96.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**

低效又漫长,而且极易碰到天花板技术停滞不前!**

![](https://img-blog.csdnimg.cn/direct/743b668910224b259a5ffe804fa6d0db.png)
[外链图片转存中...(img-yQ6jKBZl-1715737524932)]
[外链图片转存中...(img-BJGZDXNS-1715737524932)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值