0. 前言
本文为flutter学习笔记的中文文档,加上了些练手的小项目。如有不太能理解的地方请参考flutter学习笔记的英文部分,
1. Flutter介绍
一般来说,开发一个移动应用是一个复杂且艰巨的任务。有许多的可用框架来开发一个移动应用,android提供了一个基于JAVA语言的原生开发框架,IOS提供了基于Objective-C / Shift 语言的原生开发框架。
然而,为了开发一个对Android和IOS系统都支持的应用,我们需要用使用两种不同的框架和两种不同的语言来编写代码。为了解决这种复杂的问题,已经存在了些的移动开发框架对Android和IOS都适用。这些框架从简单的基于hybrid开发(混合开发,使用HTML作为UI界面,用JavaScript来处理逻辑)的HTML到特定的复杂语言框架(需要很费力地进行原生开发)。不论这些框架简单或者复杂,都通常有一些缺点,其中一个主要的缺点就是那些框架的性能底下。
在这种情况下,Flutter --简单的、高性能的、基于Dart语言的框架。Flutter通过直接在操作系统的canvas(画布)而不是通过原生开发框架来渲染UI以保证Flutter的高性能。
Flutter也提供许多随时可用的Widgets(UI部件)来创建一个现代应用程序。这些Widgets针对移动应用进行了优化并且使用这些Widgets来设计应用可以像设计HTML一样简单。
具体来说,Flutter应用本身就是一个WIdget。Flutter的Widgets也支持动画(animation)和手势(gesture)。应用的逻辑层是基于原生开发的。Widget可以拥有一个state,通过改变Widget的state,Flutter将会自动的比较WIdget的state新旧状态并且只渲染Widget的必要的变化而不是重新渲染整个widget。
1.1 Flutter的特性
Flutter框架为开发者提供了一下的特性
- 现代的反应框架
- 使用Dart编程语言,并且Dart语言非常容易学习
- 迅速的开发
- 漂亮且流畅的UI界面
- 庞大的Widget种类
- 可以在不同的平台运行相同的UI
- 高性能应用
1.2 Flutter的优点
Flutter有着漂亮的和可自定义的Widget来实现高性能和杰出的移动应用,它满足了所有的自定义的需求。除此之外,Flutter也如下的许多优点:
- Dart有着庞大的软件仓库,可以让你拓展到你的应用中。
- 开发者只需要为两个应用(Android平台和IOS平台的不同应用)编写一份代码即可
- Flutter有更少的测试工作。因为它只需要一份代码,在两个不同的平台只需要进行一次自动化测试即可。
- 因为Flutter的简单性使其非常适合快速开发。而它的定制化能力和拓展性使其变得更加强大。
- 开发者可以使用Flutter来完全控制Widget和布局(layout)。
- Flutter提供许多非常棒的开发工具,并具有惊人的热重载(hot reload)功能。
1.3 Flutter的缺点
尽管Flutter有很多优点,它也有些缺点
- 由于Flutter采用Dart来编程,开发折需要学习新的语言(尽管它很容易学)
- 现代的框架都尽可能的试图讲逻辑层和UI分离开来,但是在Flutter中,UI和逻辑层都是混合在一起的。我们可以通过一些编程技巧和使用高级模块来分离UI和逻辑层。
2. Flutter的安装
这一章将会详细的指导你在本地的电脑上安装Flutter
2.1 windows上的安装
在这部分,让我们看看如何在你系统上安装Flutter SDK 和它所需要的一些东西
Step 1: 去Flutter官网上下载最新版本的Flutter SDK。(译者在翻译的时候,最新的版本是1.2,由于GFW,在国内官网正常是无法访问的,所以译者在这儿推荐安装参考Flutter中文社区)
Step 2: 解压你下载的zip文件到C:\flutter\ 目录中
Step 3: 在系统中添加flutter/bin目录的环境变量
Step 4: Flutter提供了一个工具flutter doctor,它用来检查所有flutter开发所需的是否已经安装。在命令行里输入:
1 | flutter doctor |
Step 5: 上述的命令将会对系统进行分析并会显示如下类似的内容
1 | Doctor summary (to see all details, run flutter doctor -v): |
上面的消息表明了所有的开发工具都是可用的只是设备还未连接。我们可以用usb线来连接android或者启动一个android模拟器来解决上面的问题。
Step 6: 如果flutter doctor反馈没有android SDK的话,请安装最新的Android SDK。
Step 7: 如果flutter doctor返回没有Android Studio的话,请安装最新的Android Stuido。
Step 8: 启动一个安卓模拟器或者连接到真机上来开发安卓应用
Step 9: 在AS(Android Studio,后文简称AS)中安装Flutter和Dart的插件,它提供了创建新的Flutter应用的初始模块,可以运行和调试你的Flutter应用。
- 打开AS
- 点击File(文件) > Settings(设置) > Plugins(插件)
- 选择Flutter插件并安装
- 当提示你安装Dart插件的时候,点击 ‘Yes’
- 重启AS
2.2 MacOS上的安装
在MacOS上安装Flutter有如下步骤:(译者还是在这儿推荐Flutter中文社区上的安装步骤)
Step 1: 去Flutter官网上下载最新版本的Flutter SDK。
Step 2: 解压你下载的zip文件到/path/to/flutter 目录中
Step 3: 在~/.bashrc 文件中添加
1 | export PATH="$PATH:/path/to/flutter/bin" |
Step 4: 在终端输入
1 | source ~/.bashrc |
Flutter提供了一个工具flutter doctor,它用来检查所有flutter开发所需的是否已经安装。这一点与上面的windows相似。
Step 5: 如果flutter doctor提示的话,请安装最新版本的xcode
Step 6: 如果flutter doctor提示的话,请安装最先版本的Android SDK
Step 7: 如果flutter doctoer提示的话,请安装最先版本的AS
Step 8: 启动一个安卓模拟器或者连接到真机上来开发安卓应用
Step 9: 启动IOS模拟器或者连接到真机上来开发IOS应用
Step 10: 在AS中安装Flutter和Dart的插件,它提供了创建新的Flutter应用的初始模块,可以运行和调试你的Flutter应用。
- 打开AS
- 点击 Perferences > Plugins.
- 选择Flutter插件并安装
- 当提示你安装Dart插件的时候,点击 ‘Yes’
- 重启AS
3. 在AS上创建简单的应用
在这一章节,我们通过创建一个简单的Flutter来理解Flutter开发的基本流程。
Step 1: 打开AS
Step 2: 通过点击File -> New -> New Flutter Project 来创建Flutter project。
Step 3: 选择Flutter Application,然后点击Next
Step 4: 配置应用如下然后点击Next
(在这儿配置,可以根据自己需求来配置,不一定非得跟教程一模一样)
- Project Name: hello_app
- Flutter SDK Path: 找到你当初解压的flutter SDK的那个目录
- Project Location: 存放该项目的文件夹
- Description: (这儿看自己需求来写吧)
Step 5: 配置Project
设置公司域名并点击Finish(译者认为,此处不太重要)
AS会创建最小功能的完整应用,我们看一看应用的结构以方便我们根据需求来更改代码,应用的项目结构如下:
应用的不同组成部分解释如下:
- android - 自动生成的源码来创建的Android应用
- ios - 自动生成的源码来创建IOS应用
- lib - 包含有使用flutter框架编写的Dart代码的主文件夹
- lib/main.dart - Flutter应用程序的入口
- test - 包含有Flutter应用测试的Dart代码的文件夹
- test/widget_test.dart - 示例代码
- .gitignore - Git的版本控制文件
- .metadata - flutter工具自动生成的
- .packages - 自动生成的用来追踪flutter的包
- .iml - AS需要使用的项目文件
- pubspec.yaml - Flutter的包管理,可以用来添加第三方包
- pubspec.lock - Flutter包管理自动生成的
- README.md - 以MarkDowm文件格式书写的项目描述文件
Step 6: 用下面的代码来替代在/lib/mian.dart下的dart文件
1 | import 'package:flutter/material.dart'; |
让我们逐行来理解dart代码
(空的一行也算一行)
- Line 1: 导入flutter的包 -material 。material是flutter的一个包,通过Android根据特定的Material 设计来创建UI
- Line 3: Flutter的应用程序入口, 将MyApp类作为参数传递给runApp来调用。
- Line5 - 17: Widget在flutter框架用来创建UI,StatelessWidget是一个widget,它不用维护任何State。MyApp继承)了StatelessWidget并且重写它的build方法。build方法是用来创建应用的部分UI,在这儿build方法使用MaterialApp,一个用来创建应用的root层面的UI。它有三个属性 - title, theme, home。
- title: 应用的标题
- theme: Widget的主题。在这儿我们使用ThemeData类和它的属性-primarySwatch来给整体的应用设置为蓝色。
- home:home是应用的内部UI,这儿我们设置了别的Widget - MyHomePage。
- Line 19 - 38: MyHomePage除了然回了Scaffold Widget,其他都和MyApp相似。Scaffold是一个仅次于MaterialApp widget的顶层widget,用来创建符合material设计的UI。它有两个重要的属性,appBar和body。appBar用来显示应用的头部,APPBar是也是一个widget,用来渲染应用的头部,通常在appBar中使用AppBar。body用来显示应用的实际内容,这儿我们使用Center widget(用来让它的子widget居中)。在上述的例子中的Text用来显示文本,在屏幕的中间展示。
Step 7: 现在通过Run -> Run mian.dart来运行程序
Step 8: 最终的应用输出如下:
4. Flutter应用的体系结构
这一章节,我们将学习到Flutter框架的体系结构
4.1 Widgets
Flutter框架的核心概念就是”在flutter中,一切都是widget”。Widget是组成应用UI的基础UI部分。
在Flutter中,应用的本身就是一个Widget。应用是最顶层的Widget,它的UI是由一个或者更多的子Widget构建而成,而它的子部件也用它们的子部件构成(读者们可以将widget理解为小的积木,构造应用就像一个搭积木的过程,用一个个不同的积木构成)。这种可组合性,让我们可以创建任何复杂度的UI。
例如,在之前创建的hello world 应用的widget层次结构如下:
这儿有几点需要注意的地方:
- MyApp是用户创建的widget,并且它使用Flutter的原生widget-MaterialApp来构建的。
- MaterialApp有一个home的属性来指定主页的UI,本例中使用的自己创建的MyHomePage作为主页UI。
- MyHomePage是用的flutter的其他原生widget-Scaffold来创建的。
- Scaffold有两个属性,body和appBar,这两个属性我们前一个章节有稍微讨论过。
- body用于指定他主要的UI,而appBar用于指定头部的UI
- Herader的UI通过flutter的原生widget - AppBar来构建,Body的UI通过使用Center的widget
- Center的widget有个属性 - child,它引用具体的内容,本例中使用Text来构建的。
4.2 Gestures(手势)
Flutter的widget支持通过一个特别的widget - GestureDetector来进行交互。GestureDetector是一个不可视的widget,它可以捕获用户的交互比例如它的子widget的轻机,拖拽等等。许多flutter的原生widget支持通过使用GestureDetector来与用户进行交互。我们还可以通过使用GestureDetector的widget来将交互的功能整合到现有的widget中。我们将会在后面的章节中详细的讲解Gestures
4.3 State的概念
Flutter通过提供特殊的widget -StatefulWidget来支持State(状态)管理。widget支持状态管理的widget都源自于StatefulWidget,所有其他的widget都是来自于StatelessWidget。Flutter的widget反应在本机当中,这一点跟reactjs很相似,而且当内部状态改变时,StatefulWidget将会自动地进行重新渲染。这种自动的渲染通过找到新旧widget的不同点进行优化,且只对必要的变化进行渲染。
4.4 Layers
Flutter框架中最重要的概念就是将框架按照复杂度按次序的递减划分为不同的种类。一层(layer)是直接使用下一层下构建而成的。最顶层是Android或IOS的特定widget,而下一层就是所有flutter的原生widget。下一层是渲染层,它是一种低级渲染部件可以渲染Flutter的所有东西。层取决于核心平台的特定代码。
下图描述了Flutter的layer的一般概述:
下面的几点总结了Flutter的体系结构:
- 在Flutter中,所有的都是widget,并且一个复杂的widget是由已经存在的widget组成。
- 必要时可以使用GestureDetector widget来添加交互的功能
- 在使用StatefulWidget widget时可以用State的widget来维护状态。
- Flutter提供分层设计,所以可以根据任务的复杂性对任意层进行编程。
我们将会在下面的章节进行详细的讨论。
5. Dart编程
Dart是一种开源通用的编程语言。最开始它是被Google开发出来的。Dart是一种类似C语言语法风格的面向对象的语言。它支持一些像接口,类等等的编程概念。但是,不像其他编程语言,Dart并不支持数组。Dart的集合可以用来复制数据结构,比如数组,范型,和可选类型。
下面的代码展示了一个简单的Dart程序:
1 | void main() |
5.1 变量和数据类型
变量称为存储位置,数据类型仅指与变量和函数关联的数据的类型和大小。
Dart使用 var 关键字来申明变量。var的语法定义如下:
1 | var name = 'Dart'; |
final和const关键字用来申明常量。它们定义如下:
1 | void main() { |
Dart语言支持一下的数据类型
Numbers : 它用来表示数字包括整型和双精度类型
Strings : 它用来表示字符的序列。字符串的值可以用单引号或者双引号来指定。
Booleans : Dart使用bool关键字来表示布尔值(真和假)
Lists and Maps : 它用来表示对象的集合。一个简单的List可以定义成如下:
1
2
3
4void main() {
var list = [1,2,3,4,5];
print(list);
}上面定义列表可以产生列表[1,2,3,4,5]
Map可以定义成如下:
1
2
3
4void main() {
var mapping = {'id': 1,'name':'Dart'};
print(mapping);
}(如果读者有python基础的话,此处的Map类型与python中的字典类型一样,由键值对组成)
Dynamic : 如果变量的类型没有被定义的话,可以使用默认类型dynamic。下面的例子展示了dynamic类型变量的定义
1
2
3
4void main() {
dynamic name = "Dart";
print(name);
}
5.2 决策与循环
决策块会在执行指令前评估条件。Dart支持If, If..else和switch关键字。
循环将会重复代码段直到满足一个特定的条件。Dart支持for, for..in, while 和do..while循环
让我们通过一个简单的控制语句和循环的例子来理解
1 | void main() { |
上述的代码输出了从1到10的偶数
5.3 函数
一个函数是一组语句的集合在一起来执行一个特定的任务。让我们一个用Dart写的一个简单的函数,例子如下
1 | void main() { |
上面的函数将两个值加在一起得到7最终输出。
5.4 面向对象编程
Dart是一个面向对象语言,它支持面向对象编程的特性如类,接口等等。
类是用来创建对象的一个蓝图,类的定义包括以下几点:
- 构造函数
- 函数
- Fields(译者也不知道该如何翻译👀,可以通过后面的例子来体会)
- Getters and setters(译者也不知道该如何翻译👀,可以通过后面的例子来体会)
现在让我们用上面几点创建一个简单的类:
1 | class Employee { |
6. Widget
在之前的章节中,我们提到过“在Flutter框架中,所有的东西都是Widget”这一核心概念。并且在之前的章节里已经学了如何创建一个新的widget。
在这一章节,让我们来更深入的学习widget的概念和在Flutter框架中不同种类的可以使用的widget。
让我们看看之前创建的Hello World的应用的MyHomePage的widget。
1 | class MyHomePage extends StatelessWidget { |
这里我们通过继承StatelessWidget来创建了一个新的widget。
需要注意的是,StatelessWidget在它的派生类中需要一个单一的方法去构建。构造(build)方法通过BuildContext的参数来获取环境的内容来构建widget,并且最终返回它所构建的widget。
在代码中,我们使用title和Key来作为构造函数的参数。其中title是用来展示标题,Key用来区分构造环境中的widget。
在这里,build方法调用了Scaffold的build的方法,Scaffold的build又调用其中的AppBar和Center方法。
最终,Center的build方法又调用了Text的构造方法。
(译者言: 关于build方法,在flutter中,常用的widget的build方法不需要显式的表示出来,如上文的AppBar, Center, Text)
下面的图,或许可以让你更好的理解
6.1 Widget可视化构建
在Flutter中,widget可以根据它们的特性分为多个种类,分类如下:
- 平台特定的widget
- 布局widget
- 状态管理widget
- 与平台无关的widget(基础widget)
让我们对每一点都进行详细的讲解
6.1.1 平台相关widget
Flutter有特定平台(Android和IOS)的特定widget
Android的特定的widget是基于AndroidOS的Material风格设计的。Android特定的Widget就是Material widget。
IOS的特定的widget是基于基于Apple的Human Interface的指导来设计的,IOS特定的widget就是Cupertino widget
其中material中最常用的widget如下:
- Scaffold
- AppBar
- BottomNavigationBar
- TabBar
- TabBarView
- ListTile
- RaiseButton
- FloatingActionButton
- FlatButton
- IconButton
- DropdownButton
- PopupMenuButton
- ButtonBar
- TextField
- Checkbox
- Radio
- Switch
- Slider
- Date & Time Pickers
- SimpleDialog
- AlertDialog
其中Cupertino中最常用的widget如下:
- CupertinoButton
- CupertinoPicker
- CupertinoDatePicker
- CupertinoTimerPicker
- CupertinoNavigationBar
- CupertinoTabBar
- CupertinoTabScaffold
- CupertinoTabView
- CupertinoTextField
- CupertinoDialog
- CupertinoDialogAction
- CupertinoFullscreenDialogTransition
- CupertinoPageScaffold
- CupertinoPageTransition
- CupertinoActionSheet
- CupertinoActivityIndicator
- CupertinoAlertDialog
- CupertinoPopupSurface
- CupertinoSlider
6.1.2 布局widget
在Flutter中,一个widget可以由一个或者多个widget组成。为了将多个widget组合到一个widget时,Flutter提供了大量的布局特征的widget。比如,子widget可以通过是使用Center widget来变得居中。
主流的布局widget如下:
- Container: 一个长方形的盒子容器,可以使用BoxDecoration widget的background, boeder和shadow来进行装饰。
- Center: 让它的子widget居中。
- Row: 将子widget按照水平方向排放。
- Column: 将子widget按照竖直方向排放。
- Stack: 将一个widget放在另一个widget之上。
在后续的章节将会对布局widget进行详细的讨论。
6.1.3 状态管理widget
在Flutter中,所有的widget都被分为StatelessWidget和StatefulWidget。
由StatelessWidget派生出的Widget不含有任何State(状态)的信息,但是它可以包含由StatefulWidget派生出的widget。应用程序具有动态性,它的动态性本质是应用在交互时widget的交互动作和状态的改变。比如,点击一个计数按钮将会增加/减少内部的计数器状态一次而且原生Flutter的widget将会通过使用新的状态信息进行自动的重新渲染。
我们将会在后续的状态管理章节对StatefulWidget进行详细的讨论。
6.1.4 与平台无关的widget(基础widget)
Flutter提供大量的基础widget,可以用一种与平台无关的方式来创建简单的以及复杂的UI。在这一章节我们来看看一些基础的widget
>> Text
Text widget用来展示一段字符。字符串的格式可以使用style属性和TextStyle类来设置。一个简单的例子如下:
1 | Text('Hello World!', style: TextStyle(fontWeight: FontWeight.bold)) |
Text widget有一个特殊的构造函数 - Text.rich , 它接受TextSpan类型的子类来指定不同格式的字符串。TextSpan widget本质上是递归的,所以TextSpan的子部件还可以是TextSpan。一个简单的例子如下:
1 | Text.rich( |
Text widget最重要的几个属性如下:
- maxLines,int : 显示行数的最大值
- overflow,TextOverFlow : 使用TextOverFlow类来指定文字溢出后的显示。(比如,文字过长是直接截断,还是省略成几个点)
- style,TextStyle : 使用TextStyle类来指定字符串的风格。
- textAlign,TextAlign : 使用TextAlign类给文本校准比如,左对齐,右对齐,齐行等等。
- textDirection,TextDirection : 文本“流动”的方向,从左到右或者从右到走。
>> Image
Image widget用来在应用中展示图片。Image widget提供不同的构造函数来从不同的来源导入图片,用法如下:
- Image - 使用ImageProvider的通用图片加载方式
- Image.asset - 从flutter项目的assets来导入图片
- Image.fole - 从系统文件夹导入图片
- Image.memory - 从内存中导入图片
- Image.Network - 从网络导入图片
在Flutter中最简单的和展示图片的方式就是通过将图片作为应用的asset,并且按照需求在widget中导入图片。用法如下:
创建一个文件夹:在项目文件夹里创建”assets”,并且在里面放一些必要的图片。
用如下的方式在pubspec.yaml中指定assets
1
2
3flutter:
assets:
- assets/smiley.png现在,在应用中导入并显示
1
Image.asset('assets/smiley.png')
之前创建的hello world应用中的MyHomePage widget的完整源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: Image.asset("assets/smiley.png")
),
);
}效果图如下:
Image widget最重要的几个属性如下:
- image,ImageProvider: 真实图片的导入(原本图片啥样子的,导入后还是啥样子)
- width,double: 图片的宽
- height,double: 图片的高
- alignment,AlignmentGeometry: 在范围内图片的对齐方式。
>> Icon
Icon widget用来显示IconData类中描述的字体的字形。例如,下面的代码导入了一个简单的电子邮件图标:
1 | Icon(Icons.email) |
把上述代码应用到之前的helle world应用程序中:
1 | class MyHomePage extends StatelessWidget { |
效果如下:
7. Layout
之前强调过的概念”在Flutter中,所有的东西都是widget”,所以Flutter整合了UI布局功能到widget本身当中。Flutter提供相当多的特别设计的widget,如Container,Center,Align等等,仅用于UI的布局当中。通常与其他的widget组合在一起来使用layout widget。下面的章节,我们将详细的讨论。
7.1 Layout Widget的类型
Layout的widget可以基于它的子部件的分成两个不同的类别:
- Widget支持单一的子widget
- Widget支持多个的子widget
接下来我们详细的对这两种进行讨论
7.2 Single Child Widgets
在这一类别中,只有一个widget可以作为其子部件,并且每一个widget都会有特别的布局功能。
举个例子,Center widget仅仅会让它的子部件相对于父部件居中,并且Container widget提供完全的灵活性以不同的方式来放置它的子部件,如padding,decoration等等。
创建具有单一功能的高质量部件(如button,label等等)Single child widget无疑是最佳的选择。
使用Container部件来创建一个简单的button的代码如下:
1 | class MyButton extends StatelessWidget { |
在上面的代码中,我们使用了两个widget(一个Container widget和一个Text widget)来自定义了一个按钮,效果如下:
让我们看一看Flutter中提供的几个重要的Single child widget:
Padding:通过使用给定的填充(padding)来排列其子部件。而Padding是由EdgeInsets类来提供的。
Align: 使用alignment的属性值来在其内部对齐它的子部件。alignment属性的值可以由FractionalOffset类来设置。FractionalOffset类根据到左上角的距离来设置offset(偏移量)。
偏移值得一些值可以如下:
- FractionalOffset(1.0, 0.0)代表着右上。
- FractionalOffset(0.0, 1.0)代表左下。
关于Offset(偏移)的一个简单的例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Center(
child: Container(
height: 100.0,
width: 100.0,
color: Colors.yellow,
child: Align(
alignment: FractionalOffset(0.2, 0.6),
child: Container(
height: 40.0,
width: 40.0,
color: Colors.red,
),
),
),
)FittedBox: 缩放它的子部件,然后根据指定的fit对其子部件进行定位。
AspectRatio: 试图将子部件的大小调整为指定的宽高比。
ConstrainedBox
Baseline
FractinallySizedBox
IntrinsicHeight
IntrinsicWidth
LiimitedBox
OffStage
OverflowBox
SizedBox
SizedOverflowBox
Transform
CustomSingleChildLayout
最一开始的时候创建的hello world应用程序是使用了基于material的布局widget来设计的home page。让我们修改hello world应用程序,以使用下面指定的基本布局widget来构建home page。
- Container: 一个通用的单个子部件盒子类型的widget,具有对齐,填充,边框和边距以及丰富的样式功能。
- Center: 一个简单的单个子部件容器类型的widget,用来让它的子widget居中。
修改后的MyHomePage和MyApp widget的代码如下:
1 | class MyApp extends StatelessWidget { |
上面的代码中:
- Container widget是最顶层的widget或者称为根(root)widget。
- BoxDecoration有许多的属性,比如color,border等等。为了装饰Container widget,上文的使用的color来设置Container的颜色。
- Container的padding使用EdgeInset类来进行设置,它提供了指定填充值的选项。
- Center是Container widget的子widget。然后Text又是Center的子widget。Text用来显示信息,Center用来使文本消息相对于父widget - Container居中。
上述代码最终结果显示效果如下:
7.3 Multiple Child Widgets
在这一种类中,widget将会有不止一个的子部件,并且每一个widget的布局都是独立的。
比如,Row widget允许它的children(注意此处children,与上文的child,读者们应该可以体会其中的区别)在水平方向布局,而Column widget允许它的children在竖直方向上布局。通过组合Row和Column,我们可以构建任何复杂度的widget。
下面我们学一些常用的widget,如下:
- Row - 允许它的children以水平方式排列。
- Column - 允许它的children以竖直方式排列。
- LIstView - 允许将它的children排列为列表。
- GridView - 允许将它的children排列为画廊(gallery)。
- Expanded - 用来让Row和Column的children widget使用尽可能多的最大区域。
- Table - 基于Table的widget。
- Flow - 基于Flow的widget。
- Stack - 基于Stack的widget。
7.4 高级的布局应用
在本节中,让我们学习如何使用单个和多个子布局widget来创建具有自定义设计的产品列表的复杂UI界面。
为了达到上述的目的,请按照下面的步骤,一步步来:
在AS上创建一个新的Flutter应用,product_layout_app。
用下面的代码。来替换AS自动生成的mian.dart代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product layout demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)),
);
}
}此处,我们已经通过继承继承StatelessWidget来创建了MyHomePage,而不是默认的StatefulWidget,并且移除了与StatefulWidget相关的代码。
现在创建一个新的widget - ProductBox。ProductBox是根据下图来设计的:
ProductBox的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name, this.description, this.price, this.image})
: super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 120,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.name,
style: TextStyle(fontWeight: FontWeight.bold)),
Text(this.description),
Text("Price: " + this.price.toString()),
],
)))
]))); }
}请注意观察下面出现在代码中的:
ProductBox有四个参数,具体如下:
- name - 商品名
- description - 商品描述
- price - 商品的价格
- image - 商品的图片
ProductBox使用了七个内置的widget,具体如下:
- Container
- Expanded
- Row
- Column
- Card
- Text
- Image
ProductBox就是使用了上述的widget来设计的。widget的排列或层次结构如下图所示:
下面在应用程序的assets文件(这个文件夹是读者自己创建的)中,放置一些假的商品信息图片。然后在pubspec.yaml中配置我们的assets文件夹,方法如下:
1
2
3
4
5
6
7assets:
- assets floppy.png
- assets iphone.png
- assets laptop.png
- assets pendrive.png
- assets pixel.png
- assets tablet.png iPhone.png
Pixel.png
Laptop.png
Tablet.png
Pendrive.png
Floppy.png
最终在MyHomePage中使用我们刚刚创建的ProductBox。具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage medium",
price: 20,
image: "floppy.png"),
],
));
}
}这儿我们使用ProductBox作为ListView的子(children)widget。
product_layout_app应用程序的完整商品布局代码(main.dart)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product layout demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage medium",
price: 20,
image: "floppy.png"),
],
));
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name, this.description, this.price, this.image})
: super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 120,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.name,
style: TextStyle(fontWeight:
FontWeight.bold)),
Text(this.description),
Text("Price: " + this.price.toString()),
],
)))
])));
}
}最终的应用程序输出如下:
![flutter学习笔记22](/home/h4ck3r/文档/Ling’s Blog/web/source/Blog_Img/flutter学习笔记22.png)
8. Gestures
对于用户而言,gestures(手势)是一个最主要的与手机应用交互的途径。手势通常被定义为旨在激活手机的特定控件的用户的任何物理动作/移动。手势可以从简单的轻击屏幕,到游戏应用里许多的复杂行为。
一些广泛使用的gesture如下:
- Tap: 用指尖触摸设备表面的一小段时间,然后并释放手指(就是轻击)
- Double Tap: 在短时间内轻击两次
- Drag: 用指尖触摸设备的表面,然后以稳定的方式移动指尖,然后最终松开指尖。(就是拖动)
- Flick: 跟拖动类似,不过是用更快的方式进行。
- Pinch: 用两根手指捏住设备的表面。(对比实际中我们缩放图片的方式)
- Spread/Zoom: 与pinch相反(对比实际中我们放大图片的方式)
- Panning: 用指尖触摸设备表面,并在不松开指尖的情况下在设备表面以任意方向移动。
Flutter通过其优秀的widget(GestureDetector)来为所有类型的gesture提供一个极好的支持。GestureDetector是一个不可视的widget,主要用于检测用户的手势。为了识别一个widget的手势,widget需要放在GestureDetector内部。GestureDetector将会捕获手势,然后根据gesture来处理不同的事件。
一些gesture的相关事件如下:
- Tap
- onTapDown
- onTapUp
- onTap
- onTapCancel
- Double tap
- onDoubleTap
- Long press
- onLongPress
- Vertical drag
- onVerticalDragStart
- onVerticalDragUpdate
- onVerticalDragEnd
- Horizontal drag
- onHorizontalDragStart
- onHorizontalDragUpdate
- onHorizontalDragEnd
- Pan
- onPanStart
- onPanUpdate
- onPanEnd
现在让我们通过添加gesture探测来修饰我们的hello world应用程序,并且来理解那些概念。
- 改变MyHomePage widget的body内容,具体如下
1 | body: Center( |
观察到这儿我们把GestureDetector widget放在了Text widget结构上方,捕获onTap的时间,并且最终显示一个对话窗口。(需要注意的是此处的_showDialog是自己定义的方法,直接运行而且没有定义方法的话,会报错。)
当用户点击hello world的信息时,就会执行showDialog的方法来展示一个对话框。这个对话框使用了通用的showDialog和AlertDialog widget来创建一个新的dialog widget。具体的_showDialog的定义代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// user defined function
void _showDialog(BuildContext context) {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Message"),
content: new Text("Hello World"),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}应用程序将会使用热加载特性来在设备上进行重载。现在,简单的点击信息Hello World并且将会显示如下对话框
![flutter学习笔记23](/home/h4ck3r/文档/Ling’s Blog/web/source/Blog_Img/flutter学习笔记23.png)
然后可以点击Close选项来关闭对话框。
mian.dart的完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hello World Demo Application',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
// user defined function
void _showDialog(BuildContext context) {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Message"),
content: new Text("Hello World"),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: GestureDetector(
onTap: () {
_showDialog(context);
},
child: Text(
'Hello World',
))),
);
}
}最终,Flutter通过Listener widget来提供一个低级gesture探测机制。它将会探测所有的用户交互并且调度以下的事件:
- PointerDownEvent
- PointerMoveEvent
- PointerUpEvent
- PointerCancelEvent
Flutter还提供了一组widget来执行特定手势和高级手势。那组widget具体如下:
- Dismissible: 支持flick手势来关闭widget。
- Draggable: 支持drag手势来移动widget。
- LongPressDraggable: 当它的父类widget也是可拖动的话,LongPrssDraggable将会支持drag手势来移动widget。
- DragTarget: 接受所有的可拖动widget。
- IgnorePointer: 在手势检测过程中隐藏widget及其子级。
- AbsorbPointer: 停止手势检测过程本身,因此任何重叠的widget也无法参与手势检测过程,因此不会引发任何事件。
- Scrollable: 支持在widget内部的可滚动的内容。
9. 状态管理
管理应用程序中的状态是应用程序生命周期中最重要和必要的过程之一。
让我们考虑一个简单的购物车应用。
- 用户将会使用其凭据来登录到应用中。
- 一旦用户登录后,应用应该在所有的界面中持续的保持登录状态。
- 同样的,当用户选择产品并保存到购物车中时,购物车信息应在页面之间保留,直到用户关闭购物车为止。
- 在任何情况下,用户及其购物车信息都称为该应用程序的状态。
状态管理可以根据状态在应用中的持续时间划分为两个种类。
- Ephemeral(短暂的) - 仅仅持续几秒,比如一个动画的当前状态或者一个给商品评分的页面。
- app State - 在整个应用周期内都在持续存在,比如登录的用户的详细信息,购物车信息等等。Flutter通过Scoped_model来打到达到上述的目的。
9.1 短暂的状态管理
由于Flutter应用是由许许多多的widget组成,所以状态管理也是通过widget来完成。状态管理的入口是Statefulwidget。Widget可以继承Statefulwidget来维护它和它的子级状态。Statefulwidget为widget提供了创建状态(state)的选项 - State
让我们创建一个带有状态管理的widget - RatingBox。这个widget是用来显示当前特定商品的评分。让我们一步一步的创建带有状态管理的RatingBox widget,具体如下:
通过继承StatefulWidget来创建widget。
1
2class RatingBox extends StatefulWidget {
}给RatingBox创建一个状态(state),_RatingBoxState继承自State
(关于T,已经在前文解释过了) 1
2class _RatingBoxState extends State<RatingBox> {
}重写StatefulWidget的createState的方法来创建state,_RatingBoxState
1
2
3
4class RatingBox extends StatefulWidget {
_RatingBoxState createState() => _RatingBoxState();
}
使用RatingBoxState来创建用户界面的_RatingBox widget。通常,用户界面将通过RatingBox widget本身的构建方法完成。但是,当需要状态管理时,我们需要在__RatingBoxState widget构建用户界面。当widget的状态改变时,这确保了用户界面的再次渲染。
1 | Widget build(BuildContext context) { |
在这儿,我们使用了三颗星星,它们使用IconButton widget来创建的并且使用Row widget来将它们排列成一行。通过红星的顺序来显示评分。比如,如果打分是两星的话,那么前两个星是红色的,最后一个是白色的。
在__RatingBoxState写入方法来改变或者设置widget的状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {
setState( () {
_rating = 3;
});
}这里的每一个方法都通过setState来设置当前的rating。
将用户手势(点击星星)连接到合适的状态更改方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}此处,onPressed的事件会调用相关的函数来改变状态并且随后改变用户的界面。比如,如果一个用户点击了三星,之后_setRatingAsThree将会被调用并且将__rating改变为3。因为状态被改变了,构造方法将会再次被调用然后用户界面将会被再次构建和渲染。
RatingBox widget的完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69class RatingBox extends StatefulWidget {
_RatingBoxState createState() => _RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {
setState( () {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}让我们创建一个新的应用,并且使用我们刚刚创建的RatingBox widget来显示商品的评分。
在AS上创建一个新的Flutter应用,product_state_app。使用下面的代码来取代自动生成的main.dart代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product state demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)),
);
}
}此处,我们已经通过继承StatelessWidget创建了MyHomePage widget而不是默认的StatefulWidget并且移除相关的代码。
将我们之前创建的RatingBox widget包含进去。
创建一个ProductBox widget来列出商品并带上评分。具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name, this.description, this.price,
this.image})
: super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 120,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.name,style: TextStyle(fontWeight:FontWeight.bold)),
Text(this.description),
Text("Price: " +this.price.toString()),
RatingBox(),
],
)))
])));
}
}将ProductBox widget加到MyHomePage widget当中。具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductBox(
name: "Pixel",
description: "Pixel is the most feature phone ever",
price: 800,
image: "pixel.png"),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage
medium",
price: 20,
image: "floppy.png"),
],
));
}
}应用的完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product layout demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for
meeting",
price: 1500,
image: "tablet.png"),
ProductBox(
name: "Pendrive",
description: "iPhone is
price: 100,
image: "pendrive.png"),
ProductBox(
name: "Floppy Drive",
description: "iPhone is
price: 20,
image: "floppy.png"),
ProductBox(
name: "iPhone",
description: "iPhone is
price: 1000,
image: "iphone.png"),
ProductBox(
name: "iPhone",
description: "iPhone is
price: 1000,
image: "iphone.png"),
the stylist phone ever",
the stylist phone ever",
the stylist phone ever",
the stylist phone ever",
],
));
}
}
class RatingBox extends StatefulWidget {
_RatingBoxState createState() => _RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {
setState( () {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ? Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size: _size,)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name, this.description, this.price,
this.image})
: super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.name, style:TextStyle(fontWeight:FontWeight.bold)),
Text(this.description),
Text("Price: " + this.price.toString()),
RatingBox(),
],
)))
])));
}
}最终,运行程序并且试试我们的状态管理 - 商品列表页。结果如下:
![flutter学习笔记24](/home/h4ck3r/文档/Ling’s Blog/web/source/Blog_Img/flutter学习笔记24.png)
点击评分的星星将会更新商品的评分。比如,给苹果设置2星,评分将会显示如下:
![flutter学习笔记25](/home/h4ck3r/文档/Ling’s Blog/web/source/Blog_Img/flutter学习笔记25.png)
9.2 应用状态-Scoped_model
Flutter使用scoped_model包来提供一个简单的方式来管理应用状态。Flutter的包单纯的是可重用功能的库。我们将会在下面的章节中详细的学习flutter的包。
scoped_model提供了三个主类来保证应用程序的健壮状态管理,详细如下:
Model
Model(模型)封装了应用的状态。当我们需要时可以使用模型(通过继承Model类)来维持应用的状态。它有一个单一的方法,notifyListeners,当模块的状态改变时都需要调用这个notifyListeners方法。notifyListeners将会做一些必要的事来更新UI。
1 | class Product extends Model { |
ScopedModel
ScopedModel是一个widget,它保存给定的模型,然后根据需要将其传递给所有后代widget。如果需要多个模型,则需要嵌套ScopedModel。
Single model
1
2
3
4ScopedModel<Product>(
model: item,
child: AnyWidget()
)Multiple model
1
2
3
4
5
6
7ScopedModel<Product>(
model: item1,
child: ScopedModel<Product>(
model: item2,
child: AnyWidget(),
),
)ScopedModel.of是一种用于获取ScopedModel基础模型的方法。即使不需要更改UI,也可以在不需要更改UI的情况下使用它。以下内容不会更改产品的用户界面(此处指代rating)
1
ScopedModel.of<Product>(context).updateRating(2);
ScopedModelDescendant
ScopedModelDescendant是一个widget,它可以从更高级别的widget(此处指代ScopedModel)获取模型,当模型改变时将会构建它的用户界面。
ScopedModelDescendant有两个属性,builder和child。child是UI的一部分,它并不会做任何变化,并且会被传递给builder。builder接受一个带有三个参数的函数:
content - ScopedModelDescendant传递的应用程序内容
child - UI的一部分不会基于model做任何变化。
model - 该实例的实际模型。
1
2
3
4return ScopedModelDescendant<ProductModel>(
builder: (context, child, cart) => { ... Actual UI ... },
child: PartOfTheUI(),
);
让我们使用scoped_model而不是StatefulWidget来改变我们之前的例子。
在AS中创建一个新的Flutter应用,product_scoped_modek_app。
使用我们的product_state_app代码来替代初始的默认代码(main.dart)
从product_nav_app中复制assets文件夹到product_rest_app中并将assets添加到pubspec.yaml文件中
1
2
3
4
5
6
7
8flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png在pubspec.yaml中配置scoped_model包,具体如下:
1
2dependencies:
scoped_model: ^1.0.1此处,你应该使用最新的版本。请在此处查找最新版本的包。(译者在翻译时候最新的版本还是1.0.1)
AS将会提示你更新pubspec.yaml
![flutter学习笔记33](/home/h4ck3r/文档/Ling’s Blog/web/source/Blog_Img/flutter学习笔记33.png)
点击Get dependencies选项。AS将会从网上获取到包,并配置到应用中。
使用我们先前的代码替代默认的初始代码(main.dart)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product state demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)),
);
}
}在main.dart中导入scoped_model包
1
import 'package:scoped_model/scoped_model.dart';
让我们创建一个商品类,Product.dart来组织我们的商品信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import 'package:scoped_model/scoped_model.dart';
class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;
Product(this.name, this.description, this.price, this.image,
this.rating);
factory Product.fromMap(Map<String, dynamic> json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);
}
void updateRating(int myRating) {
rating = myRating;
notifyListeners();
}
}这儿我们使用了notifyListeners,当rating被改变时,notifyListeners将会改变UI。
让我们在Product类中编写getProducts方法来生成我们的虚拟商品记录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29static List<Product> getProducts() {
List<Product> items = <Product>[];
items.add(Product(
"Pixel",
"Pixel is the most feature-full phone ever",
800,
"pixel.png", 0));
items.add(Product(
"Laptop",
"Laptop is most productive development tool",
2000,
"laptop.png", 0));
items.add(Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png", 0));
items.add(Product(
"Pendrive",
"Pendrive is useful storage medium",
100,
"pendrive.png", 0));
items.add(Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium",
20,
"floppy.png", 0));
return items;
}在mian.dart中导入product.dart
1
import 'Product.dart'
让我们改变我们新的RatingBox以支持scoped_model概念。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69class RatingBox extends StatelessWidget {
RatingBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
double _size = 20;
print(item.rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 1
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(2),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 3
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}此处,RatingBox继承自StatelessWidget而不是StatefulWidget。同时,我们使用Product 模型中的updateRating方法来设置rating。
让我们修改ProductBox widget以与Product,ScopedModel和ScopedModelDescendant类一起使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: ScopedModel<Product>(
model: this.item,
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style:
TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
ScopedModelDescendant<Product>(
builder: (context, child, item) {
return RatingBox(item: item);
})
],
))))
]),
));
}
}这儿,我们将RatingBox与ScopedModel和ScopedModelDecendant联系在了一起.
改变我们的MyHomePage widget来使用我们的ProductBox widget。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item: items[index]);
},
));
}
}此处,我们使用来ListView.builder来动态的创建我们的商品列表。
应用的完整代码如下:
main.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'Product.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product state demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item: items[index]);
},
));
}
}
class RatingBox extends StatelessWidget {
RatingBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
double _size = 20;
print(item.rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 1
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(2),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 3
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: () => this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: ScopedModel<Product>(
model: this.item,
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style:
TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
ScopedModelDescendant<Product>(
builder: (context, child, item) {
return RatingBox(item: item);
})
],
))))
]),
));
}
}最终编译运行,结果跟之前的例子的图一样,除了这个应用使用了scoped_model概念。
9.3 导航和路由
在任何应用程序中,从一个页面/屏幕导航到另一个页面/屏幕都定义了应用程序的工作流程。处理应用程序导航的方式称为“路由”。Flutter提供了一个基本的路由类– MaterialPageRoute和两个方法Navigator.push和Navigator.pop,以定义应用程序的工作流程。
MaterialPageRoute
MaterialPageRoute是一个widget,用于通过使用特定于平台的动画替换整个屏幕来渲染其UI。
1 | MaterialPageRoute(builder: (context) => Widget()) |
在这里,builder将通过提供应用程序的当前上下文来接受一个函数来构建其内容。
Navigation.push
Navigation.push用于使用MaterialPageRoute widget来导航到新界面。
1 | Navigator.push( |
Navigation.pop
Navigation.pop用于导航到上一个界面。
1 | Navigator.push(context); |
让我们创建一个新的应用程序以更好地理解导航概念。
在ASh中创建一个新的flutter应用。
将product_state_app中的assets文件夹复制到product_nav_app。并且在在pubspec.yaml中添加assets。
1
2
3
4
5
6
7
8flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png使用下面的代码来替代默认的代码(main.dart):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product state demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)),
);
}
}让我们创建一个Product类来组织商品信息。
1
2
3
4
5
6
7
8class Product {
final String name;
final String description;
final int price;
final String image;
Product(this.name, this.description, this.price, this.image);
}让我们在Product类中编写方法getProducts来生成我们的虚拟产品记录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29static List<Product> getProducts() {
List<Product> items = <Product>[];
items.add(Product(
"Pixel",
"Pixel is the most feature-full phone ever",
800,
"pixel.png"));
items.add(Product(
"Laptop",
"Laptop is most productive development tool",
2000,
"laptop.png"));
items.add(Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png"));
items.add(Product(
"Pendrive",
"Pendrive is useful storage medium",
100,
"pendrive.png"));
items.add(Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium",
20,
"floppy.png"));
return items;
}将product.dart导入到main.dart中。
1
import 'Product.dart';
让我们导入我们新的widget - RatingBox
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90class RatingBox extends StatefulWidget {
_RatingBoxState createState() => _RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}让我们修改ProductBox widget以使用我们的新的Product类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item})
: super(key: key);
final Product item;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " + this.item.price.toString()),
RatingBox(),
],
)))
]),
));
}
}让我们重写MyHomePage widget以使用Product模型并使用ListViewl来列出所有产品。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductPage(item:
items[index]),
),
);
},
);
},
));
}
}在这里,我们已经使用MaterialPageRoute导航到产品详细信息页面。
现在,让我们添加ProductPage来显示产品详细信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41class ProductPage extends StatelessWidget {
ProductPage({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/appimages/" +
this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight: FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
],
)))
]),
),
),
);
}
}应用程序的完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Product {
final String name;
final String description;
final int price;
final String image;
Product(this.name, this.description, this.price, this.image);
static List<Product> getProducts() {
List<Product> items = <Product>[];
items.add(Product(
"Pixel", "Pixel is the most featureful phone ever", 800,
"pixel.png"));
items.add(Product("Laptop", "Laptop is most productive development
tool",
2000, "laptop.png"));
items.add(Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png"));
items.add(Product(
"Pendrive", "iPhone is the stylist phone ever", 100,
"pendrive.png"));
items.add(Product(
"Floppy Drive", "iPhone is the stylist phone ever", 20,
"floppy.png"));
items.add(Product(
"iPhone", "iPhone is the stylist phone ever", 1000,
"iphone.png"));
return items;
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product Navigation demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductPage(item:
items[index]),
),
);
},
);
},
));
}
}
class ProductPage extends StatelessWidget {
ProductPage({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight:FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
],
)))
]),
),
),
);
}
}
class RatingBox extends StatefulWidget {
_RatingBoxState createState() => _RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight:
FontWeight.bold)),
Text(this.item.description),
Text("Price: " + this.item.price.toString()),
RatingBox(),
],
)))
]),
));
}
}
运行该应用程序,然后单击任何一项产品。它将显示相关的详细信息页面。我们可以通过单击后退按钮转到主页。该应用程序的产品列表页面和产品详细信息页面如下所示:
![flutter学习笔记26](/home/h4ck3r/文档/Ling’s Blog/web/source/Blog_Img/flutter学习笔记26.png)
![flutter学习笔记27](/home/h4ck3r/文档/Ling’s Blog/web/source/Blog_Img/flutter学习笔记27.png)
(常用的导航方式其实还有Navigator.pushNamed(),具体使用方法参考Flutter中文社区的教程)
版权声明:转载请注明出处!
文章说明: 文章如有不足或者纰漏之处,欢迎留言斧正!