Flutter入门学习

370 阅读4分钟

前言

Flutter是Google开源的应用开发框架,仅通过一套代码,就能构建精美的、原生平台编译的多平台应用。在进入学习之前,我们需要先安装好运行环境,学习基本的Dart语法,了解Flutter项目的基本目录结构

入门

  • 环境安装

  • 项目运行

  • 基础知识

学习方法

官方资料、视频教程、开源项目、参与技术分享及讨论

学习资料&传送门

名称链接相关说明
Flutter问答flutterchina.club/faq/了解Flutter
Dart官网dart.dev/了解Dart
Dart开发者官网api.dart.dev/stable/2.15…学习Dart
Flutter官网flutter.cn/docs了解Flutter开发
Flutter官方GitHub地址github.com/flutter学习Flutter开发
Flutter实用教程flutter.cn/docs/cookbo…学习Flutter开发
Flutter CodeLabcodelabs.flutter-io.cn/学习Flutter开发
Flutter中文网flutterchina.club/get-started…了解Flutter及学习更多知识
Flutter-SDKapi.flutter-io.cn/Flutter API 参考文档
Flutter安装环境flutter.cn/docs/get-st…获取Flutter-SDK,开发Flutter项目
AndroidStudiodeveloper.android.com/studio一款Android&Flutter的开发工具
pub.devpub.dev/flutter/pac…官方的插件平台,可用于上传自定义组件库,也可以用于学习或使用第三方组件库
Flutter实战.第二版book.flutterchina.club/学习资料
awesome-fluttergithub.com/Solido/awes…学习资料
awesome-flutter/www.devio.org/2018/09/09/…学习资料
Flutter老孟控件大全laomengit.com/flutter/wid…学习资料
flutter-gogithub.com/alibaba/flu…学习资料
咸鱼技术www.yuque.com/xytech/flut…学习资料
链家技术github.com/LianjiaTech…学习资料
flutter_boostgithub.com/alibaba/flu…混编插件
FlutterDouBangithub.com/kaina404/Fl…学习项目

Flutter基础

目录结构

根据以下Flutter项目为例,展开项目的目录结构说说:

Xnip2022-12-02_10-26-05.jpg

第一个应用程序

打开AndroidStudio>Create New Flutter Project>Flutter Application

创建项目完成之后,我们会发现在lib下面有一个main.dart文件,它是dart的执行程序入口,下面我们来看下项目创建的这段默认的代码:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

通过以上代码,我们会发现它依次运行

main()> runApp()>MyApp()>build()>MyHomePage>()>_MyHomePageState()>build()

其中MyApp继承了StatelessWidget类,

MyHomePage继承了StatefulWidget类,

_MyHomePageState继承了State,

_MyHomePageState类的build函数返回值是一个Widget对象:Scaffold(),而我们看到的页面正是这个Weidet

其中我们会发现,在每个Widget的build方法中都会有一个参数BuildContext参数,BuildContxt是用来传递各个Widget在Widget Tree中的具体位置,Flutter就是根据context包含的信息,来绘制各个Widget及其相对位置

生命周期

Flutter跟iOS和Android一样都拥有自己的生命周期,生命周期就是从创建到销毁的一个过程。在Flutter中一切都是Widget,所以它的生命周期分为两部分,一个是App的生命周期,另外一个就是Widget的生命周期。

App的生命周期

我们可以通过with WidgetsBindingObserver来监听App的生命周期,然后实现didChangeAppLifecycleState(AppLifecycleState state)回调函数,利用AppLifecycleState的枚举值来判断app的生命周期的状态。

程序入口:main函数只会执行一次,但是在退出应用程序之后会再次执行

void main() {
  // 例如:在点击home键后重新进入app时不会被执行
  // 在杀死app,或者点击返回键退出到桌面,或者执行SystemNavigator.pop()等情况后会再次执行
  runApp(MyApp());
}

我们可以通过AppLifecycleState知道App的整个生命周期

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  var _tag = 'TagAppLife   ';

  @override
  void initState() {
    super.initState();
    print(_tag + 'initState......');
    // 添加生命周期监听
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    super.dispose();
    print(_tag + 'dispose......');
    // 移除生命周期监听
    WidgetsBinding.instance.removeObserver(this);
  }

  /// 初次进入widget时,不执行AppLifecycleState的回调
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    switch (state) {
      case AppLifecycleState.resumed:
        // 应用程序可见(从后台重新切换到前台时候被调用)
        print(_tag + 'AppLifecycleState resumed');
        break;
      case AppLifecycleState.paused:
        // 应用程序不可见(只要应用程式为不可见时都被会调用)
        print(_tag + 'AppLifecycleState paused');
        break;
      case AppLifecycleState.inactive:
        // 切换到后台时,随后pause也会被回调
        print(_tag + 'AppLifecycleState inactive');
        break;
      case AppLifecycleState.detached:
        // 应用程序仍然驻留在Flutter引擎上,在没有视图的情况下运行
        print(_tag + 'AppLifecycleState detached');
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}

Widget的生命周期

在Flutter中一切都是Widget,其中StatelessWidget和StatefulWidget是 flutter的基础组件。StatelessWidget是一个无状态的组件,用于那些始终不变的UI控件,通常自定义控件就会选择继承它;StatefulWidget是一个有状态的组件,可以通过改变状态使得 UI 发生变化,可以包含用户交互。

StatelessWidget的生命周期
StatelessWidget不需要维护状态,它通常在build方法中通过嵌套其它Widget来构建UI,对于StatelessWidget来说,生命周期只有他自己的构造函数和build函数。(先执行构造函数,后执行build函数)

class MyApp extends StatelessWidget {
  // This widget is the root of your application.

  MyApp() {
    print('TagAppLife   ' + '构造函数......');
  }

  @override
  Widget build(BuildContext context) {
    print('TagAppLife   ' + 'build函数......');
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

StatefulWidget的生命周期
StatefulWidget拥有自己的状态,它是由Widget和State组成的,通过主动调用setState()函数可以通知框架内部发生改变,接着build函数就会被执行,重新构建widget树,从而达到界面刷新的目的。

  • createState: 在StatefulWidget创建State状态,只执行一次

  • reassemble: 点击闪电进行热重载时执行,仅用于debug调试,release版本不会执行该函数

  • initState: 该函数是StatefulWidget在被创建后调用的第一个方法,而且只执行一次,一般用于初始化操作

  • didChangeDependencies: 在initState回调函数执行之后立即调用,之后只有当StatefulWidget依赖的InheritedWidget发生变化之后,该函数才会被调用

  • build:主要是用于构建Widget子树,绘制界面,当主动调用setState方法后会被再次执行

  • didUpdateWidget: 重写此方法以在 [widget] 更改时做出响应

  • deactivate: 框架在移除这个 [State] 对象时调用这个方法,例如退出界面

  • dispose:组件被销毁,当这个对象从树中永久移除时调用,例如退出界面

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title});

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  var _tag = 'TagAppLife   ';

  _MyHomePageState() {
    print(_tag + '构造函数......');
  }

  @override
  void reassemble() {
    super.reassemble();
    // 点击闪电进行热重载时执行,仅用于debug调试,release版本不会执行该函数
    // debug热重载时,initState函数不会被调用,可以通过此函数代替初始化
    print(_tag + 'reassemble......');
  }

  @override
  void initState() {
    super.initState();
    // 该函数是StatefulWidget在被创建后调用的第一个方法,而且只执行一次,一般用于初始化操作。
    // 在执行该函数时,StatefulWidget的[mounted]值会变为true,
    // 调用[dispose]之后框架将永远不会询问[State],StatefulWidget的mounted的值会变为false,
    // 注意:除非[mounted]为真,否则直接调用[setState]是会报错的
    print(_tag + 'initState......');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 在initState回调函数执行之后立即调用,之后只有当StatefulWidget依赖的InheritedWidget发生变化之后,该函数才会被调用
    // InheritedWidget可以实现局部的子widget更新
    print(_tag + 'didChangeDependencies......');
  }

  @override
  Widget build(BuildContext context) {
    // 主要是用于构建Widget子树,绘制界面,当主动调用setState方法后会被再次执行
    print(_tag + 'build......');
    return Scaffold();
  }

  @override
  void didUpdateWidget(covariant MyHomePage oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 重写此方法以在 [widget] 更改时做出响应
    // 框架总是在调用[didUpdateWidget]后调用[build],即意味着在 [didUpdateWidget] 中对 [setState] 的任何调用都是多余的。
    print(_tag + 'didUpdateWidget......');
  }

  @override
  void deactivate() {
    super.deactivate();
    // 框架在移除这个 [State] 对象时调用这个方法,例如退出界面,调用了 Navigator.pop(context);
    print(_tag + 'deactivate......');
  }

  @override
  void dispose() {
    super.dispose();
    // 组件被销毁,当这个对象从树中永久移除时调用,例如退出界面,调用了 Navigator.pop(context);
    print(_tag + 'dispose......');
  }
}

基础知识

dart是由Google开发的计算机编程语言,它可以被用于web/服务器/移动应用/物联网等领取的开发。要学Flutter开发必须首先会dart。

变量规则

dart是一个强大的脚本语言,可以不预先定义变量类型,自动会类型推倒,定义变量可以通过var关键字声明变量,也可以通过具体类型来声明变量

例如:
var str = ' hello world';
String str = 'hello world';

dart中常量可以通过关键字const和final进行修饰,如何理解const和final之间的差别:

  • final、const必须初始化

  • final、const只能赋值一次

  • final修饰的List集合任意索引可以修改,const修饰的不可以修改

  • final可以开始不赋值,但是只能赋值一次,const一开始就得赋值

  • final可修饰实例变量、const不可以修饰实例变量

  • final不仅有const的编译时常量的特性,最重要的是它是运行时常量

  • final是惰性初始化,即在运行时第一次使用前才初始化

  • 访问类中const修饰的变量需要static修饰

数据类型

dart支持的数据类型:int/double/string/bool/list/map ,int和double都是num类型的子类

运算表达式

算术运算符: +(加)   -(减)   *(乘)   /(除)   ~/(取整)   %(取余)
关系运算符: ==(等于)   !=(不等于)   >(大于)  <(小于)  >=(大于等于)  <=(小于等于)
逻辑运算符: !(非,取反)   &&(并且)   ||(或者)
基础赋值运算符:=(直接赋值)  ??=(如果为空才赋值)
复合赋值运算符:+=  -=  *=  /=  %=  ~/=

方法函数

Dart 没有 「public」「private」等关键字,默认就是public的,private请使用下划线 _开头。

  /// 方法的作用域
  void _test() {
    // 方法里面可以再写方法
    method1() {
      print("111111");
      method1_1() {
        print("111111_111111");
      }

      // 方法里面可以再写方法
      method1_1();
    }

    /// 可选参数,没有默认值。 {}代表需要命名参数
    method2(String str, {bool flag}) {
      print("222222");
    }

    method1();
    method2("222222", flag: true);
    method3("333333", true);
    // 把方法当作参数传入
    method4(method5);
  }

  /// 可选参数,并且附默认值。 []代表无需命名参数
  method3(String str, [bool flag = false]) {
    if (flag) {
      method3_1(flag);
    } else {
      print("333333");
    }
  }

  /// =>代表返回
  var method3_1 = (bool flag) => print("333333_" + flag.toString());

  method5() => print("555555");

  method4(m()) {
    print("444444");
    m();
  }

面向对象

Dart所有东西都是对象,所有的对象都继承object类。dart是一门使用类和单继承的面向对象语言,所有的对象都是类的实例,并且所有类都是obejct的子类。

class Person{

  String _name = "";
  int _age = 0;

  /// 如果你定义了一个类,而没有定义构造函数,那么它将有一个默认的构造函数,这个构造函数没有参数
  Person(this._name);

  /// 命名构造函数,使用命名构造函数可以为类提供多个构造函数
  /// 但是命名构造函数不可继承,如果子类想要有和父类一样的命名构造函数,那就写个同名的,通常也会在子类的命名构造函数里,调用父类的同名命名构造函数
  Person.test1(String name,int age){
    this._name = name;
    this._age = age;
  }

  /// 命名构造函数(Dart的语法糖)
  Person.test2(this._name, this._age);

  int get getAge => _age;

  set setAge(int value) {
    _age = value;
  }

  String get getName => _name;

  set setName(String value) {
    _name = value;
  }

  @override
  String toString() {
    return 'Person{_name: $_name, _age: $_age}';
  }

}

常用命令行

flutter获取Flutter中所有的命令行帮助
flutter doctor展示安装工具及环境配置等信息
flutter --version查看flutter、dart版本信息
flutter upgrade升级 Flutter SDK
flutter run运行flutter应用程序
flutter run --release运行flutter应用程序(正式包)
flutter packages get获取项目中的依赖包(不包含flutter sdk)
flutter packages upgrade更新项目中的依赖包(不包含flutter sdk)
flutter packages outdated显示项目中过时的依赖包(不包含flutter sdk)
flutter logs查看应用程序运行日志
flutter pub deps查看项目依赖树
flutter analyze对项目进行静态代码检查,也是检验项目规范的一个标准
flutter test对当前项目进行单元测试
flutter build apk打包安卓项目
flutter build ios打包iOS项目
flutter build web构建应用程序以进行部署
flutter format .格式化代码并自动移除未使用的导入包
flutter build appbundle构建一个发布的 app bundle
flutter build ipa构建一个发布的 ipa
flutter build ipa --dart-define=xxx=true自定义环境构建一个发布的 ipa

DevTools介绍

DevTools 是Flutter官方推出的一套适用于 Dart 和 Flutter 的性能和调试工具。了解更多

总结

万丈高楼平地起,一砖一瓦皆根基