Flutter探索之路–Chapter 1

HelloWorld–国际惯例

国际惯例编写Hello World

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Welcome to Flutter',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Welcome to Flutter'),
),
body: new Center(
child: new Text('Hello World'),
),
),
);
}
}

编译运行,如下:

image-20200719202914500

引用库

flutter中,我们通过import语句进行引用

import 'dart:io';
import 'package:mylib/mylib.dart';

当多个库中有冲突的名字的时候

Element element = new Element();
lib2.Element element2 = new lib2.Element();

引用库的一部分

//导入foo

import 'package:lib1/lib.dart' show foo;

//除了foo导入其他所有内容

import 'package:lib2/lib2.dart' hide foo;

开始学习–Flutter体系结构

img

框架层(Framework)

框架层是由Dart所编写的,里面包含:Material(Material Design风格的组件)、Cupertino(针对iOS风格的组件)、Widgets(组件)、Rendering(渲染)、Animation(动画)、Painting(绘制)、Gestures(手势)、Foundation(基础库)。通常开发者使用Framework层所提供的API去开发自己的项目。

引擎层(Engine)

引擎层(Engine)是由C++编写而成,里面包含Skia、Dart、Text三个部分。

  • Skia:图形渲染库
  • Dart:Dart VM虚拟机,用于编译和运行Dart代码
  • Text:文本渲染(区分平台)

开始学习–Flutter程序结构

img

新建Flutter项目的结构和原生android的工程结构不一样,我们不能用android那种多module 多lib的结构去创建module和lib,因为我们的代码都是在lib目录里面完成的,除非要用到原生交互的代码,你可以在android目录里面去写,然后在lib目录里面去引用这些交互的代码。

  • Android目录
    • 存放的是Flutter与android原生交互的一些代码,这个路径的文件和创建单独的Android项目的基本一样的
  • ios目录
    • 存放的是Flutter与ios原生交互的一些代码。
  • lib目录
    • 存放的是Dart语言编写的代码,这里是核心代码。不管是Android平台,还是ios平台,安装配置好环境,可以把dart代码运行到对应的设备或模拟器上面
  • pubspec.yaml文件
    • 配置依赖项的文件,比如配置远程pub仓库的依赖库,或者指定本地资源(图片、字体、音频、视频等)

开始学习–打包项目

创建keystore证书

创建命令:

keytool -genkey -alias flutterkey -keyalg RSA -keysize 1024 -keypass 123456 -validity 3650 -storepass 111111 -keystore D:\keytoolTest\flutterkeys.jks

命令解析:

  • -genkey: 创建一个默认的.keystore文件
  • -alias flutterkey: flutterkey就是keyAlias的值
  • -keyalg: 指定密钥加密算法,命令中使用的是RSA算法
  • -keysize: 密钥长度
  • keyPassword: 密钥密码
  • storePassword: 密钥库口令
  • storeFile: 证书存放路径

代码中配置证书

进入android目录,新建一个key.properties文件,具体填写如下:

storePassword=111111
keyPassword=123456
keyAlias=flutterkey
storeFile=D:\keytoolTest\flutterkeys.jks

在Gradle中配置签名

build.gradle中修改如下:

def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

signingConfigs中修改如下:

signingConfigs {
release {
// 读取key.properties配置文件里面的每一项
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}

buildTypes修改:

img

构建应用

  • cd flutter项目根路径(例如:cd E:/flutter_demo )。
  • 运行flutter build apk`(flutter build命令默认使用–release)。
  • release版本的APK会生成在flutter项目根路径/build/app/outputs/apk/app-release.apk

Flutter的Theme

创建主题的方式是将ThemeData提供给MaterialApp构造函数,ThemeData的主要属性如下表

属性名 类型 说明
accentColor Color 前景色(文本、按钮)
accentColorBrightness Brightness accentColor的亮度
accentIconTheme IconThemeData 与突出颜色对照的图片主题
accentTextTheme TextTheme 与突出颜色对照的文本主题
backgroundColor Color 与primaryColor对比的颜色
bottomAppBarColor Color BottomAppBar的默认颜色
brightness Brightness 应用程序整体主题亮度
buttonColor Color RaisedButtons的默认填充色
buttonTheme ButtonThemeData 按钮等控件的默认配置主题
canvasColor Color MaterialType.canvas的默认颜色
cardColor Color Material用于Card的时候的颜色
chipTheme ChipThemeData Chip的样式
dialogBackgroundColor Color Dialog的背景色
disabledColor Color 不可用状态下的颜色
dividerColor Color 分割线颜色
errorColor Color 验证错误的颜色
hashColor int 对象hash值
highlightColor Color 高亮颜色
iconTheme IconThemeData 图标主题
indicatorColor Color TabBar选中时的颜色
inputDecorationTheme InputDecorationTheme InputDecoration主题
platform TargetPlatform Widget需要适配的目标类型
primaryColor Color App主要部分的背景色
primaryColorBrightness Brightness primaryColor的亮度
primaryColorDark Color primaryColor的暗色颜色
primaryColorLight Color primaryColor的亮色颜色
primaryIconTheme IconThemeData 一个与主色对比的图片主题
primaryTextTheme TextThemeData 一个与主色对比的文本主题
scaffoldBackgroundColor Color Scaffold的默认颜色
secondaryHeaderColor Color 有选定行时PaginatedDataTable标题的颜色
selectedRowColor Color 选中行时的高亮颜色
sliderTheme SliderThemeData slider的主题
splashColor Color 墨水喷溅的颜色
slpashFactory InteractiveInkFeatureFactory InkWall和InkResponse的墨水喷溅外观
textSelectionColor Color 选中文本的颜色
textSelectionHandleColor Color 当前文本的哪个部分的句柄颜色
textTheme TextTheme 与卡片和画布对比的文本颜色
toggleableActiveColor Color 切换widget的活动状态的颜色
unselectedWidgetColor Color widget处于非活动状态下的颜色
runtimeType Type 对象的运行时类型

如果没有提供主题,Flutter会创建一个默认主题

局部主题

关于主题,如果我们希望在应用程序的一部分使用其他的主题,我们可以有两种方式创建,创建特有的主题数据或者扩展父主题

创建特有的主题

实例化一个ThemeData对象,并传递给Theme,例如

Theme(
data: ThemeData(accentColor: Colors.yellow),
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
));

扩展父主题

扩展父主题的时候,可以不需要覆盖所有的主题属性,我们可以通过copyWith方法来实现,例如

Theme(
data: Theme.of(context).copyWith(accentColor: Colors.yellow),
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
));

实战距离

我们设置应用的主题颜色为绿色,界面中间的文本背景为橙色、FloatingActionButton的背景颜色为橙色

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appName = '自定义主题学习';
return MaterialApp(
title: appName,
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.lightGreen[600],
accentColor: Colors.orange[600]),
home: MyHomePage(title: appName),
);
}
}

class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Container(
color: Theme.of(context).accentColor,
child: Text('当前文本有背景颜色', style: Theme.of(context).textTheme.title),
),
),
floatingActionButton: Theme(
data: Theme.of(context).copyWith(accentColor: Colors.grey),
child: FloatingActionButton(
onPressed: null,
child: Icon(Icons.computer),
),
),
);
}
}

运行之后效果就是这样的

image-20210103223127597.png

开始学习–Stateful和Stateless Widget

Stateless widgets 是不可变的, 这意味着它们的属性不能改变 - 所有的值都是最终的

Stateful widgets 持有的状态可能在widget生命周期中发生变化. 实现一个 stateful widget 至少需要两个类:

  • 一个 StatefulWidget类
  • 一个 State类。 StatefulWidget类本身是不变的,但是 State类在widget生命周期中始终存在

我们在通过VS Code的命令创建flutter project之后,默认生成的project就是一个有状态组件的示例,这个示例中,点击+号按钮,中间的数字就会+1。分析这个demo:

image-20210103223653452.png

StatelessWidget

StatelessWidget从字面意思上来看是一个没有状态的widget,StatelessWidget没有要管理的内部状态,如果我们的页面不依赖于Widget对象本身的配置信息和BuldContext的时候就可以用无状态组件。

StatelessWidget的声明周期只有一个:build,它只会在三种情况下被调用:

  • 将widget插入树中的时候,也就是第一次构建
  • 当widget的父级更改了其配置时,例如,Less的父类改变了text的值
  • 当它依赖的InheritedWidget发生变化时

StatefulWidget

官方Demo简单分析

代码中,MyHomePage继承自StatefulWidget类,并重写了createState方法:

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

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

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

final String title;

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

而状态类必须继承自State类,例如:

class _MyHomePageState extends State<MyHomePage>

变量以下划线(_)开头,在Dart语言中使用下划线前缀标识符,会强制其变成私有的

之后定义一个变量_counter作为计数器,通过调用setState方法来控制这个变量

int _counter = 0;

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

完整代码,可以创建工程之后阅读