二、Flutter实战

2021-10-26  本文已影响0人  清风徐来_362f

第四章Flutter实战

4.1 Fluter APP 代码结构

1.png
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。

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不被重新初始化

class _ChatPageState extends State<ChatPage>
    with AutomaticKeepAliveClientMixin
  @override
  bool get wantKeepAlive => true;
    super.build(context);
4.2.2 混合开发
//第一步: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);
      });
    }
  }

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

4.2.3 安装包结构

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


5.png

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

4.3 Flutter 插件开发(plugin )

- 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发起调用产生的对象,其中包含了方法名以及一个参数集合

flutter create --org com.plug.jcfc.cn --template=plugin --platforms=android,ios -i objc -a java flutter_plug

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 不但可以引用服务器上的插件,也可以引用本地路径下的插件。如此我们可以在插件未发布的情况下,直接在本地的测试工程里对插件进行测试

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语言中文网

上一篇 下一篇

猜你喜欢

热点阅读