code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群

作者:子木_lsy
链接:juejin.im/post/6844904194869051405
声明:本文已获子木_lsy授权发表,转发等请联系原作者授权

这篇文章将概述 Android组件化的架构搭建FlutterAndroid 如何混合开发 (整个App只有首页是用原生Android完成,其他页面都是引入之前的做好的Flutter页面) ,主宿主程序由 Android 搭建,采用了组件化的架构搭建整个 App ,不同业务,对应不同的 module 工程,业务之间采用接口通信 (ARouter) ,以 module 的形式混入 Flutter,通过 MethodChannel 和 Flutter 端进行数据通信等,且这些功能实现源码开源,感兴趣的小伙伴可以移步至 GitHub。https://github.com/persilee/android_ctrip

以下博文会分为4个部分概述:

  • 项目完成的功能预览

  • 项目组件化结构分析

  • 项目功能详细概述(所用知识点)

  • Android Flutter 混合开发

项目完成的功能预览

首先,我们还是通过一个视频来快速预览下项目完成的功能和运行效果,如下

https://www.bilibili.com/video/BV1W54y1B72U/

看完视频后,其实大部分功能和之前的 纯flutter项目 功能相同,只是首页新增了4个tab推荐页面及携程二楼和布局改变。

大家也可扫描,安装体验:

Android Flutter 混合开发高仿大厂 App_第1张图片 手机扫描二维码安装

项目组件化结构分析

项目结构图预览

其次,分析梳理下项目结构,项目的结构大致如图,还有一些细枝末节的没有体现在图里:

Android Flutter 混合开发高仿大厂 App_第2张图片 project structure

项目结构分析

业务工程

把具体独立的业务都拆分成单独的 module 减小项目的维护压力

  • ft_home: 首页模块,这个模块其实还可以继续拆分,可把4个 tab (精选、附近、景点、美食) 页都拆成模块,这里我暂时没有拆分,后续会完成

  • ft_destination: 目的地模块,其实并没有建立这个模块,因为直接引入了之前做好的 flutter 页面

  • ft_travel: 旅拍模块,同样也使用了 flutter 页面

  • flutter: flutter模块,这个模块是从 flutter_module 中自动生成的,后面介绍到

基础库工程

把具体的功能都封装成独立的库供业务模块使用,降低项目的维护成本及代码之间耦合性

  • lib_network: 网络库,使用 okhttp 插件二次封装,业务层简单的调用即可

  • lib_webview: 打开网页的webview库,使用了 agentweb 插件二次封装,业务层只需要一句代码即可完成网页的跳转

  • lib_image_loader: 图片加载库,使用了 glide 插件二次封装,业务层只需一句代码即可加载不同参数的图片

  • lib_asr: 百度AI语音库,通过 Android 集成好供 Flutter 端使用

  • lib_common_ui: 公共UI库,重复多次使用的页面集中管理

  • lib_base: 基础库,通过 ARouter 的 service 功能暴露接口提供服务给业务层,当然业务层也可以在这里暴露接口供外界使用

这里有一些使用的插件并没有在项目结构图里体现出来(结构图空间有限)。

插件

在这里把项目使用的插件整理列举出来供大家参考:

  • magicindicator 强大、可定制、易扩展的 ViewPager 指示器框架,首页的4个 tab (精选、附近、景点、美食) 就是用这个实现的。

  • immersionbar 一句代码轻松实现状态栏、导航栏沉浸式管理

  • pagerBottomTabStrip 页面底部和侧边的导航栏,首页、目的地、旅拍、我的页面切换就是用这个实现的。

  • rxjava/rxandroid 异步和链式编程

  • butterknife view注入插件,配合Android插件使用,可快速自动生成 init view的代码,不用写一句 findViewById 的代码。

  • gson json解析,配合Android插件使用,可快速生成实体类

  • smartRefreshLayout 智能下拉刷新框架,携程二楼及下拉刷新加载更多就是用这个实现的

  • eventbus 发布/订阅事件总线,优雅的完成组件之间通信

  • arouter 依赖注入、路由跳转、注册service,优雅的完成模块之间的通信

  • okhttp 网络请求插件

  • agentweb webview框架,进行简单的二次封装可优雅的进行网页跳转

  • glide 高性能、可扩展的图片加载插件

  • banner 图片轮播控件

基本就是这些了,应该没有漏的,插件的详细使用,请进入各插件的 GitHub 主页。

在此,把我项目的插件引入代码及版本管理的 gradle 代码贴出来,如下:插件引入代码:

dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    implementation rootProject.depsLibs.appcompat    implementation rootProject.depsLibs.legacy    implementation rootProject.depsLibs.recyclerview    implementation rootProject.depsLibs.constraintlayout    implementation rootProject.depsLibs.cardview    //tab指示器    implementation rootProject.depsLibs.magicindicator    //沉浸式    implementation rootProject.depsLibs.immersionbar    //导航栏    implementation rootProject.depsLibs.pagerBottomTabStrip    //rxjava    implementation rootProject.depsLibs.rxjava    //rxandroid    implementation rootProject.depsLibs.rxandroid    //view 注入    implementation rootProject.depsLibs.butterknife    //view 注入    annotationProcessor rootProject.depsLibs.butterknifeCompiler    //gson    implementation rootProject.depsLibs.gson    //banner    implementation rootProject.depsLibs.banner    //smartRefreshLayout 上下拉刷新    implementation rootProject.depsLibs.smartRefreshLayout    implementation rootProject.depsLibs.refreshHeader    implementation rootProject.depsLibs.refreshHeaderTwoLevel    implementation rootProject.depsLibs.refreshFooter    //eventbus    implementation rootProject.depsLibs.eventbus    //arouter库    implementation(rootProject.depsLibs.arouterapi) {        exclude group: 'com.android.support'    }    annotationProcessor rootProject.depsLibs.aroutercompiler    //引入home模块    implementation project(':ft_home')    //引入图片加载库    implementation project(':lib_image_loader')    //引入网络库    implementation project(':lib_network')    //webview    implementation project(':lib_webview')    //引入基础ui库    implementation project(':lib_common_ui')    //base库    implementation project(':lib_base')    //引入flutter模块    implementation project(':flutter')    //引入百度AI语音库    implementation project(':lib_asr')}

版本管理代码 (统一管理版本号) :

ext {    android = [            compileSdkVersion: 29,            buildToolsVersion: "29.0.0",            minSdkVersion    : 19,            targetSdkVersion : 29,            applicationId    : 'net.lishaoy.android_ctrip',            versionCode      : 1,            versionName      : '1.0',            multiDexEnabled  : true,    ]    depsVersion = [            appcompat            : '1.1.0',            legacy               : '1.0.0',            recyclerview         : '1.0.0',            constraintlayout     : '1.1.3',            cardview             : '1.0.0',            magicindicator       : '1.5.0',            immersionbar         : '3.0.0',            pagerBottomTabStrip  : '2.3.0X',            glide                : '4.11.0',            glidecompiler        : '4.11.0',            butterknife          : '10.2.1',            butterknifeCompiler  : '10.2.1',            rxjava               : '3.0.0',            rxandroid            : '3.0.0',            okhttp               : '4.7.2',            okhttpLogging        : '4.7.2',            gson                 : '2.8.6',            banner               : '2.0.10',            smartRefreshLayout   : '2.0.1',            refreshHeader        : '2.0.1',            refreshFooter        : '2.0.1',            refreshHeaderTwoLevel: '2.0.1',            eventbus             : '3.2.0',            agentweb             : '4.1.3',            arouterapi           : '1.5.0',            aroutercompiler      : '1.2.2',    ]    depsLibs = [            appcompat            : "androidx.appcompat:appcompat:${depsVersion.appcompat}",            legacy               : "androidx.legacy:legacy-support-v4:${depsVersion.legacy}",            recyclerview         : "androidx.recyclerview:recyclerview:${depsVersion.recyclerview}",            constraintlayout     : "androidx.constraintlayout:constraintlayout:${depsVersion.constraintlayout}",            cardview             : "androidx.cardview:cardview:${depsVersion.cardview}",            magicindicator       : "com.github.hackware1993:MagicIndicator:${depsVersion.magicindicator}",            immersionbar         : "com.gyf.immersionbar:immersionbar:${depsVersion.immersionbar}",            pagerBottomTabStrip  : "me.majiajie:pager-bottom-tab-strip:${depsVersion.pagerBottomTabStrip}",            glide                : "com.github.bumptech.glide:glide:${depsVersion.glide}",            glidecompiler        : "com.github.bumptech.glide:compiler:${depsVersion.glidecompiler}",            butterknife          : "com.jakewharton:butterknife:${depsVersion.butterknife}",            butterknifeCompiler  : "com.jakewharton:butterknife-compiler:${depsVersion.butterknifeCompiler}",            rxjava               : "io.reactivex.rxjava3:rxjava:${depsVersion.rxjava}",            rxandroid            : "io.reactivex.rxjava3:rxandroid:${depsVersion.rxandroid}",            okhttp               : "com.squareup.okhttp3:okhttp:${depsVersion.okhttp}",            okhttpLogging        : "com.squareup.okhttp3:logging-interceptor:${depsVersion.okhttpLogging}",            gson                 : "com.google.code.gson:gson:${depsVersion.gson}",            banner               : "com.youth.banner:banner:${depsVersion.banner}",            smartRefreshLayout   : "com.scwang.smart:refresh-layout-kernel:${depsVersion.smartRefreshLayout}",            refreshHeader        : "com.scwang.smart:refresh-header-classics:${depsVersion.refreshHeader}",            refreshHeaderTwoLevel: "com.scwang.smart:refresh-header-two-level:${depsVersion.refreshHeader}",            refreshFooter        : "com.scwang.smart:refresh-footer-classics:${depsVersion.refreshFooter}",            eventbus             : "org.greenrobot:eventbus:${depsVersion.eventbus}",            agentweb             : "com.just.agentweb:agentweb:${depsVersion.agentweb}",            arouterapi           : "com.alibaba:arouter-api:${depsVersion.arouterapi}",            aroutercompiler      : "com.alibaba:arouter-compiler:${depsVersion.aroutercompiler}",    ]}

项目功能详细概述(所用知识点)

这里主要对首页功能及知识点进行概述,由于其他页面是引入了之前的 Flutter 页面, 具体功能在 Flutter 10天高仿大厂App及小技巧积累总结 已经介绍过了,在这就不再阐述。

首页重点概述以下功能的实现:

  • 下拉刷新、携程二楼

  • 搜索appBar

  • 渐变色网格导航

  • banner组件

  • 多状态的tab指示器 (滚动固定顶部)

下拉刷新、携程二楼

首先,看看具体的效果图,如图:

second floor

下拉刷新和携程二楼是使用 smartRefreshLayout 插件完成的,实现代码如下:

private void initRefreshMore() {    homeHeader.setRefreshHeader(new ClassicsHeader(getContext()), -1, (int) Utils.dp2px(76)); //设置下拉刷新及二楼header的高度    homeHeader.setFloorRate(1.6f); //设置二楼触发比率    homeRefreshContainer.setPrimaryColorsId(R.color.colorPrimary, R.color.white); //设置下拉刷新及二楼提示文字颜色    homeRefreshContainer.setOnMultiListener(new SimpleMultiListener() {        @Override        public void onLoadMore(@NonNull RefreshLayout refreshLayout) {            loadMore(refreshLayout); //加载更多        }        @Override        public void onRefresh(@NonNull RefreshLayout refreshLayout) {            refreshLayout.finishRefresh(1600); //设置下拉刷新延迟        }        @Override        public void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight) {            homeSecondFloorImg.setVisibility(View.VISIBLE);  //隐藏二楼背景图            homeSearchBarContainer.setAlpha(1 - Math.min(percent, 1)); //改变searchBar透明度        }        @Override        public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {            if (oldState == RefreshState.ReleaseToTwoLevel) {  //即将去往二楼状态处理                homeSecondFloorImg.setVisibility(View.GONE);                homeHeaderContent.animate().alpha(1).setDuration(666);            } else if (newState == RefreshState.PullDownCanceled) { //下拉取消状态处理                homeHeaderContent.animate().alpha(0).setDuration(666);            } else if (newState == RefreshState.Refreshing) { //正在刷新状态处理                homeHeaderContent.animate().alpha(0).setDuration(666);            } else if (oldState == RefreshState.TwoLevelReleased) { // 准备去往二楼完成状态处理,这里打开webview                WebViewImpl.getInstance().gotoWebView("https://m.ctrip.com/webapp/you/tsnap/secondFloorIndex.html?isHideNavBar=YES&s_guid=feb780be-c55a-4f92-a6cd-2d81e04d3241", true);                homeHeader.finishTwoLevel();            } else if (oldState == RefreshState.TwoLevel) { //到达二楼状态处理                homeCustomScrollView.setVisibility(View.GONE);                homeHeaderContent.animate().alpha(0).setDuration(666);            } else if (oldState == RefreshState.TwoLevelFinish) { //二楼完成状态处理                homeCustomScrollView.setVisibility(View.VISIBLE);                homeCustomScrollView.animate().alpha(1).setDuration(666);            }        }    });}

XML 页面布局文件代码如下:

                                                    ...    

具体实现详情,可移步 GitHub 查看源码。

搜索appBar

搜索栏的滚动的 placeholder 文字是使用 banner 插件实现的,点击搜索框可跳转到搜索页面 (flutter写的搜索页面) ,跳转页面后可以把 placeholder 文字带到 flutter 搜索页面。

效果如图:

Android Flutter 混合开发高仿大厂 App_第3张图片 search bar

滚动的placeholder文字实现代码如下 (搜索框的实现就不再这里展示都是一些XML布局代码):

        homeSearchBarPlaceholder                .setAdapter(new HomeSearchBarPlaceHolderAdapter(homeData.getSearchPlaceHolderList())) // 设置适配器                .setOrientation(Banner.VERTICAL) // 设置滚动方向                .setDelayTime(3600) // 设置间隔时间                .setOnBannerListener(new OnBannerListener() {                    @Override                    public void OnBannerClick(Object data, int position) {  //点击打开 flutter 搜索页面                        ARouter.getInstance()                                .build("/home/search")                                .withString("placeHolder", ((Home.SearchPlaceHolderListBean) data).getText())                                .navigation();                    }                });    }

searchBar的具体功能不过多阐述,和之前的项目一致。

渐变色网格导航

渐变色网格导航基本都是一些 XML 页面布局代码,只是我把它封装成了单独的组件,效果如图

Android Flutter 混合开发高仿大厂 App_第4张图片

封装之后的引入就非常简单,代码如下:

            ...

具体实现详情,可移步 GitHub 查看源码。

banner组件

banner组件也是用 banner 插件实现的,如图

Android Flutter 混合开发高仿大厂 App_第5张图片 banner

实现代码如下:

private void initBanner() {    homeBanner.addBannerLifecycleObserver(this)            .setAdapter(new HomeBannerAdapter(homeData.getBannerList())) //设置适配器            .setIndicator(new EllipseIndicator(getContext()))           //设置指示器,如图的指示器是我自定义的插件里并没有提供            .setIndicatorSelectedColorRes(R.color.white)                //设置指示器颜色            .setIndicatorSpace((int) BannerUtils.dp2px(10))             //设置间距            .setBannerRound(BannerUtils.dp2px(6));                      //设置圆角}

多状态的tab指示器

多状态的tab指示器的实现需要注意很多细节,因为它是在首页的 fragment 的 ScrollView 里嵌入 viewPaper,首先你会发现 viewPaper 不显示的问题,其次是滚动不流畅的问题,这两个问题我的解决方案是:

  • viewPaper 不显示的问题:使用自定义的 ViewPager 重写 onMeasure 方法,重新计算高度

  • 滚动不流畅的问题:使用自定义的 ScrollView,重写 computeScroll 和 onScrollChanged 重新获取滚动距离

实现效果如图:

tab page

这个功能实现代码过多不便在这里展示,具体实现详情,可移步 GitHub 查看源码。

Android Flutter 混合开发

这个项目的实现只有首页是用 Android 原生实现,其他的页面均是 Flutter 实现的,之前 纯Flutter项目。

Android 引入 Flutter 进行混合开发,需要以下几个步骤

  • 建立一个flutter module

  • 编写flutter代码 (创建 flutter 路由)

  • flutter 和 android 之间相互通信

下面依次概述这几部分是如何操作实现的。

建立一个flutter module

这个应该不用过多描述,基本操作大家都会 File --> New --> New Module 如图:

Android Flutter 混合开发高仿大厂 App_第6张图片 flutter module

新建完成之后,android studio 会自动生成配置代码到 gradle 配置文件里,且生成一个 flutter 的 library 模块。

Tips:新建的时候最好 flutter module 和 android 项目放到同级目录下;新版的 android studio 才会自动生成 gradle 配置代码,老版本貌似需要手动配置

如,没有生成 gradle 配置代码,你需要在根项目的 settings.gradle 文件里手动加入如下配置:

setBinding(new Binding([gradle: this]))evaluate(new File(  settingsDir, //设置根路径,根据具体flutter module路径配置  'flutter_module/.android/include_flutter.groovy'))include ':flutter_module'

还需在宿主工程 (没改名的话都是app) 的 build.gradle 引入 flutter, 如下:

dependencies {    ...    //引入flutter模块    implementation project(':flutter')    ...}

编写flutter代码

编写flutter代码,在 flutter module 里按照正常 flutter 开发流程编写 flutter 代码即可。(我项目里的 flutter 的代码是之前项目都写好的,复制过来,改改包的引入问题,就可以运行了。)

这里需要注意的是,flutter 有且只有一个入口,就是 main() 函数,我们需要在这里处理好 flutter 页面的跳转问题。

在 android 端,创建 flutter 页面,代码如下:

    Flutter.createView(getActivity(),getLifecycle(),"destination");

Flutter.createView 需要3个参数 activity 、lifecycle 、route ,这个 route 就是要传递到 flutter 端的,当然,它是 String 类型的,我们可以自由发挥传递普通字符串或 json 字符串等。

我们也可以通过其他的方式创建 flutter 页面,如:Flutter.createFragment() 、 FlutterActivity.withNewEngine()、 FlutterFragment.createDefault() 等。

具体的使用,可前往 Flutter官方文档 查阅。

那么,flutter 端如何接收这个 route 参数,是通过 window.defaultRouteName,此项目里管理 flutter 端路由代码如下:

void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      debugShowCheckedModeBanner: false,      title: 'Flutter model',      theme: ThemeData(        primarySwatch: Colors.blue,        fontFamily: 'PingFang',      ),      home: _widgetRoute(window.defaultRouteName), // 通过 window.defaultRouteName 接收 android 端传来的参数    );  }}Widget _widgetRoute(String defaultRouteName) {    Map params = convert.jsonDecode(defaultRouteName); //解析参数    defaultRouteName = params['routeName'];    placeHolder = params['placeHolder'];    switch (defaultRouteName) { // 根据参数返回对应的页面        ...        case 'destination/search':            return DestinationSearchPage(                hideLeft: false,        );        ...        default:            return Center(                child: Column(                mainAxisAlignment: MainAxisAlignment.center,                children: [                    Text('not found $defaultRouteName',                        textDirection: TextDirection.ltr),                ],                ),            );    }}

其实,flutter 端接收这个 route 参数,还有一种方法,就是通过 onGenerateRoute,它是 MaterialApp 里的一个方法。

代码如下:

onGenerateRoute: (settings){ //通过 settings.name 获取android端传来的参数    return _widgetRoute(settings.name);},

flutter 和 android 之间相互通信

flutter 端可以调用 android 端的方法及相互传递数据是如何实现的,flutter 官方提供了3个方法可以实现,分别是:

  • EventChannel:单向的持续通信,如:网络变化、传感器等。

  • MethodChannel:一次性通信,一般适用如方法的调用。

  • BasicMessageChannel:持续的双向通信。

此项目里采用了 MethodChannel 方法进行通信,如:flutter 端调用 android 端的AI智能语音方法以及 flutter 打开 android 端页面就是用 MethodChannel 实现的。

flutter 端调用 android 端的AI智能语音方法代码如下:

class AsrManager {  static const MethodChannel _channel = const MethodChannel('lib_asr');  //开始录音  static Future start({Map params}) async {    return await _channel.invokeMethod('start', params ?? {});  }  //停止录音    ...  //取消录音    ...  //销毁    ...}

flutter 打开 android 端页面代码如下:

class MethodChannelPlugin {  static const MethodChannel methodChannel = MethodChannel('MethodChannelPlugin');  static Future gotoDestinationSearchPage() async {    try {      await methodChannel.invokeMethod('gotoDestinationSearchPage'); //gotoDestinationSearchPage 参数会传到android端    } on PlatformException {      print('Failed go to gotoDestinationSearchPage');    }  }    ...}

android 接收也是通过 MethodChannel ,具体实现代码如下:

public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {    private static MethodChannel methodChannel;    private Activity activity;    private MethodChannelPlugin(Activity activity) {        this.activity = activity;    }    //调用方通过 registerWith 来注册flutter页面    public static void registerWith(FlutterView flutterView) {        methodChannel = new MethodChannel(flutterView, "MethodChannelPlugin");        MethodChannelPlugin instance = new MethodChannelPlugin((Activity) flutterView.getContext());        methodChannel.setMethodCallHandler(instance);    }    @Override    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {        if (methodCall.method.equals("gotoDestinationSearchPage")) { // 收到消息进行具体操作            EventBus.getDefault().post(new GotoDestinationSearchPageEvent());            result.success(200);        }         ...        else {            result.notImplemented();        }    }}

android flutter 混合开发基本就是这3个步骤,其他一些细节及具体的流程请参考 GitHub 项目源码。

最后附上项目及博客地址:

项目地址:https://github.com/persilee/android_ctrip
博客地址:https://h.lishaoy.net/androidctrip

相关阅读

1 那些初学者实践 Flutter 最常出现的错误
2 Flutter 2020首个稳定版 1.17 重磅发布:多个新增特性
3 Flutter 开发小结 | Tips
4 使用Flutter一年后,这是我得到的经验
5 Flutter 添加到现有项目

如果你有写博客的好习惯欢迎投稿赞+在看,小生感恩❤️

更多相关文章

  1. Eclipse用svn管理Android项目问题 (转来的)
  2. Android零基础入门第15节:掌握Android Studio项目结构,扬帆起航
  3. Android应用项目绑定appcom_v7打包时,出现错误:"XXX"isnottransla
  4. Android 轻松实现语音识别的完整代码[转]
  5. Android 启动页面优化 (白屏 、等待的问题)
  6. Android Studio 添加 C、C++ 代码
  7. Android 实现语音识别的完整代码
  8. Android 中使用代码动态网格布局
  9. 现有Android项目引入ReactNative--九步大法

随机推荐

  1. 十、正则表达式
  2. IntelliJ IDEA 激活码2021,激活码2020 永
  3. 跨域配置相关选项
  4. Golang笔记之基本组成元素
  5. 作业2-html注册页面和课程表
  6. 初识初学表单
  7. 前后端Date类型装换
  8. 图片上传
  9. 我的第一款 Drone 插件
  10. “Affinity Publisher”让排版更简单,轻松