作者:真丶深红骑士

链接:

https://juejin.im/user/597247ad5188255aed1fbba6

本文由作者授权发布。

01前言

1.什么是Flutter

上周我的一位微信好友问我有没有学Flutter,我回答说还没真正学,他说应该要接触一下。对于新技术的诞生,我始终保持敬畏之心,和另一位大学舍友聊了当时如何入坑Android的经历,才发现自己的学习方式和路线有很多的问题,知识点很零乱,知识没有系统化,不多说了,后面学习新的知识一定要从“碎片化”到“整体化”。2018年2月,在世界移动大会上,Google发布了Flutter的第一个beta版本,2108年6月11日发布首个预览版,在2018年12月05日北京时间凌晨1点45分,在Flutter Live上,谷歌Flutter团队推出Flutter1.0,Flutter1.0版本是UI工具包的第一个稳定版本,在2019年2月27日世界移动通信大会上Google推出1.2版本,带来全新的Web开发工具。另外今日头条团队即将开源让Flutter真正支持View级别的混合开发(上层Flutter Framework引入Widget/LayerTree等概念自己实现了界面描述框架,下层Flutter Engine把LayerTree用OpenGL渲染成用户界面),闲鱼团队也开源了基于 Redux数据管理的组装式Flutter应用框架。什么是Flutter呢?Flutter是一个跨平台的免费开源的移动UI框架,是Google的移动应用SDK,用于在极短时间内在iOS和Android平台上创建高质量的原生体验,简而言之就是在iOS下和Android下共用一套代码,一套代码就能在两个操作系统下运行,其官方编程语言是是Dart,学习这门语言很快就上手。Flutter提供很多丰富的UI组件库,开发者可以快速开发出灵活的UI界面,另外Flutter已经加入Material Design组件大家庭中,也就是说Flutter可以使用Material Theming和Material 组件,可以相信未来会有更多的创意设计UI风格会涌现。

2.Flutter的特性


1、快速开发,Flutter的热重载可帮助快速轻松试验,构建UI,添加功能和快速修复错误,在iOS和Android的模拟器和硬件上体验亚秒级重载同时不会丢失状态,这里直接引用官方的图:

2、机具表现力和美观UI,Flutter内置的Material Design和Cupertino(iOS风格)的部件、丰富的手势API、自然平滑的滑动和不同的平台表现来提升用户体验,直接看下图:

3、现代化响应式框架,使用现代化响应式框架和丰富的平台、布局和基础组件来构建用户界面,使用功能强大且灵活的API(针对2D,动画,手势,动效等)解决复杂的用户界面设计。

 1class CounterState extends State<Counter> { 2  int counter = 0; 3 4  void increment() { 5    // Tells the Flutter framework that state has changed, 6    // so the framework can run build() and update the display. 7    setState(() { 8      counter++; 9    });10  }1112  Widget build(BuildContext context) {13    // This method is rerun every time setState is called.14    // The Flutter framework has been optimized to make rerunning15    // build methods fast, so that you can just rebuild anything that16    // needs updating rather than having to individually change17    // instances of widgets.18    return new Row(19      children: <Widget>[20        new RaisedButton(21          onPressed: increment,22          child: new Text('Increment'),23        ),24        new Text('Count: $counter'),25      ],26    );27  }28}

4、使用平台原生功能及SDK,通过平台API,第三方SDK和原生代码让英语具有强大的扩展性,Flutter允许重复使用现有的Java,Swift和Object代码,并访问iOS和Android上的原生功能和SDK,例如下图获取手机电量数值:


 1Future<Null> getBatteryLevel() async { 2  var batteryLevel = 'unknown'; 3  try { 4    int result = await methodChannel.invokeMethod('getBatteryLevel'); 5    batteryLevel = 'Battery level: $result%'; 6  } on PlatformException { 7    batteryLevel = 'Failed to get battery level.'; 8  } 9  setState(() {10    _batteryLevel = batteryLevel;11  });12}

5、统一应用开发体验:Flutter丰富的工具库可以让开发者轻松在iOS和Android设备上实现自己的想法,可以充分利用现有的大部分Java、Object-C或者Swift代码。

3.2019年Flutter产品线

2019年一月底,Flutter团队公布了2019年Flutter的产品线,为以下几点确定了明确的计划:

  • 核心基础:Bug修复,性能调优,内存诊断工具优化,改进测试流程。

  • 生态系统:更好的C/C++库支持,推进官方开发/维护的Packages达到与核心框架相同质量和完整性。

  • 动态更新:在国内,这是必不可少的,提供Android上的动态修复:代码更新从服务器推送到Android应用里,也就是热更新。

  • 支持开发移动端web应用。 并且Flutte UX研究团队会定期根据用户反馈来助力打造更优的Flutter,根据用户反馈来调整开发重点,可见Flutter UX团队的用心和付出程度,今年拭目以待。

02Flutter架构

1.架构图

Flutter框架从到下包含三部分:函数式响应的Framework(Dart),Engine(C++),Embedder(Platform Specific)。下面直接上图:

  • FrameWork是用Dart实现,提供了Material风格的小部件、Cupertino风格的小部件(用于iOS)、文本图像按钮组件、动画、手势等。

  • Engine时用C++实现的,上图详细了,实际包含三部分:Skia、Dart、Text。Skia是一个开源的2D图形库,为硬件和软件平台提供API。Dart包括Dart运行时和垃圾回收。Flutter在调试模式下运行,由JIT(Just in Time,运行时编译,边运行边编译)支持,如果在发布模式下,则是通过AOT(Ahead of Time,运行前编译,普通的静态编译)将Dart代码编译原生的"arm"代码。Text是指文本渲染库。

  • Embedder是指嵌入器,就是可将Flutter嵌入到各种平台中,它的主要任务是渲染Surface设置、线程设置和插件。

2.渲染过程


这里结合上面两张图来看,可以知道,当需要更新UI的时候,Framework通知Engine,Engine会等到下个Vsync信号到达的时候,会通知Framework,然后Framework会进行Animate,Build,Layout,Paint,最后生成Layer。Compositor 把所有的Layer组合成Scene,再通过Skia进行处理,最后Engine通过GPU GI接口提交数据给GPU,GPU最后经过处理后在显示器显示。上面简单了解什么Flutter,Flutter特性和框架,有了初步的了解,下面就开始一步一步实现第一个Flutter应用。

03Flutter环境搭建

1.获取Flutter SDK

务必电脑安装git,本文是在Windows环境下配置的,MAC下配置环境流程是一致的。首先要获取Flutter SDK,使用git去克隆仓库然后添加Flutter工具到自己的环境变量,运行flutter doctor来显示剩下需要安装的依赖,国内用户最好配置一下两个环境变量:

1PUB_HOSTED_URL=https://pub.flutter-io.cn

如下图所示:

1FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

如图所示:

因为这次我第一次在电脑上安装Flutter,所以就要克隆这个远程仓库,我在电脑F盘下执行下面git命令:

1 git clone -b beta https://github.com/flutter/flutter.gitgit clone -b beta https://github.com/flutter/flutter.git

如图所示:

下载比较慢,等了十多分钟才下载完。下载完看到F盘果然有一个名字叫flutter的文件夹,接着将克隆下来的项目bin目录配置到环境变量Path去,因为我是下载到F盘,所以下图路径是:

如果需要更新flutter的sdk,在命令行执行flutter upgrade命令即可。


2.运行flutter doctor


打开一个新的命令提示符窗口,运行下面命令flutter doctor,看是否需要安装任何依赖项来完成安装,这个命令会检测环境和在终端生成报告,Dart SDK和Flutter捆绑在一起,没必要单独去安装Dart。初次运行它会下载自己的依赖库并且自行编译,可能比较慢。后续运行flutter命令就会很快。这里注意,如果CMD窗口显示乱码问题,下面是解决方案:

  • Win+R进入进入CMD命令行输入regedit进入注册表

  • 找到 HKEY_CURRENT_USER\Console\%SystemRoot%_system32_cmd.exe 如果该项下已存在CodePage项,则把值改为十进制”65001”,如果不存在,在该项下新建一个 DWORD(32位值),命名为“CodePage”,值设为“65001”

  • 重启cmd后生效


    输入flutter doctor运行结果截图所示:


    注意看到Android toolchain选项 意思是得要运行flutter doctor --android-licenses同意协议才可以安装Android工具链。输入flutter doctor --android-licenses一直按y即可。最后重新输入flutter doctor,结果如下图:


    从上面运行结果可以知道一下信息:

  • Flutter的版本和渠道已经安装

  • Flutter运行需要的Android工具链已安装

  • Android studio开发工具已经安装

  • flutter插件和Dart插件没有安装(在Android stdio安装下面会说)

  • 没有连接手机

3.搭建Android Studio开发环境

在Android Studio--File--Settings --plugins搜索Flutter即可,如下图:


点击安装的时候会弹出一下提示框:


大概意思是安装Flutter插件需要Dart插件的支持,就是需要和Dart插件一起安装,安装完成后,重启开发工具,就可以新建和开发程序了。注意:如果提示cannot download xxxx的话去plugins.jetbrains.com/搜索插件名字,下载对应的插件后解压到对应的位置,如下图:


或者点击插件本地安装:


最后重启自己的编译器即可。

04创建第一个Flutter程序

1.New Flutter Application


因为现在是要创建应用程序,因此在Create New Flutter Project下选择Flutter Application


2.配置项目信息


在下一个页面输入项目名字,配置Flutter SDK目录,项目存放的路径,项目的描述,公司的域名,项目的包名如图所示:



最后点击finish等待Android Studio完成所要创建的Flutter项目了。

3.项目目录


项目建立完成后,编辑器给我们生成的目录如下:


  • android:Andorid相关代码目录,里面代码配置和单独创建Andorid项目有些不一样

  • ios:iOS相关代码目录,存放Flutter与ios原生交互的一些代码

  • lib:应用源文件,dart文件,核心文件,可以创建不同的文件夹,存放不同文件

  • test:测试文件

  • .gitignore:忽略文件,记录一些不需要关注变更记录的文件,就是不添加到版本记录里面

  • .metadata:记录一些Flutter project一些基本信息,如版本,项目类型

  • .packages:记录一些lib文件的路径

  • .iml:是由IntelliJ IDEA创建的模块文件,用于开发Java应用程序的IDE。它存储有关开发模块的信息,该模块可能是Java,Plugin,Android或Maven组件; 保存模块路径,依赖关系和其他设置

  • pubspec.lock:这是根据当前项目依赖所生成的文件,记录当前使用的依赖版本

  • pubspec.yaml:包含Flutter应用程序的包数据,这个是配置依赖项的文件,比如配置远程public仓库的依赖项,或者本地资源(图片,音视频)

  • README.md:根据意思就是“读我”,这里会记录一些项目结构,详细信息,帮助信息,了解一个项目都是通常通过这个文件入手。

总体来看和原生Android的工程结构不一样了,因为代码都是在lib目录完成的,所以不能用Android多module多lib结构去创建module和lib,除非要用到原生交互的代码,可以在android目录里面去写,然后在lib目录里面去引用。相对Android开发者而言,多了ios目录,ios开发者而言,多了android目录,其他文件上面有具体详细说明。因为lib目录是开发者最主要关注的,打开lib目录,发现有个后缀是dart的文件,里面内容都是用dart语法来写的,还发现有void main() => runApp(MyApp());main()函数对于学过java而言都非常清晰,这个应该是入口函数,另外发现StatelessWidget,StatefulWidget一些小控件,这里想应该是UI组件吧。这里先不管那么多,直接点击运行图标,运行效果图如下:



走到这里,说明项目环境配置成功,并成功运行第一个简单的程序。
05编写第一个应用

1.编写“Hello world”

现在还是按照新手走,先自己撸个“Hello world”出来吧。把main.dart中所有代码去掉,替换下面代码,屏幕中心显示Hello World

 1import 'package:flutter/material.dart'; 2 3//这个是Dart中单行函数或者方法的简写 4void main() => runApp(MyApp()); 5 6//程序继承StatelessWidget,该应用程序成为一个widget,在Flutter中,大多数东西都是widget 7class MyApp extends StatelessWidget { 8  // 这个是应用的根widget 9  @override10  Widget build(BuildContext context) {11    //注意:一个app只能有一个MaterialApp12    return MaterialApp(13      //标题栏的名字14      title: 'Hello Flutter',15      //这个是Material library提供的一个widget,它提供了默认的导航栏、标题栏16      //包含主屏幕的widget树的body属性17      home:new Scaffold(18        appBar:new AppBar(19          title:const Text("Weclome to Flutter"),20        ),21        body:const Center(22          child:const Text("Hello World"),23        ),24    ),25    );26  }27}import 'package:flutter/material.dart';
2
3//这个是Dart中单行函数或者方法的简写
4void main()=> runApp(MyApp());
5
6//程序继承StatelessWidget,该应用程序成为一个widget,在Flutter中,大多数东西都是widget
7class MyApp extends StatelessWidget{
8 // 这个是应用的根widget
9 @override
10 Widget build(BuildContext context){
11 //注意:一个app只能有一个MaterialApp
12 return MaterialApp(
13 //标题栏的名字
14 title: 'Hello Flutter',
15 //这个是Material library提供的一个widget,它提供了默认的导航栏、标题栏
16 //包含主屏幕的widget树的body属性
17 home:new Scaffold(
18 appBar:new AppBar(
19 title:const Text("Weclome to Flutter"),
20 ),
21 body:const Center(
22 child:const Text("Hello World")
,
23 ),
24 ),
25 )
;
26 }
27}

Android模拟器运行效果如下:


iOS模拟器运行效果如下:


感觉在iOS运行效果比Android上好多了。看到这里发现Android并不是沉浸式状态栏,而iOS默认就是沉浸式了,那有没有办法也能让Android版本也做成沉浸式呢,答案是肯定有的,一开始我的思路是应该是在main方法里判断是不是Android版本,如果是就设置,网上找里找,这种方法果然可行:


  1. 首先先导包:

1import 'dart:io';2import 'package:flutter/services.dart';import 'dart:io';2import 'package:flutter/services.dart';

  1. 在mian方法根据Android版本做设置:

 1//入口函数 2void main() { 3  //MaterialApp组件渲染后 4  runApp(MyApp()); 5  //判断如果是Android版本的话 设置Android状态栏透明沉浸式 6  if(Platform.isAndroid){ 7    //写在组件渲染之后,是为了在渲染后进行设置赋值,覆盖状态栏,写在渲染之前对MaterialApp组件会覆盖这个值。 8    SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent); 9    SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);10  }11}//入口函数
2void main(){
3 //MaterialApp组件渲染后
4 runApp(MyApp());
5 //判断如果是Android版本的话 设置Android状态栏透明沉浸式
6 if(Platform.isAndroid){
7 //写在组件渲染之后,是为了在渲染后进行设置赋值,覆盖状态栏,写在渲染之前对MaterialApp组件会覆盖这个值。
8 SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
9 SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
10 }
11}

还有另外一种方法,因为Flutter主入口只有一个MainActivity,也就是说所有的Flutter页面都会运行这个MainActivity,那我们只需要在这个主入口判断一下版本号然后将状态栏颜色设置成透明,具体位置在android->app->src->main->xxx->MainActivity:


 1public class MainActivity extends FlutterActivity { 2  @Override 3  protected void onCreate(Bundle savedInstanceState) { 4    super.onCreate(savedInstanceState); 5    //设置状态栏透明 6    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP) 7    {//API>21,设置状态栏颜色透明 8      getWindow().setStatusBarColor(0); 9    }10    GeneratedPluginRegistrant.registerWith(this);11  }12}public class MainActivity extends FlutterActivity{2  @Override3  protected void onCreate(Bundle savedInstanceState){4    super.onCreate(savedInstanceState);5    //设置状态栏透明6    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)7    {//API>21,设置状态栏颜色透明8      getWindow().setStatusBarColor(0);9    }10    GeneratedPluginRegistrant.registerWith(this);11  }12}

最终效果Android和iOS下运行如下:


这样效果好很多了。另外发现,Android下的标题栏是左边对齐的,那怎么做成iOS那样标题栏在中间呢?很简单,在AppBar加上Center widget就可以了,代码如下:


1appBar:new AppBar(2          //ios和android标题栏统一在中间3          title:new Center(child :const Text("Weclome to Flutter")),45        ),new AppBar(2          //ios和android标题栏统一在中间3          title:new Center(child :const Text("Weclome to Flutter")),45        ),

最终效果如下:


这样真正做到了iOS和Android版本统一了。上面代码app继承了StatelessWidget,这样本身也成为了widget,在Flutter中,很多时候一切都看作是widget,layout和padding等等。上面例子结构很清晰明了,就是StatelessWidget类包含了(应用栏)Appbar,和构成主页面widget树结构的body属性。而body的widget又包含了一个Center widget,Center widget又包含一个Text子widget,Center widget可以将子widget对齐到屏幕中心。
2.使用外部package
现在,通过在pubspec.yaml文件配置依赖项,去依赖一个提供英文单词的包,在pub.dartlang.org/flutter/这个网站可以找到很多开源软件包,可以搜索指定的软件包查看对应版本。

  1. pubspec文件管理着Flutter应用程序的静态资源文件,那在pubspec.yaml文件,将english_words(3.1.5版本)添加到依赖项,这里要注意:和^之间是有空格的,不能会出错。如下所示:

1dependencies:2  flutter:3    sdk: flutter45  # The following adds the Cupertino Icons font to your application.6  # Use with the CupertinoIcons class for iOS style icons.7  cupertino_icons: ^0.1.28  english_words: ^3.1.5dependencies:
2 flutter:
3 sdk: flutter
4
5 # The following adds the Cupertino Icons font to your application.
6 # Use with the CupertinoIcons class for iOS style icons.
7 cupertino_icons: ^0.1.2
8 english_words: ^3.1.5
  1. 这时候点击编辑器的右上角的Packages get,就是把package拉取到项目中去,并且可以看到控制台输出:Runningflutter packages getin flutter_demo...

  2. 在lib/main.dart下,将english_words导入,显示灰色证明你导入的库没有使用,如下图所示:

  3. 改用英文单词的package来生成文本,代码改为如下:

 1//程序继承StatelessWidget,该应用程序成为一个widget,在Flutter中,大多数东西都是widget 2class MyApp extends StatelessWidget { 3  // 这个是应用的根widget 4  @override 5  Widget build(BuildContext context) { 6    //随机生成函数 7    var wordPair = new WordPair.random(); 8    return MaterialApp( 9      //标题栏的名字10      title: 'Hello Flutter',11      //这个是Material library提供的一个widget,它提供了默认的导航栏、标题栏12      //包含主屏幕的widget树的body属性13      home:new Scaffold(14        appBar:new AppBar(15          //ios和android标题栏统一在中间16          title: new Center(child: const Text("Weclome to Flutter")),17        ),18        body:new Center(19          //使用随机生成的英文单词 为什么不能const呢 因为 内容发生变化 const是用来修饰常量的20          child:new Text(wordPair.asPascalCase),21        ),22    ),23    );24  }25}//程序继承StatelessWidget,该应用程序成为一个widget,在Flutter中,大多数东西都是widget
2class MyApp extends StatelessWidget{
3 // 这个是应用的根widget
4 @override
5 Widget build(BuildContext context){
6 //随机生成函数
7 var wordPair = new WordPair.random();
8 return MaterialApp(
9 //标题栏的名字
10 title: 'Hello Flutter',
11 //这个是Material library提供的一个widget,它提供了默认的导航栏、标题栏
12 //包含主屏幕的widget树的body属性
13 home:new Scaffold(
14 appBar:new AppBar(
15 //ios和android标题栏统一在中间
16 title: new Center(child: const Text("Weclome to Flutter")),
17 ),
18 body:new Center(
19 //使用随机生成的英文单词 为什么不能const呢 因为 内容发生变化 const是用来修饰常量的
20 child:new Text(wordPair.asPascalCase)
,
21 ),
22 ),
23 )
;
24 }
25}

注意的是Center widget 和 Text widget要用new来创建,因为内容修改了,并不是常量了,这时候按保存就可以看到新的单词出现在屏幕中间文本了。

3.添加有状态的widget

上面MyApp是继承StatelessWidget,StatelessWidget是无状态的也是不可控的,意思是其属性是不能改变的,所有的值都是最终的。在平时开发中,很多控件都需要根据特定场景改变自身的状态,那么Flutter有没有提供有状态的widget呢?答案肯定是有的,Statefulwidget是有状态的,在其生命周期保持的状态可能会变化,实现一个有状态的widget至少需要两个类:StatefulWidgets类和State类。

  1. 添加RandomWordsWidget类,这个类继承StatefulWidget类,这个类添加到main.dart文件最底下


1//创建有状态的widget2class RandomWordsWidget extends StatefulWidget{34  @override5  State<StatefulWidget> createState() {6    return new RandomWordsState();7  }8}//创建有状态的widget2class RandomWordsWidget extends StatefulWidget{34  @override5  State<StatefulWidget> createState(){6    return new RandomWordsState();7  }8}
  1. 添加RandomWordsState了,这个类会保存RandomWordsWidget的状态,后面会保存一些特殊不同状态下的词组,并且在里面添加build方法,不然会报错,把生成单词的代码放到这个build类中去,如下图:

    
    
    1//用来保存RandomWords widget的状态2class RandomWordsState extends State<RandomWordsWidget>{3  @override4  Widget build(BuildContext buildContext){5    var wordPair = new WordPair.random();6    return new Text(wordPair.asPascalCase);78  }9}//用来保存RandomWords widget的状态2class RandomWordsState extends State<RandomWordsWidget>{3  @override4  Widget build(BuildContext buildContext){5    var wordPair = new WordPair.random();6    return new Text(wordPair.asPascalCase);78  }9}
  2. MyApp下改成创建RandomWordsWidget即可:


 1//程序继承StatelessWidget,该应用程序成为一个widget,在Flutter中,大多数东西都是widget 2class MyApp extends StatelessWidget { 3  // 这个是应用的根widget 4  @override 5  Widget build(BuildContext context) { 6    return MaterialApp( 7      //标题栏的名字 8      title: 'Hello Flutter', 9      //这个是Material library提供的一个widget,它提供了默认的导航栏、标题栏10      //包含主屏幕的widget树的body属性11      home:new Scaffold(12        appBar:new AppBar(13          //ios和android标题栏统一在中间14          title: new Center(child: const Text("Weclome to Flutter")),15        ),16        body:new Center(17          child:new RandomWordsWidget()18        ),19    ),20    );21  }//程序继承StatelessWidget,该应用程序成为一个widget,在Flutter中,大多数东西都是widget2class MyApp extends StatelessWidget{3  // 这个是应用的根widget4  @override5  Widget build(BuildContext context){6    return MaterialApp(7      //标题栏的名字8      title: 'Hello Flutter',9      //这个是Material library提供的一个widget,它提供了默认的导航栏、标题栏10      //包含主屏幕的widget树的body属性11      home:new Scaffold(12        appBar:new AppBar(13          //ios和android标题栏统一在中间14          title: new Center(child: const Text("Weclome to Flutter")),15        ),16        body:new Center(17          child:new RandomWordsWidget()18        ),19    ),20    );21  }

这时候运行的效果还是跟之前一样,不过实现方式不一样。

4.创建ListView

下面创建一个滑动组件ListView,开发者接触最多应该是这个滑动组件,下面实现当用户滑动列表的时候,ListView会不断增长,不断显示新的单词。

  1. 在RandomWordsState这个类创建一个数组列表,用来保存词组,这个变量以下划线为开头,Dart语言中下划线前缀是表示强制私有

    
    
    1final _normalWords = <WordPair>[];final _normalWords = <WordPair>[];
  2. 在RandomWordsState类添加_buildNormalWords方法,用于构建一个ListView,ListView提供了itemBuilder属性,这是一个工厂builder作为匿名函数进行回调,这个函数需要传入两个参数,一个是BuildContext上下文和行迭代器。对于ListView每一行都会执行这个函数调用,这里想想好像是平时Android开发中ListView创建添加item的方法。

    
    
     1//创建填充单词的ListView 2  Widget _buildNormalWorlds() { 3    //内容上下16dp 4    return new ListView.builder( 5        padding: const EdgeInsets.fromLTRB(0, 16, 0, 16), 6        //每个单词都会条约一次itemBuilder,然后将单词添加到ListTile中 7        itemBuilder: (context, i) { 8          //首先创建10条单词 9          if (i >= _normalWords.length) {10            //接着再生成10个单词,添加到列表上11            _normalWords.addAll(generateWordPairs().take(10));12          }13          return _buildItem(_normalWords[i]);14        });15  }//创建填充单词的ListView2  Widget _buildNormalWorlds(){3    //内容上下16dp4    return new ListView.builder(5        padding: const EdgeInsets.fromLTRB(0, 16, 0, 16),6        //每个单词都会条约一次itemBuilder,然后将单词添加到ListTile中7        itemBuilder: (context, i) {8          //首先创建10条单词9          if (i >= _normalWords.length) {10            //接着再生成10个单词,添加到列表上11            _normalWords.addAll(generateWordPairs().take(10));12          }13          return _buildItem(_normalWords[i]);14        });15  }
  3. 因为ListView是需要设置Item项的样式,这里同样在RandomWordsState里添加_buildItem方法,用来加载指定数据,这里指定内容居中显示:

    
    
    1  //设置每个item项的内容和样式2  Widget _buildItem(WordPair pair) {3    return new ListTile(4      title: new Text(5        pair.asPascalCase,6        textAlign: TextAlign.center,7      ),8    );9  }//设置每个item项的内容和样式2  Widget _buildItem(WordPair pair){3    return new ListTile(4      title: new Text(5        pair.asPascalCase,6        textAlign: TextAlign.center,7      ),8    );9  }
  4. 更改RandomWordsState的build方法,增加_buildNormalWords方法的调用,不是直接用单生成库,代方法如下:

    
    
     1  Widget build(BuildContext buildContext) { 2//    var wordPair = new WordPair.random();删掉 3//    return new Text(wordPair.asPascalCase);删掉 4      return new Scaffold( 5        appBar: new AppBar( 6          title: new Center(child: const Text("Weclome to Flutter")), 7        ), 8        body:_buildNormalWorlds(), 9      );10  }Widget build(BuildContext buildContext){2//    var wordPair = new WordPair.random();删掉3//    return new Text(wordPair.asPascalCase);删掉4      return new Scaffold(5        appBar: new AppBar(6          title: new Center(child: const Text("Weclome to Flutter")),7        ),8        body:_buildNormalWorlds(),9      );10  }
  5. 更改MyApp的build方法,因为标题的设置放在了RandomWordsState里了,所以不需要再额外添加,并将home变成RandomWords widget,代码一下子简洁很多如下:

    
    
     1//程序继承StatelessWidget,该应用程序成为一个widget,在Flutter中,大多数东西都是widget 2class MyApp extends StatelessWidget { 3  // 这个是应用的根widget 4  @override 5  Widget build(BuildContext context) { 6    return MaterialApp( 7        home:new RandomWordsWidget(), 8    ); 9  }10}//程序继承StatelessWidget,该应用程序成为一个widget,在Flutter中,大多数东西都是widget2class MyApp extends StatelessWidget{3  // 这个是应用的根widget4  @override5  Widget build(BuildContext context){6    return MaterialApp(7        home:new RandomWordsWidget(),8    );9  }10}

    最终效果如图所示

  6. 下面添加分割线,让UI更美观,这里通过因为计算机里循环开始都是从0开始的,我_buildNormalWorlds方法里判断是奇数项的话就构造出分割线添加到ListView,这里注意分割线也是一项,那么生成单词的时候要稍微处理下,具体代码如下:

5.添加交互


  1. 在RandomWordsState里添加一个_collected的Set集合,这个集合用来存放收藏后的单词,用Set的原因是Set本身的特性不允许元素有重复值:

1final Set<WordPair> _collected = new Set<WordPair>();Set<WordPair> _collected = new Set<WordPair>();
  1. _buildItem方法中添加isCollected来检查单词是否添加到收藏里


1final bool isCollected = _collected.contains(pair);final bool isCollected = _collected.contains(pair);
  1. _buildItem以后置属性来添加一个❤️图标到ListTiles,matrial包下有默认的❤️图标,这里就设置下颜色就可以,代码如下:


 1return new ListTile( 2      title: new Text( 3        pair.asPascalCase, 4        textAlign: TextAlign.center, 5      ), 6      //trailing 是后置图标属性 7      trailing: new Icon( 8        //material 包下 icons.dart 9        isCollected ? Icons.favorite : Icons.favorite_border,10        //图标颜色设置11        color:isCollected ? Colors.red : null,12      ),13    );return new ListTile(2      title: new Text(3        pair.asPascalCase,4        textAlign: TextAlign.center,5      ),6      //trailing 是后置图标属性7      trailing: new Icon(8        //material 包下 icons.dart9        isCollected ? Icons.favorite : Icons.favorite_border,10        //图标颜色设置11        color:isCollected ? Colors.red : null,12      ),13    );

运行结果后发现❤️型图标添加到每一行最右边处。

  1. 下面增加点击事件,当点击列表时,如果单词没被收藏,那么就添加收藏,并将心形图标改为收藏后的图标,如果单词被收藏了,那么就移除处收藏,并将心形图标变为默认的心形图标,代码如下:


 1      //trailing 是后置图标属性 2      trailing: new Icon( 3        //material 包下 icons.dart 4        isCollected ? Icons.favorite : Icons.favorite_border, 5        //图标颜色设置 6        color:isCollected ? Colors.red : null, 7      ), 8 9      //item的点击事件属性10      onTap:(){11        //状态设置12        setState(() {13          //如果收藏了14          if(isCollected){15            //那就将set集合里移除16            _collected.remove(pair);17          } else {18            //添加19            _collected.add(pair);20          }21        });2223      }//trailing 是后置图标属性2      trailing: new Icon(3        //material 包下 icons.dart4        isCollected ? Icons.favorite : Icons.favorite_border,5        //图标颜色设置6        color:isCollected ? Colors.red : null,7      ),89      //item的点击事件属性10      onTap:(){11        //状态设置12        setState(() {13          //如果收藏了14          if(isCollected){15            //那就将set集合里移除16            _collected.remove(pair);17          } else {18            //添加19            _collected.add(pair);20          }21        });2223      }

效果如下:

  1. 下面实现跳转新页面,首先添加一个收藏单词页面,在Flutter中,导航器管理应用程序的路由栈,什么是路由栈呢?路由栈就是用来管理路由(页面)。就是我现在要打开一个新的页面,那么这个新的页面就会压入路由栈中,如果我现在在新的页面,点击返回到上一个页面,那么路由栈会弹出这个新页面路由,将显示返回到前一个页面,这里想想和Android原生用栈管理页面的方法一样,首先在首页面增加跳转新页面图标,代码如下:


 1Widget build(BuildContext buildContext) { 2      return new Scaffold( 3        appBar: new AppBar( 4          title: new Center(child: const Text("Weclome to Flutter")), 5          //增加图标和点击事件动作 Icons.list是icon的类型 onPressed添加点击事件 点击会执行_collectWordsPage方法 6          actions:<Widget>[ 7            new IconButton(icon: const Icon(Icons.list),onPressed: _collectWordsPage), 8          ], 910        ),11        body:_buildNormalWorlds(),12      );13  }Widget build(BuildContext buildContext){2      return new Scaffold(3        appBar: new AppBar(4          title: new Center(child: const Text("Weclome to Flutter")),5          //增加图标和点击事件动作 Icons.list是icon的类型 onPressed添加点击事件 点击会执行_collectWordsPage方法6          actions:<Widget>[7            new IconButton(icon: const Icon(Icons.list),onPressed: _collectWordsPage),8          ],910        ),11        body:_buildNormalWorlds(),12      );13  }

实际运行后,AppBar导航栏最右边的位置添加了一个图标,这时候点击没有任何反应,因为_collectWordsPage没有任何代码,下面添加跳转到新页面的实现。

  1. 在_collectWordsPage添加Navigator.push,这个简单易懂,就是会把页面入栈


1  //跳转新页面开始2  void _collectWordsPage(){3       Navigator.of(context).push(456       );7  }//跳转新页面开始2  void _collectWordsPage(){3       Navigator.of(context).push(456       );7  }
  1. 下面添加MaterialPageRoute和builder,添加构建ListTile行的代码,并添加项与项之间的分割线,最后通过toList来转换。MaterialPageRoute是一种模态路由,可以通过平台自适应来切换屏幕,Android而言,页面推送过渡向上滑动页面,淡入淡出,弹出过渡则是向下滑动页面。IOS而言,页面从右侧滑入,反向弹出,当另一个页面进入覆盖时,该页面向左移动。


 1  //跳转新页面开始 2  void _collectWordsPage() { 3    Navigator.of(context).push( 4      new MaterialPageRoute<void>( 5        builder: (BuildContext context) { 6          //传递收藏后的单词 7          final Iterable<ListTile> tiles = _collected.map((WordPair pair) { 8            return new ListTile( 9              title: new Text(10                pair.asPascalCase, //单词11              ),12            );13          },14          );15        },1617      ),18    );19  }//跳转新页面开始2  void _collectWordsPage(){3    Navigator.of(context).push(4      new MaterialPageRoute<void>(5        builder: (BuildContext context) {6          //传递收藏后的单词7          final Iterable<ListTile> tiles = _collected.map((WordPair pair) {8            return new ListTile(9              title: new Text(10                pair.asPascalCase, //单词11              ),12            );13          },14          );15        },1617      ),18    );19  }
  1. 创建Scaffold,其中包含标题栏,body由ListView组成,每一个Item项之间有一条分割线,运行发现,新的路由会自动添加返回按钮,这是因为Navigator会在应用栏自动添加一个“返回”按钮,不需要调用Navigator.pop,点击返回按钮会返回到主界面,代码如下:


 1//跳转新页面开始 2  void _collectWordsPage() { 3    Navigator.of(context).push( 4      new MaterialPageRoute<void>( 5        builder: (BuildContext context) { 6          //传递收藏后的单词 7          final Iterable<ListTile> tiles = _collected.map((WordPair pair) { 8            return new ListTile( 9              title: new Text(10                pair.asPascalCase, //单词11              ),12            );13          },14          );15          //添加分割线开始16          final List<Widget> divided = ListTile.divideTiles(17            tiles: tiles,18            context:context,19          ).toList();20          return new Scaffold(21            appBar: new AppBar(22              title: new Center(child: const Text("Collect Words")),23            ),24            body: new ListView(children: divided),25          );26        },2728      ),29    );30  }//跳转新页面开始2  void _collectWordsPage() {3    Navigator.of(context).push(4      new MaterialPageRoute<void>(5        builder: (BuildContext context) {6          //传递收藏后的单词7          final Iterable<ListTile> tiles = _collected.map((WordPair pair) {8            return new ListTile(9              title: new Text(10                pair.asPascalCase, //单词11              ),12            );13          },14          );15          //添加分割线开始16          final List<Widget> divided = ListTile.divideTiles(17            tiles: tiles,18            context:context,19          ).toList();20          return new Scaffold(21            appBar: new AppBar(22              title: new Center(child: const Text("Collect Words")),23            ),24            body: new ListView(children: divided),25          );26        },2728      ),29    );30  }

最终运行效果发现标题并不是居中对齐,因为默认的有边距,这时候需要自定义AppBar就可以,代码如下:


 1          return new Scaffold( 2            appBar: new AppBar( 3              titleSpacing: 0.0, 4              //MaterialPageRoute这个自带了返回键 下面的属性设置取消返回键 5              automaticallyImplyLeading:false, 6              title: new Container(decoration: new BoxDecoration(color: new Color(0x00000000), 7              ), 8              child:new Stack( 9                children: <Widget>[10                  new Container(11                    //左边位置12                    alignment: Alignment.centerLeft,13                    child:new IconButton(14                      //图标样式15                      icon:new Icon(Icons.arrow_back),16                      //点击事件17                      onPressed: (){18                        Navigator.pop(context);19                      }20                    ),21                  ),22                  new Center(child:new Text("Collect Words")),23                ],24              ),)25            ),26            body: new ListView(children: divided),27          );return new Scaffold(2            appBar: new AppBar(3              titleSpacing: 0.0,4              //MaterialPageRoute这个自带了返回键 下面的属性设置取消返回键5              automaticallyImplyLeading:false,6              title: new Container(decoration: new BoxDecoration(color: new Color(0x00000000),7              ),8              child:new Stack(9                children: <Widget>[10                  new Container(11                    //左边位置12                    alignment: Alignment.centerLeft,13                    child:new IconButton(14                      //图标样式15                      icon:new Icon(Icons.arrow_back),16                      //点击事件17                      onPressed: (){18                        Navigator.pop(context);19                      }20                    ),21                  ),22                  new Center(child:new Text("Collect Words")),23                ],24              ),)25            ),26            body: new ListView(children: divided),27          );

最终运行效果如下,左边是IOS,右边是Android:

六、总结

入门Flutter的第一天,简单知道了一下几点:

  1. Flutter的特性和框架构成,它和大多数构建移动应用的工具不一样,它是用自己的渲染引擎来绘制Widget,它中间层只有C/C++代码,Flutter使用Dart语言实现系统的绝大部分功能(布局,动画,手势)。

  2. 开发环境的搭建。

  3. 体验Flutter添加UI的步骤。

  4. 路由(页面)的跳转。

若有错误,欢迎指正~

推荐阅读

Flutter学习之布局、交互、动画

Android控件人生第一站,小红书任意拖拽标签控件

扫一扫 关注我的公众号

新号希望大家能够多多支持我~

Flutter学习之入门和体验相关推荐

  1. iOS程序猿的flutter学习之路

    日常学习Flutter开发的积累 推荐一些平时自己学习Flutter开发当中接触到的优秀文章 -------------------------基础知识 ----------------------- ...

  2. flutter scrollview_简单易上手的Flutter学习指南App,2020一起来玩转Flutter吧~

    Flutter是谷歌的移动UI框架,可以快速在iOS.Android.Web和PC上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和 ...

  3. Flutter:从入门到实践

    课程内容 开篇:迎合未来主流趋势,把握新技术主动权 移动开发的前方突破口在哪里? 小团队如何面向未来做技术选型? 想要独立开发一个产品,能不能做到省心省力? 我用两个关键词来回答这些问题:跨平台.Fl ...

  4. Flutter学习总纲教程

    Flutter学习总纲教程 Flutter Widget 目录 准备 学习Flutter之前,必须要了解(不需要多么精通,但至少要了解)Dart的基础特性. Dart基础特性  ·  Dart 是 G ...

  5. Flutter 3 发布了(文末推荐一个免费的在线Flutter学习教程)

    翻译自 Tim Sneath[1] 2022年5月12日的文章 <Introducing Flutter 3>[2] 作者 :Tim Sneath 翻译 :沙漠尽头的狼(谷歌翻译加持) 链 ...

  6. Flutter学习笔记学习资料推荐

    对Flutter的学习已经有一段时间了,这里做一下总结记录,东西比较多,可能主要是一些学习资料的记录,还有一些杂七杂八的学习笔记. 文章目录 Flutter 初体验 Flutter 环境配置 Flut ...

  7. Flutter学习笔记(10)--容器组件、图片组件

    如需转载,请注明出处:Flutter学习笔记(10)--容器组件.图片组件 上一篇Flutter学习笔记(9)--组件Widget我们说到了在Flutter中一个非常重要的理念"一切皆为组件 ...

  8. Flutter 学习

    Flutter 学习 参照:https://book.flutterchina.club/ 参照:https://flutter.cn/docs/development/platform-integr ...

  9. 零基础学习嵌入式入门以及项目实战开发【手把手教+国内独家+原创】

    零基础学习嵌入式入门以及项目实战开发[手把手教+国内独家+原创] 独家拥有,绝对经典                            创 科 之 龙 嵌入式开发经典系列教程 [第一期] 主讲人: ...

最新文章

  1. 2020年ACM团队新生第一次周赛题解
  2. Android IOS WebRTC 音视频开发总结(四二)-- webrtc开发者大会
  3. 计算机网络·数据链路层.三个基本问题
  4. 热释电传感器三个引脚_Adafruit的树莓派教程:热释电传感器感知运动
  5. Spring AOP 前置通知
  6. 轻量级数据sqlite的C++调用示例
  7. Kong 1.0 GA 版本正式发布,微服务 API 网关
  8. dlibdotnet 人脸相似度源代码_使用dlib中的深度残差网络(ResNet)实现实时人脸识别 - supersayajin - 博客园...
  9. golang刷Leetcode系列 --- 实现strStr()
  10. C3P0的几种使用方法(非JNDI)
  11. 3732 Ahui Writes Word
  12. Leetcode 刷题笔记(二十二) ——贪心算法篇之进阶题目
  13. sql语句优化的一些办法
  14. [系统安全] 十二.熊猫烧香病毒IDA和OD逆向分析(上)病毒初始化
  15. 瀑布、V、W、快速原型模型、增量、螺旋模型
  16. 分享活动报名收费的微信小程序制作功能介绍_瑜伽健身房培训报名小程序开发介绍
  17. cmak(kafka Manager) 编译教程
  18. 《AngularJS深度剖析与最佳实践》一1.1 环境准备
  19. 微信公众号如何上传PPT?
  20. 键盘特殊符号输入小技巧

热门文章

  1. 微信小老虎图标怎么弄?微信小老虎状态设置方法详细步骤
  2. 如何在多可系统里设置腾讯通RTX参数
  3. android 三维软件 cad,CAD实例教程:快速设计呆萌的安卓机器人
  4. HTML学习笔记 2
  5. win7配置C语言VS2010,开发Windows7软件的绝配:Visual Studio 2010
  6. wap网站服务器要求,使您的WEB服务器支持WAP数据发送
  7. 简述数学建模的过程_数学建模的一般步骤
  8. 制作STG游戏的初步构思
  9. 微服务是去ESB总线、去中心化和分布式
  10. 单片机测钳形电流表_钳形电流表,什么是钳形电流表,钳形电流表介绍--电子百科词库--科通芯城,IC及其他电子元器件交易型电商平台100%正品保证...