前言
Flutter实际上在我学习Android之前就已经听说过了,不过那时候的Flutter还是初始版本,并不如原生,虽说有跨平台的优势,但也只是了解而已,没有去正式使用,那么为什么又要学习了呢?
正文
因为时代又进了一步,现在流行内卷,你不卷别人,别人就要卷你。没办法!以往学习Flutter的大多数是前端转的,而现在大部分都是熟悉Android或者IOS的开发工程师在公司预算不足以招满两个端的前景下,去学习Flutter,怎么说呢?行走江湖,技多不压身,你可以不用精通,但你得会,不说别的,起码能让你在简历的技能栏上多写上一行,何乐而不为呢?
下面我们开始接触Flutter,首先你需要知道Flutter的官网地址:Flutter官网,Flutter中文官网,在学习过程中很多资料你都可以通过官网去查询,你所遇到的任何问题都能解决,只不过刚开始对你来说你需要走很多的弯路。
Flutter开发使用的是Dart语言,不用害怕,实际上并不难。目前最新的Flutter版本是3.x.x,在我刚听说Flutter的时候还是1.0版本,那个时候使用起来其实效果不是很好,缺少很多的依赖支持库,而且和原生的差距比较大,而在2.0的时候Flutter就已经有很大的优化了,这时候开发者和开源项目如雨后春笋一般猛增,而现在已经到了3.0,很多公司会直接在招人要求上写Flutter技能,为什么想必就不用我多说了。
一、Flutter SDK下载
Flutter作为跨平台的技术,可以在Windows、macOS、Linux、Chrome OS上安装,我这里使用的是Windows11,等我有钱了,我高低弄一个macOS,到时候Android Studio XCode都能配置起来。 下面先下载Flutter SD,地址:Flutter SDK,在里面可以看到最新的SDK版本。
我这里看到最新的是3.0.5,这个版本更新的还是很频繁的,点击这个3.0.5就会弹窗下载。
还挺大的,下载好之后,解压到指定的路径下,最好不要放在C盘,即使放在C盘也不要放在高级权限的路径中,比如User下。而我就直接解压在D盘中,如下图所示:
默认的文件夹是flutter,我这里改成了Flutter,看个人习惯。
二、环境变量配置
右键点击计算机图标,依次选择属性 → 高级系统设置 → 高级 → 环境变量。
环境变量有两种,一种是用户变量,一种是系统变量,我们只需要配置用户变量。点击新建按钮,输入变量名和变量值。
变量名:PUB_HOSTED_URL 变量值:https://pub.flutter-io.cn
输入完成之后点击确定,这个变量就保存了。
还有一个变量,继续点击新建。
变量名:FLUTTER_STORAGE_BASE_URL 变量值:https://storage.flutter-io.cn
如下图所示操作。
上面这里的用户环境变量配置是镜像配置,Flutter 源站在国内可能不太稳定,因此谷歌中国开发者社区(GDG)专门搭建了临时镜像,使得我们的 Flutter 命令行工具可以到该镜像站点下载所需资源。
最后我们配置SDK的路径
选中用户变量的Path,点击编辑,会弹出一个窗口,先不管它。我们进入到刚才的Flutter目录下的bin文件中。
需要将这个文件夹路径复制一下:也就是 D:\Flutter\bin,回到之前的那个窗口。
点击新建,粘贴刚才SDK路径,点击确定。然后关闭掉当前的所有窗口,重启你的电脑,记得加一个收藏,这样你重启电脑之后还能找到这篇文章,重启电脑后进入第三步。
三、Android Studio 开发环境
作为Android开发人员,你首先要确保你的Android Studio没有问题,然后才是在Android Studio上配置Flutter的开发环境,下面我们先通过命令行检测一下。Win + R回车,输入cmd,回车,输入flutter doctor,回车。
然后再往下滑动看看。
这条指令会检查电脑上的环境,Android Studio是没有问题的,连接设备也没有问题,网络也没有问题,现在我们的Android Studio还不支持Flutter的,因此我们需要支持它,打开Android Studio。File → Settings → Plugins ,输入Flutter。
可以看到Flutter实际上作为插件进行安装,我们点击Install进行安装,会弹出一个弹窗。
提示你安装Flutter之前需要安装Dart插件,因为Flutter使用的是Dart语言,因此点击Install让他去安装。
安装好之后点击Restart IDE重新启动Android Studio,让我们刚才安装的插件生效。
四、运行hello_world项目
我们之前下载的Flutter SDK里面有一个examples文件夹,里面是一些flutter项目,这些项目有什么作用呢?首先是让你运行来检测本地的Flutter环境配置,然后就是可以让你快速的了解Flutter。
我们通过Android Studio打开hello_world。点击Flie → Open。
点击OK。
点击Trust Project。
这就是我们的Flutter项目了,目前正在下载配置的内容,请稍等。发现项目有错误,我们打开lib下的main.dart
这里提示你Dart SDK 没有配置,而其实我们下载Flutter SDK里面就带了Dart的SDK,因此我们先配置Flutter SDK,在Android Studio中配置Flutter的SDK,如图所示。
配置好之后点击Apply按钮,再点击OK关闭这个窗口,你会看到当前的hello_world项目会再编译一次,我们再看main.dart。
点击Upgrade dependencies,更新依赖,更新完成之后,当前的main.dart中的内容就不会报错了。
然后要运行起来就需要连接真机或者启动虚拟机了。启动模拟器之后我们发现了一个问题。
项目中似乎没有识别到这个模拟设备,这个时候要看是不是模拟器有问题,于是我打开Andoid项目,发现模拟机是可以识别到,那么问题就出来Flutter上,所以我们要为Flutter配置Android 的Sdk路径,关闭Android Studio,找到Android Sdk路径,我的Android Sdk所在路径是:D:\Android\Sdk,然后我们Win + R 弹窗,输入cmd,然后进入命令窗口,输入如下指令:
flutter config --android-sdk D:\Android\Sdk
回车
配置好之后,这里会提示你重启编辑器,也就是Android Studio,我们重启Android Studio。再启动模拟器。
找到了设备。
好了,下面运行一下。
看日志好像和这个FlutterActivity有关,我们可以在AndroidManifest.xml中看到有注册这个FlutterActivity
Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present
这里看这句话,定位 S+(版本 31 及更高版本)要求在存在意图过滤器时定义 android:exported 的显式值,因为我们的虚拟机是Android 12,而在Android12中,注册Activity时要加上android:exported属性,一般启动的设置为ture,其他设置为false,那么我们设置一下看看。
再运行一下
运行起来了,下面我们再用真机运行试试看,通过USB数据线连接真机。
运行:
也可以运行成功。
通过运行sdk中自带的项目我们解决了一些问题,同时还发现这个项目比较老旧了,没有做更新,它里面还是基于Android 10去写的,Android11上运行应该没有问题,而到了Android12上就不行了,好了,不研究它的demo了,下面我们要自己创建一个Flutter项目。
五、创建Flutter项目
点击File → New → New Flutter Project。
选择Flutter,点击Next。
创建一个HelloWrold项目。
这里的项目名称必须以小写,下划线格式进行命名,让我觉得有一些不舒服,这里我修改了项目的存放路径,然后默认选择Android和iOS平台,语言使用Kotlin 和Swift,点击Finish。
项目创建完成,如下图所示:
创建完成之后我们直接运行这个项目在模拟器或者真机上。
这是一个计数器,点击右下角的浮动按钮,屏幕中间的数字会加1。
六、Flutter工程结构
现在工程已经运行起来了,对于一个新的项目工程,我们需要大概的之后它的结构内容,各个目录代表什么意思。
首先我们来看一下重点内容项目的目录。
.dart_tool
这是一个dart工具文件夹,里面包含了flutter工程的构建信息,里面还有一个version文件,说明当前使用的flutter的版本,无需什么改动,了解就好。
.idea
因为Android Studio 是由IDEA编辑器改过来的,因此会在创建项目时生成一个.idea文件夹,根据创建项目类型不同,它里面的内容就会不同,了解就好。
android
Android的项目文件,作为Android开发者,想必你肯定知道这个android文件夹中的各个文件代表什么意思。
ios
ios的项目文件,作为Android开发者,我不知道里面怎么操作的也很正常,嗯,暂时我们不考虑ios的问题。
lib
这是Flutter应用源文件,里面有一个main.dart是程序入口文件,我们运行看到的第一个页面就在这里面,稍后会详细讲述这个main.dart文件。
test
测试文件
.gitignore
git忽略文件,就是这里面的文件在提交git时会忽略掉,一般来说就是一些编译时文件,例如build之类的。
.metadata
用来记录Flutter 项目属性的的隐藏文件。
.packages
用来记录Flutter项目的包信息。
analysis_options.yaml
静态分析文件。
hello_world.iml
工程配置文件。
pubspec.lock
记录当前项目实际依赖信息的文件。
pubspec.yaml
管理第三方库及资源的配置文件。
README.md
项目描述文件。
基本的内容就说完了,这样看起来实际上Flutter工程就是一个同时内嵌了 Android 和 iOS 原生子工程的父工程,我们在 lib 目录下进行 Flutter 代码的开发,而某些特殊场景下的原生功能,则在对应的 Android 和 iOS 工程中提供相应的代码实现,供对应的 Flutter 代码引用。
Flutter 会将相关的依赖和构建产物注入这两个子工程,最终集成到各自的项目中。而我们开发的 Flutter 代码,最终则会以原生工程的形式运行。
七、Flutter开发核心思想
我们运行程序之后发现是一个计数器Demo,在这个简单示例中,从基础的组件、布局到手势的监听,再到状态的改变,Flutter 最核心的思想在这 60 余行代码中展现得可谓淋漓尽致,也就是这个lib下的main.dart文件。
我们来了解一下它在这里面做了什么?首先我们看一下main.dart中的代码:
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
这个main.dart里面有很多的注释,我这里是把注释去掉了,你可以自己去了解它们的意思,从而了解这些代码是干什么的。
我们从上往下来看
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); }
这里首先我们知道导了一个包,material是一个材料设计库,作为Android开发者你不会陌生,这说明这个页面是按照material风格设置的,然后是一个main()函数,里面通过runApp()函数执行一个app部件,也就是Widget,这里的部件就是MyApp()函数,然后我们再看MyApp()做了什么?
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } }
这里我们的MyApp是一个类,继承了StatelessWidget,这是一个无状态部件,然后实现构造方法,构造方法里面通过MaterialApp()函数定义风格,然后是标题、主题和主页面信息,这里主页面home中调用MyHomePage()函数,也就是我们当前页面所显示的内容。这里有一个Colors.blue,你试一下改成red,或者green。如果你这时候项目是运行在模拟器上或者真机上的话,你可以修改后Ctrl + S 进行保存。
然后就会直接将你刚才改动的渲染到设备上,这叫热重载,这是Flutter中很方便的一个功能,还有一点就是,你注意到模拟器上方这个黄色的闪电图标没有。
你改动后,点击它和Ctrl + S效果是一样的。比如我们再改成green,然后点击这个闪电图标。
然后我们继续往下看。
class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); }
这里MyHomePage继承StatefulWidget,这是一个有状态的部件,这里就需要一个状态了,通过createState()得到一个_MyHomePageState,这个_MyHomePageState()就是这个页面的主要内容了,它里面是
class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
这里_MyHomePageState 继承了State< MyHomePage >,创建一个_counter 变量,默认是0,我们运行时看到的就是0,通过_incrementCounter()函数调用setState(),再这里面进行_counter自增,再往下看就是build()构造函数,在 build 方法中,我们通常通过对基础 Widget 进行相应的 UI 配置,或是组合各类基础 Widget 的方式进行 UI 的定制化。
这里返回一个Scaffold,这是一个脚手架,用来构建页面,如果你有过Compose的开发经验,那么你会觉得熟悉,当然了Compose其实就是向Flutter看齐,只不过Compose是在Android原生中进行声明式UI设计,简化页面,大体思路和Flutter差不多。
然后我们看Scaffold中的内容,AppBar 是页面的导航栏,我们直接将 MyHomePage 中的 title 属性作为标题使用。body 则是一个 Text 组件,显示了一个根据 _counter 属性可变的文本:‘You have pushed the button this many times:$_counter’。floatingActionButton,则是页面右下角的带“+”的悬浮按钮。我们将 _incrementCounter 作为其点击处理函数。
这里主要的内容是值的变化和浮动按钮的点击,也就是$_counter进行赋值,onPressed表示浮动按钮按下,按下后会执行_incrementCounter,然后调用setState函数,setState 函数是 Flutter 以数据驱动视图更新的关键函数,它会通知 Flutter 框架,因为它里面_counter++,所以数据发生变化,通过刷新界面。而 Flutter 框架收到通知后,会执行 Widget 的 build 方法,根据新的状态重新构建界面。
这个思路其实你可以类比DataBinding,页面和数据相关,但是它是单向的,通过状态来控制页面UI改变,这就是显示比较新的一个框架了,MVI框架。
如果你深刻的理解了这个实例,那么你就知道了Flutter的核心思想,不要去计较某一个API的使用方式,陷在里面就得不偿失了,从全局出发。
尾声
开始Flutter的学习之旅,你是否感觉到兴奋呢?