Flutter开发技术与分享(二) —— Flutter 入门(一)

版本记录

版本号 时间
V1.0 2019.11.03 星期日

前言

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。目前公司的部分模块就是在使用Flutter进行开发。感兴趣的可以看下面几篇文章。
1. Flutter开发技术与分享(一) —— 基本概览(一)

开始

首先看下主要内容

通过使用VS Code编写跨平台应用程序,深入研究Flutter框架,以在单个代码库中构建iOSAndroid应用程序。下面是翻译文章的地址

然后看下写作环境

写作环境:Dart 2, Flutter 1.7, VS Code

自十年前iOSAndroid平台风起云涌以来,跨平台开发一直是整个移动开发领域的目标。 能够为iOS和Android编写一个应用程序的功能可以为您的公司和团队节省大量时间和精力。

多年来,已经发布了用于跨平台开发的各种工具,包括基于Web的工具(例如AdobePhoneGap),强大的框架(例如MicrosoftXamarin)以及更新的工具(例如FacebookReact Native)。 每个工具集都有其优缺点,并且在移动行业中获得了不同程度的成功。

进入跨平台领域的最新框架是GoogleFlutter。 Flutter在两个平台上均具有快速的开发周期,快速的UI呈现,独特的UI设计以及本机应用程序性能。


Introduction to Flutter

Flutter应用程序是使用Dart编程语言编写的,该语言最初也来自Google,现在是ECMA标准。 Dart与其他现代语言(例如KotlinSwift)具有许多相同的功能,并且可以转编译为JavaScript代码。

作为跨平台框架,Flutter最类似于React Native,因为Flutter允许响应式和声明式编程风格。但是,与React Native不同,Flutter不需要使用Javascript桥接,这可以缩短应用程序的启动时间和整体性能。 Dart通过使用Ahead-Of-TimeAOT编译来实现此目的。

Dart的另一个独特之处在于它还可以使用Just-In-Time or JIT编译。 FlutterJIT编译通过允许热重装(hot reload)功能在开发过程中刷新UI而无需全新的构建,从而改善了开发工作流程。

正如您将在本教程中看到的那样,Flutter框架主要围绕widgets的概念构建。在Flutter中,widgets不仅用于应用程序的视图,而且还用于整个屏幕甚至应用程序本身。

除了跨平台的iOS和Android开发之外,学习Flutter还可以让您抢先开发Fuchsia平台,Fuchsia平台目前是Google开发的实验性操作系统。

在本教程中,您将构建一个Flutter应用程序,该应用程序将查询GitHub API中的GitHub组织中的团队成员,并在可滚动列表中显示团队成员信息:

您可以同时使用iOS模拟器或Android模拟器来开发应用程序!

在构建应用程序时,您将了解有关Flutter的以下知识:

  • Setting up your development environment
  • Creating a new project
  • Hot reload
  • Importing files and packages
  • Using widgets and creating your own
  • Making network calls
  • Showing items in a list
  • Adding an app theme

顺带着你也会学习一点关于Dart的知识。


Setting up your development environment

Flutter开发可以在macOSLinuxWindows上完成。 尽管您可以将任何编辑器与Flutter工具链一起使用,但是有 IntelliJ IDEAAndroid StudioVisual Studio CodeIDE插件可以简化开发周期。 在本教程中,我们将使用VS Code

在此处here可以找到有关使用Flutter框架设置开发机器的说明。基本步骤因平台而异,但大多数情况是:

  • 1) 下载适用于您开发计算机操作系统的安装包,以获取Flutter SDK的最新稳定版本
  • 2) 将安装包解压缩到所需位置
  • 3) 将flutter工具添加到您的路径
  • 4) 运行flutter doctor命令,该命令将安装Flutter框架(包括Dart)并提醒您任何缺少的依赖项
  • 5) 安装缺少的依赖项
  • 6) 使用Flutter插件/扩展程序设置您的IDE
  • 7) 测试驱动一个应用

Flutter网站上提供的说明做得很好,可以让您轻松地在所选平台上设置开发环境。本教程的其余部分假定您已经为Flutter开发设置了VS Code,并且已经解决了flutter doctor发现的所有问题。

如果您使用的是Android Studio,那么您也应该能够很好地遵循。您还需要运行iOS模拟器,Android模拟器,或者已设置预配置的iOS设备或Android设备进行开发。

注意:要在iOS模拟器或iOS设备上进行构建和测试,您需要使用已安装Xcode的macOS


Creating a new project

在安装了Flutter扩展的VS Code中,通过选择View ► Command Palette…或在macOS上单击Cmd-Shift-P或在Linux或Windows上单击Ctrl-Shift-P来打开命令面板。 在面板中输入Flutter:New Project,然后按回车键。

输入项目的名称ghflutter,然后按回车键。 选择一个文件夹来存储项目,然后等待Flutter在VS Code中设置项目。 项目准备就绪后,将在编辑器中打开文件main.dart

VS Code中,您会在左侧看到一个面板,该面板显示您的项目结构。 有适用于iOS和Android的文件夹,还有一个包含main.dartlib文件夹,并且具有适用于两个平台的代码。 仅在本教程中,您将在lib文件夹中工作。

用以下内容替换main.dart中的代码:

import 'package:flutter/material.dart';

void main() => runApp(GHFlutterApp());


class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'GHFlutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('GHFlutter'),
        ),
        body: Center(
          child: Text('GHFlutter'),
        ),
      ),
    );
  }
}

顶部附近的main()函数对单个行函数使用=>运算符来运行该应用程序。 您有一个名为GHFlutterApp的应用程序类。

您在这里看到您的应用程序本身是一个StatelessWidget。 Flutter应用程序中的大多数实体都是无状态或有状态的widgets。 您可以覆盖widgetsbuild()方法来创建应用widgets。 您正在使用MaterialApp widgets,该widgets提供了Material Design之后的应用所需的许多组件。

对于本入门教程,通过右键单击,选择Delete选项,然后确认删除,从项目中删除test文件夹中的测试文件widget_test.dart

如果您使用的是macOS,请启动iOS模拟器。 您也可以在macOS,Linux或Windows上使用Android模拟器。 如果iOS模拟器和Android模拟器都在运行,则可以使用VS Code窗口右下方的菜单在它们之间进行切换:

要构建和运行项目,您需要首先设置启动配置。

通过单击左侧面板上的crossed bug图标,切换到Debug View

您会注意到,到目前为止,尚未定义任何配置。 单击No Configuration以获取下拉列表并选择Add Configuration

VS Code将创建一个launch.json文件,其详细信息如下:

注意:选择Add Configuration项后,将自动为您生成此文件。 在本教程中,您无需修改它。

现在,您已经完成所有工作,可以通过按F5或选择Debug ► Start Debugging或单击绿色的播放图标来构建和运行项目。 您会看到Debug Console已打开,并且如果在iOS上运行,则会看到用于构建项目的Xcode。 如果在Android上运行,则会看到Gradle被调用以进行构建。

这是在iOS模拟器中运行的应用程序:

下面,在Android模拟器中运行:

您看到的慢速模式banner表明该应用程序正在调试模式下运行。

您可以通过单击VS Code窗口顶部工具栏右侧的停止按钮来停止正在运行的应用程序:

通过单击VS Code左上方的图标或选择View ► Explorer,可以返回项目视图。


Hot Reload

Flutter开发的最佳方面之一是能够在进行更改时热重新加载您的应用程序。 这类似于Android StudioInstant Run/Apply Changes

构建并运行该应用程序,使其在模拟器或模拟器上运行:

现在,无需停止正在运行的应用程序,请将应用程序栏字符串更改为其他内容:

appBar: AppBar(
  title: Text('GHFlutter App'),
),

现在,单击工具栏上的热重载按钮或直接保存main.dart文件:

一两秒钟之内,您应该会看到正在运行的应用程序中反映出的更改:

热重载功能可能并不总是有效,官方文档official docs可以很好地解释无法使用的情况,但总体而言,在构建UI时可节省大量时间。


Importing a File

您将希望能够从创建的其他类中导入代码,而不是将所有Dart代码都保存在单个main.dart文件中。 现在,您将看到一个导入字符串的示例,该示例在需要本地化面向用户的字符串时会有所帮助。

右键单击lib并选择New File,在lib文件夹中创建一个名为strings.dart的文件:

将以下类添加到新文件中:

class Strings {
  static String appTitle = "GHFlutter";
}

将以下import添加到main.dart的顶部

import 'strings.dart';

更改widget以使用新的字符串类,以便GHFlutterApp类如下所示:

class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appTitle,
      home: Scaffold(
        appBar: AppBar(
          title: Text(Strings.appTitle),
        ),
        body: Center(
          child: Text(Strings.appTitle),
        ),
      ),
    );
  }
}

按下F5键即可构建并运行该应用,您应该不会有任何变化,但是现在您正在使用字符串文件中的字符串。


Widgets

Flutter应用程序中的几乎每个元素都是widgetwidget被设计为不可变的,因为使用不可变的widget有助于使应用程序UI保持轻便。

您将使用两种基本类型的小部件:

  • Stateless - 无状态:仅依赖于自己的配置信息的widget,例如图像视图中的静态图像。
  • Stateful - 有状态:需要维护动态信息并通过与State对象进行交互来实现的信息。

无状态小部件和有状态widget都在Flutter应用程序中的每帧上重新绘制,不同之处在于,有状态widget将其配置委托给State对象。

要开始制作自己的widget,请在main.dart底部创建一个新类:

class GHFlutter extends StatefulWidget {
  @override
  createState() => GHFlutterState();
}

您已经创建了StatefulWidget子类,并且您将覆盖createState()方法以创建其状态对象。 现在,在GHFlutter上方添加GHFlutterState类:

class GHFlutterState extends State<GHFlutter> {
}

GHFlutterState使用GHFlutter的参数扩展State

制作widget时的主要任务是覆盖将widget呈现到屏幕时调用的build()方法。

GHFlutterState中添加一个build()重写:

@override
Widget build(BuildContext context) {
​    
}

填写build()如下:

@override
Widget build(BuildContext context) {
  return Scaffold (
    appBar: AppBar(
      title: Text(Strings.appTitle),
    ),
    body: Text(Strings.appTitle),
  );
}

Scaffold是用于材料设计widgets的容器。 它充当widgets层次结构的根。 您已在Scaffold中添加了一个AppBar和一个body,每个都包含一个Text widget

更新GHFlutterApp,使其使用新的GHFlutter小部件作为其home属性,而不是构建自己的支架:

class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appTitle,
      home: GHFlutter(),
    );
  }
}

构建并运行该应用程序,您将看到新的widget在起作用:

尚未发生太大变化,但是现在您可以设置以构建新的widget


Making Network Calls

之前,您已将strings.dart文件导入到项目中。 您可以类似地导入Flutter框架和Dart中包含的其他软件包。

例如,您现在将使用框架中可用的包进行HTTP网络调用,并将生成的响应JSON解析为Dart对象。 在main.dart顶部添加两个新导入:

import 'dart:convert';
import 'package:http/http.dart' as http;

您会注意到http包不可用。 这是因为尚未将其添加到项目中。 导航到pubspec.yaml文件,然后在dependenciescupertino_icons:^ 0.1.2下添加以下内容:

  cupertino_icons: ^0.1.2

  # HTTP package
  http: ^0.12.0+2

注意:注意缩进。 保持 http 软件包声明的缩进与 cupertino_icons软件包的缩进相同。

现在,当您保存pubspec.yaml文件时,VS Code中的Flutter扩展名将运行flutter pub get命令。 Flutter将获得声明的http软件包,您的软件包也将在main.dart中可用。

现在,您将在main.dart中看到有关当前未使用的导入的指示器。

Dart应用程序是单线程的,但是Dart提供了对在其他线程上运行代码以及运行异步代码的支持,这些异步代码不会使用async / await模式阻止UI线程。

您将进行异步网络调用以检索GitHub团队成员的列表。 在GHFlutterState的顶部添加一个空列表作为属性,还添加一个属性以容纳文本样式:

var _members = [];

final _biggerFont = const TextStyle(fontSize: 18.0);

名称开头的下划线使该类的成员成为私有成员。

要进行异步HTTP调用,请向GHFlutterState添加方法_loadData()

_loadData() async {
  String dataURL = "https://api.github.com/orgs/raywenderlich/members";
  http.Response response = await http.get(dataURL);
  setState(() {
    _members = json.decode(response.body);
  });
}

您已经在_loadData()上添加了async关键字,以告知Dart它是异步的,并且还在http.get()调用上阻塞了await关键字。 您使用的dataUrl值设置为GitHub API端点,该端点检索GitHub组织的成员。

HTTP调用完成后,您将向回调传递给setState(),该回调在UI线程上同步运行。 在这种情况下,您将解码JSON响应并将其分配给_members列表。

initState()重写添加到GHFlutterState,该状态在初始化状态时调用_loadData()

@override
void initState() {
  super.initState();

  _loadData();
}

Using a ListView

现在您已经有了Dart成员列表,您需要一种在UI列表中显示它们的方法。 Dart提供了一个ListView widget,可让您在列表中显示数据。 ListView的行为类似于Android上的RecyclerView和iOS上的UITableView,在用户滚动列表以实现平滑滚动性能时回收视图。

_buildRow()方法添加到GHFlutterState中:

Widget _buildRow(int i) {
  return ListTile(
    title: Text("${_members[i]["login"]}", style: _biggerFont)
  );
}

您将返回一个ListTile widget,该widget显示从ith成员的JSON解析的login值,并使用您之前创建的文本样式。

更新GHFlutterState的构建方法,使其主体为ListView.builder

body: ListView.builder(
  padding: const EdgeInsets.all(16.0),
  itemCount: _members.length,
  itemBuilder: (BuildContext context, int position) {
    return _buildRow(position);
  }),

你已经添加paddingitemCount设置为成员的数量,并使用_buildRow()为给定的位置设置itemBuilder

您可以尝试热重载,但可能会收到“Full restart may be required”消息。 如果是这样,请按F5键构建并运行该应用程序:

进行网络通话,解析数据并在列表中显示结果就是这么简单!


Adding dividers

要将分隔符添加到列表中,您需要将item数量加倍,然后在列表中的位置为奇数时返回Divider widget。 如下更新GHFlutterState的构建方法:

body: ListView.builder(
  itemCount: _members.length * 2,
  itemBuilder: (BuildContext context, int position) {
    if (position.isOdd) return Divider();

    final index = position ~/ 2;
    
    return _buildRow(index);
  }),

确保不要错过itemCount上的* 2。 有了分隔线后,您已经从构建器中删除了padding。 在itemBuilder中,您要么返回Divider(),要么通过整数除法并使用_buildRow()来构建行项目来计算新索引。

尝试热重载,您应该在列表上看到分隔线:

要将padding重新添加到每一行中,您想在_buildRow()中使用Padding widget

Widget _buildRow(int i) {
  return Padding(
    padding: const EdgeInsets.all(16.0),
    child: ListTile(
      title: Text("${_members[i]["login"]}", style: _biggerFont)
    )
  );
}

ListTile现在是padding widget的子widget。 热重新加载以查看行上的padding,而不是分隔线上的padding


Parsing to Custom Types

在上一节中,JSON解析器将JSON响应中的每个成员作为Dart Map类型添加到_members列表中,相当于Kotlin中的MapSwift中的Dictionary

但是,您还希望能够使用自定义类型。

main.dart文件中添加一个新的Member类型:

class Member {
  final String login;

  Member(this.login) {
    if (login == null) {
      throw ArgumentError("login of Member cannot be null. "
          "Received: '$login'");
    }
  }
}

成员具有login属性和一个构造函数,如果登录值为null,则该构造函数将抛出错误。

更新GHFlutterState中的_members声明,以便它是Member对象的列表:

var _members = <Member>[];

更新_buildRow()以在Member对象上使用login属性,而不是使用映射上的login键:

title: Text("${_members[i].login}", style: _biggerFont)

现在,更新发送到_loadData()中的setState()的回调,以将解码后的映射转换为Member对象并将其添加到成员列表中:

setState(() {
  final membersJSON = json.decode(response.body);

  for (var memberJSON in membersJSON) {
    final member = Member(memberJSON["login"]);
    _members.add(member);
  }
});

如果尝试进行热重装,您可能会看到一个错误,但是停止并按F5键来构建和运行该应用,您应该会看到与以前相同的屏幕,除了现在使用新的Member类。


Downloading Images with NetworkImage

来自GitHub的每个成员都有其头像的URL。 现在,您将该头像添加到Member类中,并在应用程序中显示头像。

更新Member类以添加一个avatarUrl属性,该属性不能为null

class Member {
  final String login;
  final String avatarUrl;

  Member(this.login, this.avatarUrl) {
    if (login == null) {
      throw ArgumentError("login of Member cannot be null. "
          "Received: '$login'");
    }
    if (avatarUrl == null) {
      throw ArgumentError("avatarUrl of Member cannot be null. "
          "Received: '$avatarUrl'");
    }
  }
}

使用NetworkImageCircleAvatar widget更新_buildRow()以显示头像:

Widget _buildRow(int i) {
  return Padding(
    padding: const EdgeInsets.all(16.0),
    child: ListTile(
      title: Text("${_members[i].login}", style: _biggerFont),
      leading: CircleAvatar(
        backgroundColor: Colors.green,
        backgroundImage: NetworkImage(_members[i].avatarUrl)
      ),
    )
  );
}

通过将头像设置为ListTileleading属性,它将在行内标题之前显示。 您还使用Colors类在图片上设置了背景色。

现在更新_loadData()以在创建新Member时使用映射中的“ avatar_url”值:

final member = Member(memberJSON["login"], memberJSON["avatar_url"]);

使用F5停止,构建和运行该应用程序。 您会在每一行中看到您的成员头像:


Cleaning the Code

现在,您的大多数代码都位于main.dart文件中。 为了使代码更简洁,您可以重构已添加到文件中的widget和其他类。

lib文件夹中创建名为member.dartghflutter.dart的文件。 将Member类移至member.dart,并将GHFlutterStateGHFlutter类移至ghflutter.dart

您在member.dart中不需要任何import语句,但是ghflutter.dart中的导入应如下所示:

import 'dart:convert';
import 'package:http/http.dart' as http;

import 'package:flutter/material.dart';

import 'member.dart';
import 'strings.dart'; 

您还需要更新main.dart中的导入,以便整个文件包含以下内容:

import 'package:flutter/material.dart';

import 'ghflutter.dart';
import 'strings.dart';

void main() => runApp(GHFlutterApp());


class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appTitle,
      home: GHFlutter(),
    );
  }
}  

按下F5来构建和运行该应用程序,您应该看不到任何更改,但是代码现在更简洁了。


Adding a Theme

您可以通过将theme属性添加到您在main.dart中创建的MaterialApp中,轻松地将主题添加到应用中:

class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appTitle,
      theme: ThemeData(primaryColor: Colors.green.shade800), 
      home: GHFlutter(),
    );
  }
}  

您将绿色用作主题的“材料设计”颜色值。

按下F5来构建和运行应用程序,以查看新的主题:

大多数应用程序屏幕截图均来自Android模拟器。 您还可以在iOS模拟器中运行最终的主题应用程序:

这就是我所说的跨平台!

有关FlutterDart的知识还有很多。 最好的起点是:

  • flutter.dev上的Flutter主页。 您会发现很多很棒的文档和其他信息。
  • 在此处here查看可用的widgets
  • 这里here有一个很好的指南供Android开发人员过渡到使用Flutter
  • 适用于React Native开发人员的类似指南在这里here

后记

本篇主要讲述了Flutter 入门,感兴趣的给个赞或者关注~~~

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

推荐阅读更多精彩内容