Flutter 学习笔记

项目目录

flutter_app 
├── android       # 安卓目录 
├── build         # 构建目录 
├── ios           # iOS 目录 
├── lib           # 开发目录(相当于 src 目录) 
| ├── main.dart   # 入口文件(相当于 index.js) 
├── test          # 测试目录 
├── .gitignore    # Git 提交时,设置忽略文件内容 
├── pubspec.lock  # 项目依赖锁定信息(相当于 npm 中的 package-lock.json) 
└── pubspec.yaml  # 项目依赖配置(相当于 npm 中的 package.json)
复制代码

入口文件

Flutter 项目的入口文件是 lib/main.dart, 该文件中有一个入口方法。

入口方法

// 入口方法 
void main() { 
	// 具体内容 
}
复制代码

根函数

void main() {
	runApp(
		// 具体内容
	);
}
复制代码

runApp 函数接收一个组件,并使其成为组件树的根,框架会强制根组件覆盖整个屏幕。

UI库 Material

import 'package:flutter/material.dart';
复制代码

Mater 是一种标准的移动端和 Web 端的 UI 框架,是一套 Google 的设计规范,Flutter 项目以 Material 为 UI 基础。

Widget(组件)

Flutter 中的一切内容都是组件,在 Flutter 当作组件一般分为以下两类。

  • StatelessWidget

    无状态组件,状态不可改变的 Widget

  • StatefulWidget

    有状态组件,持有的状态,可能在 Widget 生命周期改变,如果我们想改变页面中的数据,就需要用到 StatefulWidget 。

自定义组件

为了增加代码的可读性,我们可以将部分代码分离出去,写成独立的 Widget。我们自定义的 Widget 需要继承 Flutter 提供的组件,继承的组件中有一个 build 方法,需要将我们实现的代码放到 build 方法中。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text(
          'Hello Flutter',
          textDirection: TextDirection.ltr,
        ),
      ),
    );
  }
}
复制代码

MaterialApp

字段 类型
navigatorKey(导航主键) GlobalKey
home(起始页) Widget
routes(路由列表) Map<String, WidgetBuilder>
initialRoute(初始路由名称) String
onGenerateRoute(生成路由) RouteFactory
onUnknownRoute(未知路由) RouteFactory
navigatorObservers(导航观察器) List
builder(构造器) TransitionBuilder
title(应用标题) String
onGenerateTitle(生成应用标题) GenerateAppTitle
color(颜色) Color
theme(主题配置) ThemeData
locale(本地化) Locale
localizationsDelegates(本地化委托代理) Iterable
localeResolutionCallback(本地化分辨回调) LocaleResolutionCallback
supportedLocales(应用支持区域) Iterable
debugShowMaterialGrid(是否显示 Material 网格) bool
showPerformanceOverlay(显示性能监控叠层) bool
checkerboardRasterCacheImages(棋盘格光栅缓存图像) bool
checkerboardOffscreenLayers(棋盘格层) bool
showSemanticsDebugger(显示语义调试器) bool
debugShowCheckedModeBanner(是否显示 DEBUG 横幅) bool

Scaffold

Scaffold 是 Flutter 应用的脚手架,用来搭建 Flutter 项目的基本布局结构。

  • appBar

    显示在界面顶部的一个 Appbar,也就是 Android 中的 ActionBar、Toolbar

  • body

    当前界面的主体 Widget

  • floatingActionButton

    纸墨设计中所定义的 FAB,界面的主要功能按钮

  • ……

App 结构

MyApp

常用文本组件

  • Container
    • child(声明子组件)
    • padding/margin
      • EdgeInsets.all()
      • EdgeInsets.fromLTRB()
      • EdgeInsets.only()
    • decoration
      • BoxDecoration(边框、圆角、渐变、阴影、背景色、背景图片)
    • alignment
      • Alignment (内容对齐)
    • transform
      • Matrix4(平移,旋转,缩放,斜切)
  • Column
    • Column 中的主轴方向是垂直布局
    • mainAxisAlignment:主轴对齐方式
    • crossAxisAlignment:交叉轴对齐方式
  • Row
    • Row 中的主轴方向是水平方向,其他属性和 Column 一致
  • Text(用来显示文本的组件,它是最常用的组件)
    • TextDirection(文本方向)
    • TextStyle(文本样式)
      • Colors(文本颜色)
      • FontWeight(字体粗细)
      • FontStyles(字体样式)
    • TextAlign(文本对齐)
    • TextOverflow(文本溢出)
    • maxLines(指定显示的行数)
  • RichText
    • 如果一段文字需要显示不同的样式,Text组件无法满足我们的需求,这个时候需要使用RichText
  • TextSpan
    • 类似 html 中的 span 标签,TextSpan 和 RichText 结合使用可以实现不同的样式布局。

第三方组件

dio

  • dio 是一个强大的 Dart Http 请求库(类似axios)

    pub.dev/packages/di…

  • 使用步骤

    • 在pubsepc.yaml 中添加 dio 依赖
    • 安装依赖 (pub get | flutter packages get | vs code 中保存配置,自动下载)
    • 引入 import ‘package:do/dio.dart’;
    • 使用:pub.dev/documentati…

Flutter_swiper

  • Flutter 中最好的轮播组件,适配 Android 和 iOS
  • 使用步骤
    • 在 pubsepc.yaml 中添加 flutter_swiper 依赖
    • 安装依赖 (pub get | flutter packages get | vs code 中保存配置,自动下载)
    • 引入 import ‘package:flutter_swiper/flutter_swiper.dart’;
    • 使用

生命周期

  • initState() 组件对象插入到元素树中时
  • didChangeDependencies() 当前状态对象的依赖改变时
  • build() 组件渲染时
  • setState() 组件对象的内部状态改变时候
  • didUpdateWidget() 组件配置更新时
  • deactivate() 组件对象在元素树中暂时移除时
  • dispose() 组件对象在元素树中永远移除时

路由与导航

路由简介

  • Route

    一个路由是一个屏幕或页面的抽象

  • Navigator

    • 管理路由的组件,Navigator 可以通过路由入栈和出栈来实现页面之间的跳转
    • 常用属性
      • initalRoute:初始化路由,既默认的页面
      • onGenerateRoter:根据规则匹配动态路由
      • onUnknownRoute:未知路由,也就是404
      • routes:路由集合

匿名路由

  • Navigator

    • push(跳转到指定组件)

      Navigator.push(
      	context,
      	MaterialPageRoute(
      		builder: (context) => Demo(),
      	),
      );
      复制代码
    • pop(回退)

      Navigator.pop(context);
      复制代码

命名路由

  • 声明路由

    • routes 路由表(map类型)
    • initalRoute(初始路由)
    • onUnknownRoute(未知路由)
  • 跳转到命名路由

    Navigator.pushNamed(context,'路由名称');
    复制代码
  • 配置如下

    return MaterialApp(
    	title: 'Flutter Demo',
    	routes: {
        'home':(context)=>Home(),
    		'demo': (context) => Demo(),
    	},
    	// initialRoute: 'home',
    	onUnknownRoute: (RouteSettings setting) => MaterialPageRoute(
    		builder: (context) => UnknownPage(),
    	),
    	home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
    复制代码

动态路由

  • 动态路由是指通过 onGenerateRoute 属性指定的路由,它的原理是通过传过来的路由进行动态的匹配。

    MaterialApp(
    	title: 'Flutter Demo',
    	onGenerateRoute: (RouteSettings setting) {
        print('当前路径:' + setting.name);
        
        if (setting.name == '/') {
          return MaterialPageRoute(builder: (context) => Home());
        }
        
        if (setting.name == 'demo') {
          return MaterialPageRoute(builder: (context) => Demo());
        }
    
        var uri = Uri.parse(setting.name);
        print(uri.pathSegments);
    
        if (uri.pathSegments.length == 2 && uri.pathSegments.first == 'demo') {
          var id = uri.pathSegments[1];
          return MaterialPageRoute(builder: (context) => Demo(id: id));
        }
        return MaterialPageRoute(builder: (context) => UnknownPage());
      }
    );
    复制代码

屏幕适配

  • 屏幕适配

    • 屏幕尺寸五花八门,要保证一个应用在不同的终端上表现一致
  • flutter_screenutil

  • 适配原理

    • 设计稿尺寸(例如:iphone6 的尺寸是 750*1334)
    • 终端尺寸(通过 window 或 MediaQuery.of(context) 获得)
    • 将设计稿尺寸,在终端上进行放大或缩小
  • 设计尺寸

    • designWidth:750px
    • designHeight:1334px
  • 终端尺寸(动态获取)

    • deviceWidth:1080px
    • deviceHeight:1920px
  • 缩放比例

    • scaleWidth = deviceWidth / designWidth
    • scaleHeight = deviceHeight / designHeight
  • 安装

    pub.dev/packages/fl…

  • 初始化设计尺寸

    ScreenUtilInit(designSize: Size(375, 667),...);
    复制代码
  • 设置适配尺寸

    • Flutter 1.2 之前
      • width: ScreenUtil().setWidth(100);
      • Height:ScreenUtil().setHeight(100);
    • Flutter 1.2 之后
      • width: 100.w
      • Height: 100.h

混合开发

嵌入原生 View

  1. Runner 目录下创建 iOS View,此 View 继承 FlutterPlatformView ,返回一个简单的 UILabel

    //
    //  MyFlutterView.swift
    //  Runner
    //
    //  Created by 悟空 on 2021/5/31.
    //
    
    import Foundation
    import Flutter
    
    class MyFlutterView: NSObject,FlutterPlatformView {
        
        let label = UILabel()
        
        init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) {
            super.init()
            if(args is NSDictionary){
                let dict = args as! NSDictionary
                label.text  = dict.value(forKey: "text") as! String
            }
        }
        
        func view() -> UIView {
            return label
        }
        
    }
    复制代码
  2. 创建 MyFlutterViewFactory

    //
    //  MyFlutterViewFactory.swift
    //  Runner
    //
    //  Created by 悟空 on 2021/5/31.
    //
    
    import Foundation
    import Flutter
    
    class MyFlutterViewFactory: NSObject,FlutterPlatformViewFactory {
        
        var messenger:FlutterBinaryMessenger
        
        init(messenger:FlutterBinaryMessenger) {
            self.messenger = messenger
            super.init()
        }
        
        func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
            return MyFlutterView(frame,viewID: viewId,args: args,messenger: messenger)
        }
        
        func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
            return FlutterStandardMessageCodec.sharedInstance()
        }
    }
    复制代码
  3. AppDelegate 中注册

    import UIKit
    import Flutter
    
    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        
        let registrar:FlutterPluginRegistrar = self.registrar(forPlugin: "plugins.flutter.io/custom_platform_view_plugin")!
        let factory = MyFlutterViewFactory(messenger: registrar.messenger())
        registrar.register(factory, withId: "plugins.flutter.io/custom_platform_view")
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }
    复制代码
  4. 在 Flutter/lib 目录下新建 PlatformViewDemo

    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class PlatformViewDemo extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        Widget platformView() {
          if (defaultTargetPlatform == TargetPlatform.iOS) {
            return UiKitView(
              viewType: 'plugins.flutter.io/custom_platform_view',
              creationParams: {'text': '我是 IOS 原生 View'},
              creationParamsCodec: StandardMessageCodec(),
            );
          }
          return null;
        }
    
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: platformView(),
          ),
        );
      }
    }
    复制代码

与原生通信

Flutter 与 Native 端通信一共有以下三种方式:

  • MethodChannel:Flutter 与 Native 端相互调用,调用后可以返回结果,可以 Native 端主动调用,也可以Flutter主动调用,属于双向通信。此方式为最常用的方式, Native 端调用需要在主线程中执行。
  • BasicMessageChannel:用于使用指定的编解码器对消息进行编码和解码,属于双向通信,可以 Native 端主动调用,也可以Flutter主动调用。
  • EventChannel:用于数据流(event streams)的通信, Native 端主动发送数据给 Flutter,通常用于状态的监听,比如网络变化、传感器数据等。

MethodChannel

Flutter 端创建 MethodChannel 通道,用于与原生端通信:

// com.flutter.guide.MethodChannel 是 MethodChannel 的名称,原生端要与之对应。
var channel = MethodChannel('com.flutter.guide.MethodChannel');
复制代码

发送消息:

var result = await channel.invokeMethod('sendData',{'name': 'xiazanzhang', 'age': 18})
复制代码
  • 第一个参数表示method,方法名称,原生端会解析此参数。
  • 第二个参数表示参数,类型任意,多个参数通常使用Map
  • 返回 Future,原生端返回的数据。

使用如下:

  1. Runner 目录下创建 MethodChannelDemo 类,内容如下

    //
    //  MethodChannelDemo.swift
    //  Runner
    //
    //  Created by 悟空 on 2021/5/31.
    //
    
    import Flutter
    import UIKit
    
    public class MethodChannelDemo {
        var count =  0
        var channel:FlutterMethodChannel
        init(messenger: FlutterBinaryMessenger) {
            channel = FlutterMethodChannel(name: "com.flutter.guide.MethodChannel", binaryMessenger: messenger)
            channel.setMethodCallHandler { (call:FlutterMethodCall, result:@escaping FlutterResult) in
                if (call.method == "sendData") {
                    if let dict = call.arguments as? Dictionary<String, Any> {
                        let name:String = dict["name"] as? String ?? ""
                        let age:Int = dict["age"] as? Int ?? -1
                        result(["name":"hello,\(name)","age":age])
                    }
                }
            }
            startTimer()
        }
        
        func startTimer() {
            var timer = Timer.scheduledTimer(timeInterval:1, target: self, selector:#selector(self.tickDown),userInfo:nil,repeats: true)
        }
        @objc func tickDown(){
            count += 1
            var args = ["count":count]
            channel.invokeMethod("timer", arguments:args)
        }
    }
    复制代码
  2. 修改 App Delegate

    import UIKit
    import Flutter
    
    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        
        // 嵌入原生 view
        let registrar:FlutterPluginRegistrar = self.registrar(forPlugin: "plugins.flutter.io/custom_platform_view_plugin")!
        
        // Flutter 向 iOS View 发送消息
        let factory = MyFlutterViewFactory(messenger: registrar.messenger())
        registrar.register(factory, withId: "plugins.flutter.io/custom_platform_view")
        
        // MethodChannel:与原生通信
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        MethodChannelDemo(messenger: controller.binaryMessenger)
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }
    复制代码
  3. Flutter 项目中新建 MethodChannelDemo

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class MethodChannelDemo extends StatefulWidget {
      @override
      _MethodChannelDemoState createState() => _MethodChannelDemoState();
    }
    
    class _MethodChannelDemoState extends State<MethodChannelDemo> {
      var channel = MethodChannel('com.flutter.guide.MethodChannel');
      var _data;
      var _count;
    
      @override
      void initState() {
        super.initState();
        channel.setMethodCallHandler((call) {
          setState(() {
            _count = call.arguments['count'];
          });
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Column(
            children: [
              SizedBox(
                height: 50,
              ),
              ElevatedButton(
                child: Text('发送数据到原生'),
                onPressed: () async {
                  var result = await channel
                      .invokeMethod('sendData', {'name': 'xiazanzhang', 'age': 18});
                  var name = result['name'];
                  var age = result['age'];
                  setState(() {
                    _data = '$name,$age';
                  });
                },
              ),
              Text('原生返回数据:$_data'),
              Text('接收原生主动发送数据:$_count'),
            ],
          ),
        );
      }
    }
    复制代码

BasicMessageChannel

Flutter 端创建 MethodChannel 通道,用于与原生端通信:

// com.flutter.guide.BasicMessageChannel 是 BasicMessageChannel 的名称,原生端要与之对应。
var channel = BasicMessageChannel('com.flutter.guide.BasicMessageChannel',StandardMessageCodec());
复制代码

发送消息:

var result = await channel.send({'name': 'xiazanzhang', 'age': 18});
复制代码
  • 参数类型任意,多个参数通常使用Map
  • 返回 Future,原生端返回的数据。

使用如下:

  1. Runner 目录下创建 MethodChannelDemo 类,内容如下

    //
    //  BasicMessageChannelDemo.swift
    //  Runner
    //
    //  Created by 悟空 on 2021/5/31.
    //
    
    import Flutter
    import UIKit
    
    public class BasicMessageChannelDemo {
        
        var channel:FlutterBasicMessageChannel
        
        init(messenger: FlutterBinaryMessenger) {
            channel = FlutterBasicMessageChannel(name: "com.flutter.guide.BasicMessageChannel", binaryMessenger: messenger)
            channel.setMessageHandler { (message, reply) in
                if let dict = message as? Dictionary<String, Any> {
                    let name:String = dict["name"] as? String ?? ""
                    let age:Int = dict["age"] as? Int ?? -1
                    reply(["name":"hello,\(name)","age":age])
                }
            }
        }
    }
    复制代码
  2. 修改 AppDelegate 类,在 application 方法中添加如下代码

      // BasicMessageChannel: 与 Flutter 通信
      BasicMessageChannelDemo(messenger: controller.binaryMessenger)
    复制代码
  3. Flutter 项目中新建 BasicMessageChannelDemo

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class BasicMessageChannelDemo extends StatefulWidget {
      @override
      _BasicMessageChannelDemoState createState() =>
          _BasicMessageChannelDemoState();
    }
    
    class _BasicMessageChannelDemoState extends State<BasicMessageChannelDemo> {
      var channel = BasicMessageChannel(
          'com.flutter.guide.BasicMessageChannel', StandardMessageCodec());
    
      var _data;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Column(
            children: [
              SizedBox(
                height: 50,
              ),
              ElevatedButton(
                child: Text('发送数据到原生'),
                onPressed: () async {
                  var data = {'name': 'xiazanzhang', 'age': 18};
                  var result = await channel.send(data) as Map;
                  var name = result['name'];
                  var age = result['age'];
                  setState(() {
                    _data = '$name,$age';
                  });
                },
              ),
              Text('原生返回数据:$_data'),
            ],
          ),
        );
      }
    }
    复制代码

EventChannel

Flutter 端创建 EventChannel 通道,用于与原生端通信:

// com.flutter.guide.EventChannel 是 EventChannel 的名称,原生端要与之对应。
var _eventChannel = EventChannel('com.flutter.guide.EventChannel');
复制代码

监听原生端发送的消息:

var _data;
  @override
  void initState() {
    super.initState();
    _eventChannel.receiveBroadcastStream().listen(_onData);
  }

  _onData(event){
    setState(() {
      _data = event;
    });
  }
复制代码

使用如下:

  1. Runner 目录下创建 MethodChannelDemo 类,内容如下

    //
    //  EventChannelDemo.swift
    //  Runner
    //
    //  Created by 悟空 on 2021/5/31.
    //
    
    import Flutter
    import UIKit
    
    public class EventChannelDemo:NSObject, FlutterStreamHandler{
        
        var channel:FlutterEventChannel?
        var count =  0
        var events:FlutterEventSink?
        
        public override init() {
            super.init()
        }
        
        convenience init(messenger: FlutterBinaryMessenger) {
            
            self.init()
            
            channel = FlutterEventChannel(name: "com.flutter.guide.EventChannel", binaryMessenger: messenger)
            channel?.setStreamHandler(self)
            startTimer()
        }
        
        func startTimer() {
            let timer = Timer.scheduledTimer(timeInterval:1, target: self, selector:#selector(self.tickDown),userInfo:nil,repeats: true)
        }
        @objc func tickDown(){
            count += 1
            let args = ["count":count]
            if(events != nil){
                events!(args)
            }
        }
        
        public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
            self.events = events
            return nil;
        }
        
        public func onCancel(withArguments arguments: Any?) -> FlutterError? {
            self.events = nil
            return nil;
        }
    
    }
    复制代码
  2. 修改 AppDelegate 类,在 application 方法中添加如下代码

    // EventChannel: 与 Flutter 通信
    EventChannelDemo(messenger: controller.binaryMessenger)
    复制代码
  3. Flutter 项目中新建 BasicMessageChannelDemo

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class EventChannelDemo extends StatefulWidget {
      @override
      _EventChannelDemoState createState() => _EventChannelDemoState();
    }
    
    class _EventChannelDemoState extends State<EventChannelDemo> {
      var _eventChannel = EventChannel('com.flutter.guide.EventChannel');
      var _data;
      @override
      void initState() {
        super.initState();
        _eventChannel.receiveBroadcastStream().listen(_onData);
      }
    
      _onData(event) {
        setState(() {
          _data = event;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: Text('监听原生返回的数据:$_data'),
          ),
        );
      }
    }
    复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享