二、Flutter实战

第四章Flutter实战

4.1 Fluter APP 代码结构

1.png
  • ''lib” Dart代码目录


    2.png
  • “ios”、“android”是两个平台相关代码、配置目录
  • pubspec.yaml是依赖的组件库配置如:
environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  http: 0.13.3
  dio: ^4.0.0  #大于等于4.0.0小于5.0.0
  image_picker: ^0.8.3+2

也可以配置本地图片,在工程目录下创建个images文件,将所需图片导入到该目录,并配置如下:

  # To add assets to your application, add an assets section, like this:
  assets:
     - images/

注意: 由于 yaml 文件对缩进严格,所以必须严格按照每一层两个空格的方式进行缩进,此处 assets 前面应有两个空格

4.2 代码实战

模拟微信APP,项目名称WeChatDemo。

  • 项目入口:main
void main() {
  runApp(MyApp());

}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        highlightColor: Color.fromRGBO(1, 0, 0, 0.0),
        splashColor: Color.fromRGBO(1, 0, 0, 0.0),
        cardColor: Color.fromRGBO(1, 1, 1, 0.65),//有透明层叠视图的设置
        primarySwatch: Colors.blue,
        appBarTheme: AppBarTheme(iconTheme: IconThemeData(color: Colors.black))
      ),
      home: RootPage(),
    );
  }
}
  • 主要模块:微信、通讯录、发现、我
 class RootPage extends StatefulWidget {
  const RootPage({Key? key}) : super(key: key);

  @override
  _RootPageState createState() => _RootPageState();
}

class _RootPageState extends State<RootPage> {
  int _currentIndex = 0;
  List <Widget> _pages = [ChatPage(),FriendsPage(),DiscoverPage(),MinePage()];
  final PageController _controller = PageController(initialPage: 0);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView( //通过pageView 来保持状态
        physics: NeverScrollableScrollPhysics(), //不允许滚动
        children: _pages,
        controller: _controller,
      ),
      bottomNavigationBar: BottomNavigationBar(
        onTap: (index){
          setState(() {
            _currentIndex  = index;
          });
          _controller.jumpToPage(index);
        },
        selectedFontSize: 12,
        unselectedFontSize: 12,
        currentIndex: _currentIndex,
        fixedColor: Colors.green,
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(
              icon: Image.asset('images/tabbar_chat.png',height: 25,width: 25),
              activeIcon: Image.asset('images/tabbar_chat_hl.png',height: 25,width: 25,),
              label: '微信'
          ),
          BottomNavigationBarItem(
              icon: Image.asset('images/tabbar_friends.png',height: 25,width: 25,),
              activeIcon: Image.asset('images/tabbar_friends_hl.png',height: 25,width: 25,),
              label: '通讯录'
          ),
          BottomNavigationBarItem(
              icon: Image.asset('images/tabbar_discover.png',height: 25,width: 25,),
              activeIcon: Image.asset('images/tabbar_discover_hl.png',height: 25,width: 25,),
              label: '发现'
          ),
          BottomNavigationBarItem(
              icon: Image.asset('images/tabbar_mine.png',height: 25,width: 25,),
              activeIcon: Image.asset('images/tabbar_mine_hl.png',height: 25,width: 25,),
              label: '我'
          ),
        ],
      ),
    );
  }
}

详细代码请参考项目WechatDemo

4.2.1 AutomaticKeepAliveClientMixin 混入

state 混入 AutomaticKeepAliveClientMixin,保持Tabbar 切换时state不被重新初始化

  • state 混入 AutomaticKeepAliveClientMixin
class _ChatPageState extends State<ChatPage>
    with AutomaticKeepAliveClientMixin
  • state 添加成员属性
  @override
  bool get wantKeepAlive => true;
  • 在 build 方法中添加
    super.build(context);
4.2.2 混合开发
  • Flutter调原生功能,如我的页面,更换头像需要调用系统相册,有两种实现方式
    1、MethodChannel 通道
    类似于通知,通过发送消息,监听消息回调实现与原生通讯
//第一步:Flutter端创建通道,传入唯一标识
 MethodChannel _methodChannel = MethodChannel('mine_page');
 
//第二步:Flutter端发送消息
_methodChannel.invokeListMethod('picture'); //发送了一条要更换图片的消息

//第三步:原生注册‘mine_page’通道,并监听消息发送
FlutterViewController *vc = (FlutterViewController *)self.window.rootViewController;
FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"mine_page" binaryMessenger:vc];
self.methodChannel = methodChannel;
//监听flutter消息
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
     if ([call.method isEqualToString:@"picture"]) {
           UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
           imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
           imagePicker.delegate = self;
           imagePicker.modalPresentationStyle = UIModalPresentationFullScreen;
           [vc presentViewController:imagePicker animated:YES completion:nil];
      }
 }];

//第四步:原生发送拿到相册消息
[self.methodChannel invokeMethod:@"imagePath" arguments:imagePath];

//第五步:dart监听回调,并更新状态
 _methodChannel.setMethodCallHandler((call) {
     if(call.method == 'imagePath') {
         String imagePath = call.arguments.toString().substring(7); //截取前面7个字符,拿到的参数带有file://
         setState(() {
           _avatarFile = File(imagePath);
         });
      }
    return Future(() => null);
 });

注:原生与Flutter交互的过程中必须确保是同一个通道,唯一性

2、ImagePicker,三方插件无需原生写多余代码

  void _imagePick() async {
    XFile? file =  await ImagePicker().pickImage(source: ImageSource.gallery); //相册
    if(file != null) {
      setState(() {
        _avatarFile = File(file.path);
      });
    }
  }
  • 原生项目中使用Flutter页面
    1、创建Flutter 组件,在组件项目中写dart代码


    3.png
    • Flutter App: 创建一个完整的flutter项目,里面包含安卓和iOS项目
    • Flutter Module: 创建Flutter组件时使用,混编到已有的安卓/iOS工程
    • Flutter Plugin:就是Plugin的方式进行开发,比如一些百度地图,flutter不提供,通过自建Plugin,通过Plugin中的android和ios原生项目,集成原生百度功能,通过plugin中的lib中的方法来进行flutter调用百度的相关方法
    • Flutter Package:纯Dart插件工程,仅包含Dart层的实现,往往定义一些公共Widget

    2、通过cocoapods 导入到原生项目中,如:本地有个flutter_moudle组件,原生项目中需要植入Flutter页面,podfile文件配置如下:


    4.png

3、导入Flutter库,并使用
注意:flutter页面和原生页面频繁切换会导致内存泄漏

4.2.3 安装包结构

用Xcode打开项目,选择真机编译,选择Products ->Runner.app -> 右键Show in Finder右键显示包内容,可以看到编译后结构:可执行文件、资源文件、签名、Frameworks。重点关注下Frameworks


5.png
  • App.framewok: dart代码编译后的产物,App可执行文件(二进制机器代码)
  • Flutter.framewok: 就是Flutter SDK源码文件,我们看到可执行文件大小是65.7M,而Flutter SDK目录下的同名framework也是65.7M,很好的证明了APP在打包过程中会将Flutter SDK 引擎导入到安装包中,这也是Flutter App安装包比原生项目体积大的原因


    6.png

路径:/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-armv7_arm64/Flutter.framework

4.3 Flutter 插件开发(plugin )

  • 1、Flutter插件介绍
    一种专用的Dart包,其中包含用Dart代码编写的API,以及针对Android(使用Java或Kotlin)和针对iOS(使用OC或Swift)平台的特定实现(另外也可以包含Native的组件代码),也就是说插件包括原生代码与Dart代码。插件开发完成后,将上传到dart插件管理服务仓库,类似于maven、pod库,然后在flutter开发过程中可以通过pubspec.yaml(dart包管理配置文件)来获取插件服务。

  • 2、为什么要开发Flutter插件
    首先,虽然Flutter的生态现在已经越来越完善了,但是相比于Android跟iOS原生的生态体系,还是远远不够。很多在Android跟iOS原生上有的很酷炫的库,在Flutter中还没有或者是并没有那么的完善。其次,想必大家在原生工程里都有一套用了多年的稳定基础组件,包括网络组件、数据组件等,要重新在Flutter中用dart来搭建一套,时间成本、风险成本、组件兼容性等都是不可控的。所以,最理想的方式就是Flutter的基础组件可以对我们现有原生的组件做一层包装,然后提供接口给Flutter模块进行调用,这样一来什么时间、风险、兼容性都不是问题。我们只要维护一套原生组件就好,Flutter组件只是一层包装,并不在意内部如何去实现。那么Flutter跟原生怎么进行交互呢?

  • 3、Flutter如何与原生交互
    1)MethodChannel 通道, 具体使用4.2.2混合开发已介绍。Flutter与原生的交互模型,类似于一种C-S模型。其中Flutter为Client层,原生为Server层,两者通过MethodChannel进行消息通信,原生端向Flutter提供已有的Native组件功能。

    2)究竟什么是MethodChannel
    Flutter定义了3种Channel模型,分别是: BasicMessageChannel:用于传递字符串和半结构化的信息;MethodChannel:用于传递方法调用(methodinvocation)EventChannel: 用于数据流(event streams)的通信。3种channel基本类似,这里以MethodChannel展开介绍
    3) MethodChannel有3个重要的成员变量:

- String name    
一个Channel对象通过name来进行唯一的标识,所以在Channel的命名上一定要独一无二,推荐采用组件名_Channel名 组合来进行命名

- BinaryMessenger messenger   
BinaryMessenger是Platform端与Flutter端通信的工具,其实是个中间信使,当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。BinaryMessenger维护了一个map

Binarymessenger在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。Binarymessenger只和BinaryMessageHandler打交道,而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger返回。

- MethodCodec codec  
消息编解码器Codec主要用于将二进制格式的数据转化为Handler能够识别的数据,MethodCodec主要是对MethodCall中这个对象进行序列化与反序列化,MethodCall是Flutter向Native发起调用产生的对象,其中包含了方法名以及一个参数集合

  • 4、插件工程创建
    推荐通过命令行来创建,因为通过IDE来创建有时候会卡住,而且会比较慢
flutter create --org com.plug.jcfc.cn --template=plugin --platforms=android,ios -i objc -a java flutter_plug

  • 5、目录结构
    [图片上传失败...(image-236f71-1635245763669)]

  • ib dart模块

  • android android模块

  • ios iOS模块

  • example 示例测试工程可用于插件的调试

  • pubspec.yaml flutter项目的配置文件
    ….

    pubspec.yaml: dart生态下的包管理配置文件类似 Android中的gradle、iOS中的Podfile,在这里可以统一管理整个flutter工程的dart依赖包,以及管理整个插件的发布属性。

  • 6、原生端开发
    实现MethodCallHandler接口,注册MethodChannel对象,MethodChannel在创建时一定要保证name唯一
    将MethodHandler接口注册到MethodChannel中
    包装原生端组件,包括一些二方库、三方库,将包好的方法通过MethodCallHandler暴露给Flutter端

  • 7、Flutter端开发
    找到MethodChannel对象,通过唯一标识name,注意(name一定要与原生端注册的一致)
    定义dart方法,因为要保证方法的执行不产生阻塞,所以推荐用Future async await .相关的语法见dart语法
    调用methodChannel.invokeMothed()与原生进行通信

  • 8、插件测试
    在example/lib/main.dart下调用插件中的方法,然后直接通过命令将工程跑起来查看输出

fultter run

插件都还没有发布,为什么example工程可以直接引用?看一下example目录下的pubspec.yaml文件,里面有

dependencies:
  flutter:
    sdk: flutter

  flutter_plug:
    # When depending on this package from a real application you should use:
    #   flutter_plug: ^x.y.z
    # See https://dart.dev/tools/pub/dependencies#version-constraints
    # The example app is bundled with the plugin so we use a path dependency on
    # the parent directory to use the current plugin's version.
    path: ../ 相对路径

pubspec.yaml 不但可以引用服务器上的插件,也可以引用本地路径下的插件。如此我们可以在插件未发布的情况下,直接在本地的测试工程里对插件进行测试

  • 9、插件发布
  • 完善pubspec.yaml文件
name: 插件名称  
description: 插件描述  
version: 0.0.1 版本号  
homepage: 项目主页地址  
publish_to: 填写私有服务器的地址(如果是发布到flutter pub则不用填写,插件默认是上传到flutter pub)  
  • 检查发布条件
flutter packages pub publish --dry-run  

--dry-run 参数表示本次执行会检查插件的配置信息是否有效,插件是否满足上传条件。如果成功的话并不会真正的将插件上传,而是会显示本次要发布插件的信息,并提示成功。一般在插件的正式发布前,建议先执行该命令,避免在上传过程中出现错误
  • 正式发布
发布至pub平台
flutter packages pub publish  

4.3 Flutter 插件开发(Package)

纯Dart 语言,封装的有特定功能的Widget组件,创建和发布流程和plugin插件类似


参考资料
Flutter中文网
Dart和Flutter应用程序的官方软件包库
Dart语言中文网

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容