一、安装项目
- 使用命令行安装项目
# test01 项目名称,可调整
flutter create test01
下载后的项目结构如下
启动android studio 模拟器
# 查看当前平台有多少种可调试的设备
flutter devices
- 启动 web 端
# -d 指定启动的平台类型(可通过 flutter devices 获取)
flutter run -d windows
- 资源编译完毕之后,自动打开了谷歌浏览器。
- 打开 lib/main.dart 文件,这个是 flutter 的入口文件。
二、实现hello world
2.1 基础代码
import 'package:flutter/material.dart';
// main 入口函数
void main() {
// runApp 是 Flutter内部提供的函数,当我们启动应用程序就是从这个函数开始
// Text 是 Flutter提供的组件,用于显示文本
// textDirection: TextDirection.ltr 表示文本从左向右显示
// style 属性表示文本的样式
runApp(
Center(
child: Text(
"Hello World!",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30,
color: Colors.orange
),
),
)
);
}
2.2 Material 设计风格
- material 是 Google 公司推行的一套设计风格,里面有非常多的设计规范,比如颜色、文字排版等。
- 在flutter中高度集成了
Material 风格的 Widget
,项目中可以直接使用这些 Widget。
2.3 实现一个完整的页面结构
import 'package:flutter/material.dart';
// main 入口函数
void main() {
// runApp 是 Flutter内部提供的函数,当我们启动应用程序就是从这个函数开始
// Text 是 Flutter提供的组件,用于显示文本
// textDirection: TextDirection.ltr 表示文本从左向右显示
// style 属性表示文本的样式
runApp(
MaterialApp(
title: "显示标题", // android 切换其他程序时显示的标题
home: Scaffold(
// 页面的标题
appBar: AppBar(
title: Text("标题"),
),
body: Center(
child: Text(
"Hello World",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30.0,
color: Colors.red,
),
),
),
),
)
);
}
2.4 创建自己的Widget
- flutter 开发中,可以继承 StatelessWidget 或者 StatefulWidget 来创建自己的Widget
- StatelessWidget,没有状态改变的Widget,通常仅仅展示工作而已。
- StatefullWidget:需要保存状态,并且可能出现状态改变的Widget
import 'package:flutter/material.dart';
// main 入口函数
void main() {
runApp(
MyApp()
);
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: "显示标题", // android 切换其他程序时显示的标题
home: Scaffold(
// 页面的标题
appBar: AppBar(title: Text("标题")),
body: ContentWidget(),
),
);
}
}
class ContentWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Center(child: TextWidget());
}
}
class TextWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Text(
"Hello World",
textDirection: TextDirection.ltr,
style: TextStyle(fontSize: 30.0, color: Colors.red),
);
}
}
2.5 小案例
import 'package:flutter/material.dart';
void main () {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
centerTitle: true,
),
body: HomeContent(),
),
);
}
}
class HomeContent extends StatelessWidget {
const HomeContent({super.key});
Widget build(BuildContext context) {
// Padding 距离周边的距离
return Padding(
padding: EdgeInsets.all(10),
// ListView 超出屏幕范围时,会自动滚动
child: ListView(
children: [
Item("app1", "product1", "https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k"),
Item("app2", "product2", "https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k"),
Item("app3", "product3", "https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k"),
],
),
);
}
}
class Item extends StatelessWidget {
final String appName;
final String productName;
final String imageUrl;
const Item(this.appName, this.productName, this.imageUrl, {super.key});
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 3),
),
child: Column(
children: <Widget>[
Text(appName, style: TextStyle(fontSize: 24)),
Text(productName, style: TextStyle(fontSize: 18)),
// 设置一个高度为10的空盒子
SizedBox(height: 10),
Image.network(imageUrl)
],
),
);
}
}
效果如下:
2.6 小案例
import 'package:flutter/material.dart';
void main () {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
centerTitle: true,
),
body: ContentWidget(),
// 显示右下角按钮
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print("ces");
}),
),
);
}
}
class ContentWidget extends StatefulWidget {
const ContentWidget({super.key});
State<StatefulWidget> createState() {
return ContentWidgetState();
}
}
class ContentWidgetState extends State<ContentWidget> {
int count = 0;
Widget build(BuildContext context) {
return Center(
child: Column(
// 居中
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 一行
Row(
// 居中
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 按钮
ElevatedButton(
onPressed: () {
// 处理按钮点击事件
setState(() {
count--;
});
},
child: Text('点击我-1'),
),
// 按钮
ElevatedButton(
onPressed: () {
// 处理按钮点击事件
setState(() {
count++;
});
},
child: Text('点击我+1'),
),
],
),
Text("当前计数:$count", style: TextStyle(fontSize: 25))
]
),
);
}
}
显示如图所示:
三、基础 widget
3.1 Text文本
3.1.1 示例
Text(
'Hello World \n 测试1 \n 测试2',
style: TextStyle(
fontSize: 24,
// Color(0xargb) 透明度a 红色r 绿色g 蓝色b
color: Color(0xffff0000),
),
textAlign: TextAlign.center, // 文字居中
maxLines: 2, // 最大行数
overflow: TextOverflow.ellipsis, // 超出显示省略号
textScaler: TextScaler.linear(2), // 文字缩放
);
3.1.2 Text.rich
Row(
// 一行中居中
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text.rich(
TextSpan(
children: [
TextSpan(text: "《Hello World》", style: TextStyle(fontSize: 30)),
TextSpan(text: "mhua", style: TextStyle(fontSize: 20, color: Colors.red)),
TextSpan(text:"\n测试数据11111111, 测试数据22222222, \n测试数据33333333, 测试数据4444444")
],
),
textAlign: TextAlign.center,
),
],
);
效果如下:
3.2 按钮
3.2.1 示例
import 'package:flutter/material.dart';
void main () {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
centerTitle: true,
),
body: ContentWidget(),
),
);
}
}
// 自定义按钮
class TestButton extends StatelessWidget {
const TestButton({super.key});
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
print('Button clicked');
},
style: ButtonStyle(
// 背景颜色
backgroundColor: WidgetStateProperty.all(Colors.blue),
// 文字颜色
foregroundColor: WidgetStateProperty.all(Colors.white)
),
child: Row(
// 宽度尽可能小的
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.add),
// 添加一个空隙
SizedBox(width: 10),
Text('Click me'),
],
),
);
}
}
class ContentWidget extends StatelessWidget {
const ContentWidget({super.key});
Widget build(BuildContext context) {
return Column(
children: [
// 自带 Material Design 风格背景色和阴影效果
ElevatedButton(
child: Text("ElevatedButton"),
// 设置圆角
style: ButtonStyle(
shape: WidgetStateProperty.all(
BeveledRectangleBorder(borderRadius: BorderRadius.circular(6))
),
),
onPressed: () {
print("ElevatedButton");
}
),
// 圆形悬浮按钮,通常用于主操作(如“新建”),支持扩展样式
FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => print("FloatingActionButton")
),
// 纯文本按钮
TextButton(
child: Text("TextButton"),
onPressed: () => print("TextButton")
),
// 带边框的按钮,默认透明背景,按下时边框高亮并显示阴影
OutlinedButton(
child: Text("OutlinedButton"),
onPressed: () => print("OutlinedButton")
),
IconButton(
icon: Icon(Icons.thumb_up),
onPressed: () {},
),
// 自定义按钮
TestButton()
]
);
}
}
效果如下:
3.3 图片
3.3.1 加载网络图片
Image.network("https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k")
3.3.2 加载本地图片
- assets目录 在根目录下
Image.asset("assets/images/1.jpg")
注意:需要在pubspec.yaml配置图片路径才可以正常使用
3.3.3 图片缩放模式
默认显示格式如下图:
BoxFit.fill
拉伸填充满空间,图片会出现变形。
Image.network(
"https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k",
fit: BoxFit.fill,
)
效果图:
BoxFit.cover
图片等比例拉伸,不会变形,多出的部分会被裁剪。
Image.network(
"https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k",
fit: BoxFit.cover,
)
效果图:
BoxFit.contain
默认值,图片不会变形,自适应父容器并且会完整显示出来。
Image.network(
"https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k",
fit: BoxFit.contain,
)
效果图:
BoxFit.fitWidth
图片宽度会填充满父容器的宽度,高度等比例缩放,图片不会变形,超过空间的部分会被裁剪。
Image.network(
"https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k",
fit: BoxFit.fitWidth,
)
效果图:
BoxFit.fitHeight
图片高度会填充满父容器的高度,宽度等比例缩放,图片不会变形,超过空间的部分会被裁剪。
Image.network(
"https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k",
fit: BoxFit.fitHeight,
)
效果图:
3.3.4 重复模式
Image.network(
"https://fastly.picsum.photos/id/866/4704/3136.jpg?hmac=I55vcxY9Vt3jgVTWqY8GjooRixzyYFPojxuMdtpIy9k",
repeat: ImageRepeat.repeatY,
)
- repeat 图片沿着
Y
和X
轴 重复 - repeatY 图片沿着
Y
轴 重复 - repeatX 图片沿着
X
轴 重复 - noRepeat 不重复,只显示一张图片
3.3.5 圆角图片
默认效果:
ClipOval
class ContentWidget extends StatelessWidget {
const ContentWidget({super.key});
Widget build(BuildContext context) {
return Center(
child: ClipOval(
child: Image.asset(
"assets/images/2.png",
width: 150,
height: 150,
)
),
);
}
}
效果图:
ClipRRect
class ContentWidget extends StatelessWidget {
const ContentWidget({super.key});
Widget build(BuildContext context) {
return Center(
child: ClipRRect(
// 设置圆角
borderRadius: BorderRadius.circular(20),
child: Image.asset(
"assets/images/2.png",
width: 150,
height: 150,
),
)
);
}
}
3.4 表单
3.4.1 TextField
class RegisterWidgetState extends State<RegisterWidget> {
final textEditingController = TextEditingController();
void initState() {
super.initState();
textEditingController.text = "设置默认值";
textEditingController.addListener(() {
print("监听值的改变 ${textEditingController.text}");
});
}
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
TextField(
decoration: InputDecoration(
icon: Icon(Icons.person),
hintText: '请输入用户名',
labelText: '用户名',
// 设置边框颜色
border: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 1
)
),
// 填充输入框背景颜色
filled: true,
fillColor: Colors.blue,
),
onChanged: (value) {
print(value);
},
controller: textEditingController,
),
],
),
)
);
}
}
效果图:
3.4.2 Form
- 实例代码
class LoginWidgetState extends State<LoginWidget> {
String username = '';
String password = '';
GlobalKey<FormState> formGlobalKey = GlobalKey();
Widget build(BuildContext context) {
return Form(
key: formGlobalKey,
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
icon: Icon(Icons.person),
),
onSaved: (value) {
username = value!;
},
autovalidateMode: AutovalidateMode.always,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入账号';
}
return null;
},
),
TextFormField(
obscureText: true,
autovalidateMode: AutovalidateMode.always,
decoration: InputDecoration(
labelText: '密码',
hintText: '请输入密码',
icon: Icon(Icons.lock),
),
onSaved: (value) {
password = value!;
},
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
return null;
},
),
Container(
// 尽可能 width 覆盖父容器
width: double.infinity,
padding: EdgeInsets.all(16.0),
child: ElevatedButton(
style: ButtonStyle(
// 背景颜色
backgroundColor: WidgetStateProperty.all(Colors.blue),
// 文字颜色
foregroundColor: WidgetStateProperty.all(Colors.white)
),
child: Text('登录', style: TextStyle(fontSize: 16.0),),
onPressed: () {
if (formGlobalKey.currentState != null) {
formGlobalKey.currentState!.save();
formGlobalKey.currentState!.validate();
print('用户名:$username, 密码:$password');
}
print('登录');
},
),
)
],
)
);
}
}
定义 Form 的key 属性
点击登录按钮触发 form 的
formGlobalKey.currentState!.save()
事件
formGlobalKey.currentState!.save()
事件会触发 TextFormField 下的 onSaved 事件,onSaved 事件,给变量赋值。
formGlobalKey.currentState!.save()
调用之后,就能获取变量值
表单校验,
formGlobalKey.currentState!.validate()
会触发 validator 校验
表单自动校验,输入文字的时候就会触发校验。