Flutter 学习笔记

·  阅读 246

项目目录

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'),
          ),
        );
      }
    }
    复制代码
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改