网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事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);
}
}
复制代码 - 顶部视频和综合的Tab
严格来讲,“动态”这页的代码这样就差不多可以了。 如果还要完善细节,最多也就是修改一些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)**