Flutter横跨Android、iOS、MacOS、Windows、Linux等多个系统,还可以打包成Web程序运行在浏览器上。
1 环境搭建
- 虚拟机下Win10企业版(Win10-1909-Business-Editions-DVD-x64)、Chrome。
- 下载安装Flutter依赖的命令行工具Git For Windows:Git-2.37.1-64-bit.exe。
- Flutter SDK下载:Flutter-Windows-3.0.5-Stable.zip。
- 解压到指定位置,找到文件夹中的flutter_console.bat文件,双击运行。
- 注意:不要将Flutter SDK安装到高权限路径下,例如C盘路径(系统盘)。
- 添加环境变量:控制面板—>用户账户—>更改我的环境变量—>环境变量。
- 新建变量名
PUB_HOSTED_URL
:变量值https://pub.flutter-io.cn/
。 - 新建变量名
FLUTTER_STORAGE_BASE_URL
:变量值https://storage.flutter-io.cn/
。 - 变量Path中添加:变量值
C:\Program\Flutter\bin
(Flutter SDK安装路径的bin目录)。
- 新建变量名
1-1 编辑器的安装
- 安装Visual Studio:VisualStudioSetup.exe,使用C++的桌面工具。
- 勾选
用于Windows的C++ CMake工具
。 - 勾选
Windows 10 SDK(10.0.19041.0)
。 - 勾选
MSVC v143-VS 2022 C++ x64/86生成工具(最新)
。
- 勾选
- 安装Android Studio:Android-Studio-2021.2.1.15-Windows.exe,可能需要使用梯子。
- 添加环境变量
ANDROID_SDK_HOME
:变量值C:\Program\Avd
(即模拟器存放的位置)。 - 设置Android模拟器:虚拟机貌似无法启动模拟器,建议将环境搭建在Pc主机上操作。
- Android Studio—>More Actions—>Virtual Device Manager—>Create device。
- Phone·Pixel—>Next—>镜像 R—>Download—>Next—>验证配置—>Finish。
- 安装插件:Android Studio—>Plugins—>Marketplace—>搜Flutter和Dart—>Install。
- Flutter插件:支持Flutter开发工作流(运行、调试、热重载等)。
- Dart插件:提供代码分析(输入代码时进行验证、代码补全等)。
- 添加环境变量
1-2 flutter doctor
- 文件夹下鼠标右键
Git Bash Here
,命令窗口模式输入flutter doctor
进行查看。 cmdline-tools component is missing
:Android Studio缺少cmdline-tools工具。- 启动Android Studio—>More Actions—>SDK Manager—>SDK Tools。
- Android SDK Command-line Tools (latest)—>Apply—>Finish—>OK。
Android license status unknown.
:说明该环境需要添加Android license。- 搜索
cmd
,以管理员身份运行,命令窗口下执行flutter doctor --android-licenses
。 - 在提示是否接受许可时,输入
y
进行回车确认,最后重新输入flutter doctor
进行查看。
- 搜索
$ flutter doctor
Flutter assets will be downloaded from https://storage.flutter-io.cn/.
Make sure you trust this source!
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter
(Channel stable, 3.0.5, on Microsoft Windows [版本 10.0.18363.418], locale zh-CN)
[!] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
X cmdline-tools component is missing
Run `path/to/sdkmanager --install "cmdline-tools;latest"`
See https://developer.android.com/studio/command-line for more details.
X Android license status unknown.
Run `flutter doctor --android-licenses` to accept the SDK licenses.
See https://flutter.dev/docs/get-started/install/windows
# android-setup for more details.
[√] Chrome - develop for the web
[√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.3.0)
[√] Android Studio (version 2021.2)
[√] Connected device (2 available)
[√] HTTP Host Availability
! Doctor found issues in 1 categories.
2 创建项目
- 创建项目
- 启动Android Studio—>New Flutter Project—>Flutter—>Flutter SDK path即Flutter SDK的安装路径。
- Next—>Project Name为
myflutter
(Organization包名)—>Finish—>Create—>等待打开创建的项目。
- 想要在该项目上运行程序:File—>Open—>定位到该项目目录的Android目录—>选中—>OK—>将其打开。
- Sync project with Gradle Files下载:在项目打开Android目录时便自动下载(位于Android Studio->File中)。
- No Device Selected:找不到运行设备,两种调试方式(命令窗口下
flutter devices
可检测当前可用设备)。- Android真机调试
- 手机使用USB数据线连接电脑,开启调试模式,Android Studio安装与手机对应的SDK版本。
- Android Studio—>Tools—>SDK Manager—>SDK Platforms—>勾选对应版本SDK—>OK。
- 当No Device Selected变为对应手机型号名称时,Run—>Run‘app’即可在手机中运行程序。
- 模拟器调试:Android Studio—>Tools—>Device Manager—>启动之前设置好的Android模拟器。
- Android真机调试
- 多台设备同时进行兼容性调试,命令窗口进入对应项目
myflutter
中,输入flutter run -d all
执行程序。 - 改完代码之后,不需要重新输入命令执行程序,以下通用快捷键可帮助日常开发。
- 使用热加载
Shift + R
或r
,重新加载程序。 - 若热加载没生效,则使用命令
R
进行热重启。 - 命令
p
显示网格,可以很好地掌握布局情况。 - 命令
o
支持切换Android以及iOS的预览模式。 - 命令
q
退出调试预览模式,相当于Ctrl + C
。
- 使用热加载
3 简单介绍
- 创建Flutter项目的两种方式:Android Studio(正式项目建议用)、命令
flutter create <project-name>
。 - 目录结构
- test:用于存放测试代码。
- android、ios、web、windows等:各个平台的资源文件。
- lib:flutter相关代码,开发的所有代码都放在该目录下。
- pubspec.yaml:存放项目第三方依赖、版本号和配置信息等。
- analysis_options.yaml:分析Dart语法的文件,老项目升级新项目时如果有警告信息,可以删除此文件。
3-1 入口方法
import 'package:flutter/material.dart'; // 引入material主题
void main() { // 入口方法
runApp(const Center( // Center组件,让内容居中
child: Text('Hello Flutter!', // 查看底层代码使用“Ctrl+鼠标左键”
textDirection: TextDirection.ltr, // ltr从右向左
style: TextStyle( // 改变文本样式
// color: Colors.red,
color: Color.fromRGBO(80, 120, 35, 1),
fontSize: 27,
),
),
),);
}
3-2 必须组件
- 开发中必须使用的装饰组件:MaterialApp(一般作为顶层Widget使用)、Scaffold(布局结构的基本实现)。
- MaterialApp组件的常用属性:home(主页)、title(标题)、color(颜色)、theme(主题)、routes(路由)等。
- Scaffold组件的常用属性
- appBar:显示在界面顶部的标题栏。
- body:当前界面所显示的主要内容。
- drawer:抽屉菜单控件,例如左拉、右拉、弹出对话框等。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('必须组件')),
body: const Center(
child: Text('Hello Flutter!',
textDirection: TextDirection.ltr,
style: TextStyle(
color: Colors.red,
// color: Color.fromRGBO(80, 120, 35, 1),
fontSize: 27,
),
),
),
),
),);
}
3-3 内容抽离
- Flutter自定义组件即一个类,需集成StatelessWidget或StatefulWidget。
- StatelessWidget是无状态组件,状态不可变的Widget,前期接触比较多。
- StatefulWidget是有状态组件,持有的状态可能在Widget生命周期中改变。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('内容抽离')),
body: const MyApp(), // 调用MyApp组件
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key); // 常量构造函数
Widget build(BuildContext context) {
return const Center(
child: Text('Flutter自定义组件',
textDirection: TextDirection.ltr,
style: TextStyle(
// color: Colors.red,
color: Color.fromRGBO(80, 120, 35, 1),
fontSize: 27,
),
),
);
}
}
4 基本结构
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('基本结构')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return const Center(
child: Text('Hello Flutter!'),
);
}
}
4-1 容器组件
- 容器组件:Container,与HTML中的div标签相似,主要用于布局。
- decoration(装饰容器,例如:给容器添加背景或边框等)。
- margin(和容器外部的间距)、padding(和容器内部的间距)。
- width(容器宽度)、height(容器高度)、child(容器子元素)。
- alignment(调整容器中组件的位置)、transform(让容器进行位移、缩放、旋转等)。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('容器组件')),
// body: const MyApp(),
body: Column(
children: const [
MyApp(),
MyButton(),
],
),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Center(
child: Container(
// alignment: Alignment.topLeft, // 配置容器内元素方位,center居中
alignment: Alignment.center, // topLeft左上,bottomRight右下
margin: const EdgeInsets.fromLTRB(0, 60, 0, 0), // 配置容器外部间距,设置顶部高度
width: 270, // 配置容器宽度
height: 270, // 配置容器高度
transform: Matrix4.translationValues(0, 3, 0), // 沿着y轴向下平移3
// transform: Matrix4.rotationZ(0.2), // 沿着Z轴旋转0.2
// transform: Matrix4.skewY(10), // 沿着Y轴倾斜缩放10
decoration: BoxDecoration(
color: Colors.green, // 配置容器的背景颜色
border: Border.all( // 配置边框颜色和宽度
color: Colors.purple,
width: 3,
),
borderRadius: BorderRadius.circular(25), // 配置容器圆角,100可实现圆形
boxShadow: const [ // 配置容器阴影效果
BoxShadow(
color: Colors.blue,
blurRadius: 30.0,
),
],
gradient: const LinearGradient( // 配置容器渐变颜色
colors: [ // LinearGradient背景线性渐变
Colors.green, // RadialGradient径向渐变
Colors.yellow,
],
),
),
child: const Text('容器组件',
style: TextStyle(
color: Colors.white,
fontSize: 66,
),
),
),
);
}
}
class MyButton extends StatelessWidget { // 创建一个自定义的按钮
const MyButton({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
width: 200,
height: 50,
// margin: const EdgeInsets.all(20), // 配置MyApp与MyButton边距
margin: const EdgeInsets.fromLTRB(0, 60, 0, 0),
// padding: const EdgeInsets.all(5), // 可替换alignment
// padding: const EdgeInsets.fromLTRB(70, 0, 0, 0),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20), // 配置按钮圆角
),
child: const Text('按钮',
style: TextStyle(
color: Colors.white,
fontSize: 30,
),
),
);
}
}
4-2 文本组件
- style(文本样式,参数如下)、maxLines(文本显示的最大行数)。
- color(文字颜色)、fontWeight(字体粗细:bold粗体、normal正常体)。
- fontSize(文字大小)、fontStyle(文字样式:italic斜体、normal正常体)。
- wordSpacing(单词间隙:负值则单词变紧凑)、letterSpacing(字母间隙:负值则字母变紧凑)。
- decoration(文字装饰线:none无线、lineThrough删除线、overline上划线、underline下划线)。
- decorationColor(文字装饰线颜色:red红、orange橙、yellow黄、green绿、cyan青、blue蓝、purple紫)。
- decorationStyle(文字装饰线风格:[dashed/dotted]虚线、double两根线、solid一根实线、wavy波浪线)。
- overflow(文本超出屏幕后的处理方式:clip裁剪、fade渐渐隐藏、ellipsis省略号)。
- textAlign(文本对齐方式:center居中、left左对齐、right右对齐、justfy两端对齐)。
- textScaleFactor(字体缩放)、textDirection(文本方向:ltr从左至右、rtl从右至左)。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('文本组件')),
body: const MyText(),
),
),);
}
class MyText extends StatelessWidget {
const MyText({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Container(
width: 200,
height: 200,
margin: const EdgeInsets.all(100),
decoration: const BoxDecoration(
color: Colors.yellow,
),
child: const Text('纸上得来终觉浅,绝知此事要躬行。—— 冬夜读书示子聿 ▪ 陆游',
textAlign: TextAlign.left, // 文本对齐方式
overflow: TextOverflow.ellipsis, // 溢出显示隐藏点
maxLines: 1, // 文本显示最大行数
style: TextStyle(
fontSize: 20, // 字体大小20
fontWeight: FontWeight.w900, // 字体加粗900
color: Colors.red, // 字体颜色
fontStyle: FontStyle.italic, // 字体倾斜
letterSpacing: 2, // 字体间距
decoration: TextDecoration.underline, // 下划线
decorationColor: Colors.blue, // 下划线黑色
decorationStyle: TextDecorationStyle.wavy, // 波浪线
),
),
);
}
}
4-3 图片组件
- Image.network:加载远程图片。
- alignment(图片对齐方式)、fit(依父容器控制图片的拉伸)、repeat(横纵或整个画布平铺)。
- color和colorBlendMode(图片的背景颜色,通常两者配合使用,即图片颜色与背景色混合)。
- width(图片宽度,结合ClipOval才能看到效果)、height(高度,结合ClipOval才能看到效果)。
- Image.asset:加载本地图片,需在项目的根目录中新建images文件夹(放置本地图片),并修改配置文件。
# 例如:myflutter(项目名)目录结构如下
# - images/
# - a.jpg
# - 2.0x/
# - b.jpg
# - 3.0x/
# - c.jpg
# - pubspec.yaml
# 修改pubspec.yaml配置文件内容
assets:
- images/a.jpg
- images/2.0x/b.jpg
- images/3.0x/c.jpg
(1) 加载远程图片
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('加载远程图片')),
// body: const MyApp(),
body: Column(
children: const [
MyApp(),
SizedBox(height: 5), // 设置两容器间距
MyCircle(),
],
),
),
),);
}
/*
图片地址:http://gg.gg/13jnaa、http://gg.gg/13kuon、http://gg.gg/13kuow
*/
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Center(
child: Container(
margin: const EdgeInsets.fromLTRB(0, 5, 0, 0),
height: 300,
width: 200,
decoration: const BoxDecoration(
color: Colors.yellow,
),
child: Image.network(
'http://gg.gg/13jnaa',
// alignment: Alignment.centerLeft, // 图片向左居中
// scale: 2, // 图片缩小一倍
// fit: BoxFit.fill, // 图片充满容器,可能会压缩变形
fit: BoxFit.cover, // 图片充满容器,自动裁剪成容器大小
// fit: BoxFit.fitWidth, // 宽度裁剪
// fit: BoxFit.fitHeight, // 高度裁剪
// repeat: ImageRepeat.repeatX, // X轴平铺
// repeat: ImageRepeat.repeatY, // Y轴平铺
// repeat: ImageRepeat.repeat, // X和Y轴都平铺
),
),
);
}
}
class MyCircle extends StatelessWidget { // 实现椭圆图片
const MyCircle({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Container(
height: 288,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.circular(100),
image: const DecorationImage( // 背景图片
image: NetworkImage('http://gg.gg/13kuon'),
fit: BoxFit.cover,
// fit: BoxFit.fill, // 图片会变形
),
),
);
}
}
(2) 实现圆形图片
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('实现圆形图片')),
body: const ClipImage(),
),
),);
}
/*
图片地址:http://gg.gg/13jnaa、http://gg.gg/13kuon、http://gg.gg/13kuow
*/
class ClipImage extends StatelessWidget {
const ClipImage({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Center(
child: ClipOval(
child: Image.network(
'http://gg.gg/13kuow',
width: 300,
height: 300,
fit: BoxFit.cover,
),
),
);
}
}
(3) 加载本地图片
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('加载本地图片')),
body: const LocalImage(),
),
),);
}
class LocalImage extends StatelessWidget {
const LocalImage({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.fromLTRB(50, 50, 50, 50),
height: 500,
width: 300,
decoration: const BoxDecoration(
color: Colors.yellow,
),
child: Image.asset(
'images/a.jpg',
// 'images/2.0x/b.jpg',
// 'images/3.0x/c.jpg',
// fit: BoxFit.cover,
),
);
}
}
4-4 图标组件
- 官方Icons图标(需要梯子才能访问):https://fonts.google.com/icons。
- 自定义图标:阿里巴巴矢量图标库,需要注册账号。
- 下载自定义图标所对应的
.ttf
文件,将其复制粘贴到项目根目录的新建文件夹fonts中。 - 例如在“阿里巴巴矢量图标库”中搜索想要的图标,加入购物车,购物车页点下载代码。
- 解压下载的压缩包,复制其中的
.ttf
文件粘贴到fonts中,并修改pubspec.yaml配置文件。
- 下载自定义图标所对应的
# 例如:myflutter(项目名)目录结构如下
# - fonts/
# - iconfont.ttf
# - lib/
# - icon.dart
# - main.dart
# - pubspec.yaml
# 修改pubspec.yaml配置文件内容,family可依据需求自定义字体,并在lib目录中新建icon.dart文件
fonts:
- family: Schyler
fonts:
- asset: fonts/iconfont.ttf
(1) main.dart
import './icon.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('图标组件')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Column(
children: const [
SizedBox(height: 20),
Icon(Icons.home, size: 60, color: Colors.red), // 官方图标
SizedBox(height: 20),
Icon(Icons.shop, size: 60, color: Colors.deepPurple),
SizedBox(height: 20),
Icon(icon.book, size: 60, color: Colors.lightBlueAccent),
SizedBox(height: 20),
Icon(icon.wechat, size: 60, color: Colors.green),
SizedBox(height: 20),
Icon(icon.cart, size: 60, color: Colors.cyan), // 自定义图标
],
);
}
}
(2) icon.dart
import 'package:flutter/material.dart';
class icon {
static const IconData book = IconData(
0X3447,
// code对应编码,解压的json文件中对应图标的unicode值
// pubspec.yaml配置文件中,family依据需求自定义的字体
fontFamily: 'Schyler',
matchTextDirection: true,
);
static const IconData wechat = IconData(
0Xe637,
fontFamily: 'Schyler',
matchTextDirection: true,
);
static const IconData cart = IconData(
0Xf0179,
fontFamily: 'Schyler',
matchTextDirection: true,
);
}
4-5 列表组件
- 列表布局是开发中最常用的一种布局,通过ListView来定义列表项,支持垂直和水平方向展示。
- 通过一个属性就可以控制列表的显示方向,其中列表又分成了垂直列表、水平列表和动态列表。
- 常用参数
- padding(内边距)、resolve(组件反向排序)、children(列表元素)。
- scrollDirection(Axis.vertical垂直列表、Axis.horizontal水平列表)。
(1) 垂直列表
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('垂直列表')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ListView( // 可上下滑动页面进行查看
children: const <Widget>[
ListTile(
leading: Icon(Icons.home, color: Colors.lightBlue),
title: Text('首页'),
),
Divider(), // 水平分隔线
ListTile(
leading: Icon(Icons.assignment, color: Colors.green),
title: Text('订单'),
trailing: Icon(Icons.chevron_right_sharp), // 右侧显示进入页面的“>”图标
),
Divider(),
ListTile(
leading: Icon(Icons.payment, color: Colors.orange),
title: Text('待付'),
trailing: Icon(Icons.chevron_right_sharp),
),
Divider(),
ListTile(
leading: Icon(Icons.favorite, color: Colors.red),
title: Text('收藏'),
trailing: Icon(Icons.chevron_right_sharp),
),
Divider(),
ListTile(
leading: Icon(Icons.people, color: Colors.blueGrey),
title: Text('客服'),
trailing: Icon(Icons.chevron_right_sharp),
),
Divider(),
],
);
}
}
✧ 图文列表
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('图文列表')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(10),
children: <Widget>[
ListTile(
leading: Image.network('http://www.m58.link/wiIfq'),
title: const Text('博美'),
subtitle: const Text('风度翩翩,待人友好,不挑衅,外貌也特别有魅力。'),
),
const Divider(),
ListTile(
title: const Text('雪纳瑞'),
subtitle: const Text('个性勇敢,不掉毛,聪明独立更适合办公室驯养。'),
trailing: Image.network('http://www.m58.link/ahkrp'),
),
const Divider(),
ListTile(
leading: Image.network('http://www.m58.link/tnhix'),
title: const Text('柴犬'),
subtitle: const Text('聪明,对主人看护,身体干净。'),
),
const Divider(),
ListTile(
title: const Text('拉布拉多'),
subtitle: const Text('性格比较温和,对主人忠心耿耿,外貌憨厚,比较容易驯养。'),
trailing: Image.network('http://www.m58.link/tqVLU'),
),
const Divider(),
ListTile(
leading: Image.network('http://www.m58.link/trjsD'),
title: const Text('德牧'),
subtitle: const Text('IQ高,较好训练,忠诚,善于整理头发。'),
),
const Divider(),
ListTile(
title: const Text('金毛'),
subtitle: const Text('大暖男,待人友好,性情温和;快速接受指令,适合于工作犬。'),
trailing: Image.network('http://www.m58.link/WsKFq'),
),
const Divider(),
ListTile(
leading: Image.network('http://www.m58.link/MnMuc'),
title: const Text('哈士奇'),
subtitle: const Text('长得真漂亮,走路表情包,喂食比较少。'),
),
const Divider(),
],
);
}
}
✧ 图片展示
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('图片展示')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(10),
children: <Widget>[
Image.network('http://www.m58.link/jFcvO'),
Container(
padding: const EdgeInsets.fromLTRB(0, 1, 0, 0),
height: 30,
child: const Text(
'大白斑蝶',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
),
),
Image.network('http://www.m58.link/qmwjR'),
Container(
padding: const EdgeInsets.fromLTRB(0, 1, 0, 0),
height: 30,
child: const Text(
'玉带锦蛇蝶',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
),
),
Image.network('http://www.m58.link/oFHvn'),
Container(
padding: const EdgeInsets.fromLTRB(0, 1, 0, 0),
height: 30,
child: const Text(
'蓝凤蝶',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
),
),
],
);
}
}
(2) 水平列表
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('水平列表')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return SizedBox(
height: 150, // 与ListView同层可自定义列表高度
child: ListView(
scrollDirection: Axis.horizontal, // 水平列表
padding: const EdgeInsets.all(10),
children: <Widget>[
Container(
height: 150, // 加horizontal则高度自适应
width: 150, // 不加则宽度自适应,设置无效
decoration: const BoxDecoration(color: Colors.lightBlue),
child: Column(
children: [
SizedBox(
height: 110,
child: Image.network('http://www.m58.link/uHlvA', fit: BoxFit.cover),
),
const Text('上海'),
],
),
),
Container(
width: 150,
decoration: const BoxDecoration(color: Colors.lightBlue),
child: Column(
children: [
SizedBox(
height: 110,
child: Image.network('http://www.m58.link/ifTZY', fit: BoxFit.cover),
),
const Text('广州'),
],
),
),
Container(
width: 150,
decoration: const BoxDecoration(color: Colors.lightBlue),
child: Column(
children: [
SizedBox(
height: 110,
child: Image.network('http://www.m58.link/NcAie', fit: BoxFit.cover),
),
const Text('北京'),
],
),
),
Container(
width: 150,
decoration: const BoxDecoration(color: Colors.lightBlue),
child: Column(
children: [
SizedBox(
height: 110,
child: Image.network('http://www.m58.link/rlDUS', fit: BoxFit.cover),
),
const Text('重庆'),
],
),
),
Container(
width: 150,
decoration: const BoxDecoration(color: Colors.lightBlue),
child: Column(
children: [
SizedBox(
height: 110,
child: Image.network('http://www.m58.link/dtQwt', fit: BoxFit.cover),
),
const Text('南京'),
],
),
),
],
),
);
}
}
(3) 动态列表
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('动态列表')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
List<Widget> _initListData() { // 自定义方法
List<Widget> list=[];
for(var i=0; i<=7; i++) {
list.add(ListTile(title: Text('自定义方法---$i')));
}
return list;
}
Widget build(BuildContext context) {
return ListView(
children: _initListData(), // 调用自定义方法
);
}
}
✧ mydata.dart
// 例如:myflutter(项目名)目录结构如下
// - lib/
// - res/
// - mydata.dart
// - main.dart
// - icon.dart
List listData=[
{
"title": 'Fly Word',
"author": 'Isla',
"imageUrl": 'http://www.m58.link/GxYmQ',
},
{
"title": 'Ten Clock',
"author": 'Olivia',
"imageUrl": 'http://www.m58.link/TrLBS',
},
{
"title": 'Candy Shop',
"author": 'Daniel',
"imageUrl": 'http://www.m58.link/ZqsJH',
},
{
"title": 'Roman Holiday',
"author": 'Benjamin',
"imageUrl": 'http://www.m58.link/wTiWA',
},
{
"title": 'Late Autumn Bread',
"author": 'Samuel',
"imageUrl": 'http://www.m58.link/erxaA',
},
];
✧ for动态循环
import './res/mydata.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('for动态循环')),
body: MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
MyApp({Key? key}): super(key: key) {
print(listData);
}
List<Widget> _initListData() {
List<Widget> tempList=[];
for(var i=0; i<listData.length; i++) {
tempList.add(
ListTile(
leading: Image.network('${listData[i]["imageUrl"]}'),
title: Text('${listData[i]["title"]}'),
subtitle: Text('${listData[i]["author"]}'),
),
);
}
return tempList;
}
Widget build(BuildContext context) {
return ListView(
children: _initListData(),
);
}
}
✧ map动态循环
import './res/mydata.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('map动态循环')),
body: MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
MyApp({Key? key}): super(key: key) {
print(listData);
}
List<Widget> _initListData() {
var tempList=listData.map((value) {
return ListTile(
leading: Image.network('${value["imageUrl"]}'),
title: Text('${value["title"]}'),
subtitle: Text('${value["author"]}'),
);
});
return tempList.toList();
}
Widget build(BuildContext context) {
return ListView(
children: _initListData(),
);
}
}
✧ builder动态循环
import './res/mydata.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('builder动态循环')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView.builder( // 构造函数
itemCount: listData.length,
itemBuilder: (context, i) {
return ListTile(
leading: Image.network(listData[i]['imageUrl']),
title: Text(listData[i]['title']),
subtitle: Text(listData[i]['author']),
);
},
);
}
}
4-6 网格组件
- GridView主要的三种方式:GridView.count、GridView.extent、GridView.builder。
- 常用属性
- scollDirection(滚动方法)、padding(内边距)、resolve(组件反向排序)。
- crossAxisCount(一行的组件数量)、maxCrossAxisExtent(横轴子元素的最大长度)。
- crossAxisSpacing(水平子组件之间间距)、mainAxisSpacing(垂直子组件之间间距)。
- childAspectRatio(子组件宽高比例)、gridDelegate(控制布局主要用在GridView.builder里)。
(1) count实现
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('count实现')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 5, // 一行显示5个图标
children: const [
Icon(Icons.pedal_bike),
Icon(Icons.home),
Icon(Icons.ac_unit),
Icon(Icons.search),
Icon(Icons.settings),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.circle),
],
);
}
}
(2) extent实现
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('extent实现')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return GridView.extent(
maxCrossAxisExtent: 100, // 横轴子元素的最大长度
children: const [
Icon(Icons.pedal_bike),
Icon(Icons.home),
Icon(Icons.ac_unit),
Icon(Icons.search),
Icon(Icons.settings),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.circle),
],
);
}
}
(3) 常用属性实例
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('常用属性实例')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
List<Widget> _initGridViewData() {
List<Widget> tempList=[];
for(var i=1; i<16; i++) {
tempList.add(
Container(
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Colors.blue,
),
child: Text("第${i}个元素",
style: const TextStyle(fontSize: 20),
),
),
);
}
return tempList;
}
Widget build(BuildContext context) {
return GridView.count(
padding: const EdgeInsets.all(2), // 四周间距
crossAxisSpacing: 2, // 配置水平子组件之间的间距
mainAxisSpacing: 2, // 配置垂直子组件之间的间距
crossAxisCount: 5, // 一行显示5个图标,count实现
// maxCrossAxisExtent: 100, // 与extent实现属性相同
childAspectRatio: 0.6, // 宽高比
children: _initGridViewData(),
);
}
}
(4) count动态列表
import '../res/mydata.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('count动态列表')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
List<Widget> _initGridViewData() {
var tempList=listData.map((value) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black26),
),
child: Column(
children: [
Image.network(value["imageUrl"]),
const SizedBox(height: 10),
Text(
value["title"],
style: const TextStyle(fontSize: 13),
),
],
),
);
});
return tempList.toList();
}
Widget build(BuildContext context) {
return GridView.count(
padding: const EdgeInsets.all(2), // 四周间距
crossAxisSpacing: 2, // 配置水平子组件之间的间距
mainAxisSpacing: 2, // 配置垂直子组件之间的间距
crossAxisCount: 3, // 一行显示3个图标,count实现
// maxCrossAxisExtent: 100, // 与extent实现属性相同
childAspectRatio: 0.8, // 宽高比
children: _initGridViewData(),
);
}
}
(5) builder动态列表
import '../res/mydata.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('builder动态列表')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget _initGridViewData(context, index) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black26),
),
child: Column(
children: [
Image.network(listData[index]["imageUrl"]),
const SizedBox(height: 10),
Text(
listData[index]["title"],
style: const TextStyle(fontSize: 13),
),
],
),
);
}
Widget build(BuildContext context) {
return GridView.builder(
padding: const EdgeInsets.all(2), // 四周间距
itemCount: listData.length, // 传入参数长度
// crossAxisSpacing: 2, // 配置水平子组件之间的间距
// mainAxisSpacing: 2, // 配置垂直子组件之间的间距
// crossAxisCount: 3, // 一行显示3个图标
// childAspectRatio: 0.8, // 宽高比
// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisSpacing: 2,
// mainAxisSpacing: 2,
// crossAxisCount: 3,
// childAspectRatio: 0.8,
// ), // 类似于count与extent实现的区别
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
crossAxisSpacing: 2,
mainAxisSpacing: 2,
maxCrossAxisExtent: 150,
childAspectRatio: 0.8,
),
itemBuilder: _initGridViewData,
);
}
}
5 页面布局
- StatelessWidget和StatefulWidget
- 一个Widgets可被定义为无状态组件(StatelessWidget)和有状态组件(StatefulWidget)。
- StatelessWidget是一个不需要可变状态的小组件类型,只需要初始化时的构造函数。
- StatefulWidget是个持有可变状态的小组件类型,复写createState()返回State对象。
- Row和Column:常用的布局类小组件,Row使用水平方向排列子组件,Column则垂直排列。
- Expanded
- Expanded小组件是父组件所在主轴方向上的包裹组件,用于扩展子组件填充剩余空间。
- 当Expanded小组件嵌套在Row,或Column小组件中时,会根据自身比例分配可用空间。
- Stack和Positioned
- Stack组件用于将多个子组件叠放在一起,Positioned组件用于获取并约束子组件的位置和大小。
- 而使用Align组件和PositionedDirectional组件,则可以控制小组件在相对定位坐标系中的位置。
- SingleChildScrollView
- 产生可滚动的组件区域,只有一个子组件,当使用Column或Row嵌套大量组件时可能超出屏幕尺寸。
- 此时用SingleChildScrollView组件将其包裹,使之在应用程序中可滚动并能够适应不同的屏幕尺寸。
5-1 填充组件
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('填充组件')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
// return Container(
// padding: const EdgeInsets.all(160),
// child: const Text("Hello, Flutter!"),
// );
return const Padding( // 占用内存比Container小
padding: EdgeInsets.all(160), // Padding组件属性比较单一
child: Text("Hello, Flutter!"),
);
}
}
5-2 线性布局
- mainAxisAlignment(主轴的排序方式)、crossAxisAlignment(次轴的排序方式)、children(组件子元素)。
(1) 水平布局组件
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('水平布局组件')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity, // 无穷大,占满屏幕
color: Colors.black12,
child: Row( // 外部若没有Container,行将自适应
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, // 相对于Container设置的
children: [
IconContainer(Icons.home),
IconContainer(Icons.search, color: Colors.yellow),
IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
],
),
);
// return Padding(
// padding: const EdgeInsets.all(10),
// child: Row( // 水平布局
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// IconContainer(Icons.home),
// // IconContainer(Icons.search, color: Colors.yellow),
// IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
// ],
// ),
// );
}
}
class IconContainer extends StatelessWidget {
Color color; // 默认传入颜色为红色
IconData icon; // 没有默认颜色使用required关键字
IconContainer(this.icon, {Key? key, this.color=Colors.red}): super(key: key);
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: 130,
width: 130,
color: color,
child: Icon(icon, color: Colors.white, size: 28),
);
}
}
(2) 垂直布局组件
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('垂直布局组件')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity, // 无穷大,占满屏幕
color: Colors.black12,
child: Column( // 垂直布局
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center, // 相对于Container设置的
children: [
IconContainer(Icons.home),
IconContainer(Icons.search, color: Colors.yellow),
IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
],
),
);
// return Padding(
// padding: const EdgeInsets.all(10),
// child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// IconContainer(Icons.home),
// IconContainer(Icons.search, color: Colors.yellow),
// IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
// ],
// ),
// );
}
}
class IconContainer extends StatelessWidget {
Color color; // 默认传入颜色为红色
IconData icon; // 没有默认颜色使用required关键字
IconContainer(this.icon, {Key? key, this.color=Colors.red}): super(key: key);
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: 130,
width: 130,
color: color,
child: Icon(icon, color: Colors.white, size: 28),
);
}
}
5-3 弹性布局
- Flex组件可以沿着水平或垂直方向排列子组件,如果知道主轴方向,使用Row或Column会很方便。
- Row和Column都继承自Flex,参数基本相同,所以能使用Flex的地方基本都可使用Row和Column。
- Flex组件功能强大,也可以和Expanded组件配合实现弹性布局。
(1) 水平左右比例
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('水平左右比例')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Flex(
direction: Axis.horizontal, // 水平位置
// direction: Axis.vertical, // 垂直位置
children: [
Expanded(
child: IconContainer(Icons.home), // 该元素设置宽度无效
flex: 1, // 左右侧比例1:2
),
Expanded(
child: IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
flex: 2,
),
],
);
}
}
class IconContainer extends StatelessWidget {
Color color; // 默认传入颜色为红色
IconData icon; // 没有默认颜色使用required关键字
IconContainer(this.icon, {Key? key, this.color=Colors.red}): super(key: key);
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: 130,
width: 130,
color: color,
child: Icon(icon, color: Colors.white, size: 28),
);
}
}
(2) 水平右侧固定
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('水平右侧固定')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Row(
// direction: Axis.horizontal, // 水平位置
// direction: Axis.vertical, // 垂直位置
children: [
Expanded(
child: IconContainer(Icons.home), // 该元素设置宽度无效
flex: 1,
),
IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
],
);
}
}
class IconContainer extends StatelessWidget {
Color color; // 默认传入颜色为红色
IconData icon; // 没有默认颜色使用required关键字
IconContainer(this.icon, {Key? key, this.color=Colors.red}): super(key: key);
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: 80, // 右侧固定宽度,左侧自适应
width: 80,
color: color,
child: Icon(icon, color: Colors.white, size: 28),
);
}
}
(3) 实现弹性布局
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('实现弹性布局')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ListView(
children: [
Container(
width: double.infinity,
height: 200,
color: Colors.black,
),
Row(
children: [
Expanded(
child: SizedBox(
height: 200, // 水平方向考虑高度
child: Image.network('http://www.m58.link/pOQYO', fit: BoxFit.cover),
),
flex: 2,
),
Expanded(
flex: 1,
child: SizedBox(
height: 200, // 垂直方向则考虑宽度
child: Column(
children: [
Expanded( // 图片可能不会铺满,使用SizedBox
child: Image.network(
'http://www.m58.link/BiPCY',
fit: BoxFit.cover,
),
flex: 1,
),
const SizedBox(height: 1),
Expanded(
child: SizedBox(
width: double.infinity, // 占满屏幕
child: Image.network(
'http://www.m58.link/dDobp',
fit: BoxFit.cover,
),
),
flex: 1,
),
],
),
),
),
],
),
],
);
}
}
5-4 层叠组件
- Stack即堆
- 使用Stack,或Stack结合Align,或Stack结合Positioned实现页面的定位布局。
- Stack属性包括:alignment(配置所有子元素的显示位置)、children(子组件)。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('层叠组件')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Stack( // Column则排列一起
alignment: Alignment.center,
children: [
Container(
height: 400,
width: 400,
color: Colors.red,
),
Container(
height: 200,
width: 200,
color: Colors.yellow,
),
const Text("Hello, Flutter!"),
],
);
}
}
(1) Positioned
- Stack组件结合Positioned组件可以控制每个子元素的显示位置。
- top(子元素距离顶部的距离)、bottom(子元素距离底部的距离)、child(子组件)。
- left(子元素距离左侧距离)、right(子元素距离右侧距离)、height(子组件高度)。
- width(子组件的宽度,注意宽度和高度必须是固定值,没法使用double.infinity)。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Positioned')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Container(
height: 400,
width: 400,
color: Colors.red,
child: Stack( // Stack是相对于外部容器进行定位
children: [ // 若无外部容器就相对于整个屏幕定位
Positioned(
left: 0, // 居于容器左下角
bottom: 0,
child: Container(
height: 200,
width: 200,
color: Colors.yellow,
),
),
const Positioned(
right: 0, // 居于容器右侧中间
top: 200,
child: Text("Hello, Flutter!"),
),
],
),
);
}
}
(2) 浮动导航功能
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('浮动导航功能')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size; // 获取设备的宽度和高度
return Stack(
children: [
ListView(
padding: const EdgeInsets.only(top: 50), // 将隐藏的列表01显示出来
children: const [
ListTile(title: Text("列表01")),
ListTile(title: Text("列表02")),
ListTile(title: Text("列表03")),
ListTile(title: Text("列表04")),
ListTile(title: Text("列表05")),
ListTile(title: Text("列表06")),
ListTile(title: Text("列表07")),
ListTile(title: Text("列表08")),
ListTile(title: Text("列表09")),
ListTile(title: Text("列表10")),
ListTile(title: Text("列表11")),
ListTile(title: Text("列表12")),
ListTile(title: Text("列表13")),
ListTile(title: Text("列表14")),
ListTile(title: Text("列表15")),
ListTile(title: Text("列表16")),
ListTile(title: Text("列表17")),
ListTile(title: Text("列表18")),
ListTile(title: Text("列表19")),
ListTile(title: Text("列表20")),
],
),
Positioned(
left: 0,
top: 0,
width: size.width, // 无法使用double.infinity
height: 50,
child: Row(
children: [
Expanded(flex: 1, child: Container(
alignment: Alignment.center,
height: 50,
color: Colors.purple,
child: const Text(
"二级导航",
style: TextStyle(color: Colors.white),
),
)),
],
),
),
],
);
}
}
(3) 优化浮动导航
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('优化浮动导航')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size; // 获取设备的宽度和高度
return Stack(
children: [
ListView(
padding: const EdgeInsets.only(bottom: 50),
children: const [
ListTile(title: Text("列表01")),
ListTile(title: Text("列表02")),
ListTile(title: Text("列表03")),
ListTile(title: Text("列表04")),
ListTile(title: Text("列表05")),
ListTile(title: Text("列表06")),
ListTile(title: Text("列表07")),
ListTile(title: Text("列表08")),
ListTile(title: Text("列表09")),
ListTile(title: Text("列表10")),
ListTile(title: Text("列表11")),
ListTile(title: Text("列表12")),
ListTile(title: Text("列表13")),
ListTile(title: Text("列表14")),
ListTile(title: Text("列表15")),
ListTile(title: Text("列表16")),
ListTile(title: Text("列表17")),
ListTile(title: Text("列表18")),
ListTile(title: Text("列表19")),
ListTile(title: Text("列表20")),
],
),
Positioned(
left: 0,
bottom: 0, // 二级导航居于底部
child: Container(
width: size.width, // 配置子元素的宽度和高度
height: 50,
alignment: Alignment.center,
color: Colors.purple,
child: const Text(
"二级导航",
style: TextStyle(color: Colors.white),
),
),
),
],
);
}
}
(4) Align方法介绍
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Align方法介绍')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return Container(
// alignment: Alignment.topCenter, // 方法一
width: 200,
height: 200,
color: Colors.red,
// child: const Align( // 方法二
// alignment: Alignment.center,
// child: Text("Hello, Flutter!"),
// ),
// child: const Center( // 方法三
// child: Text("Hello, Flutter!"),
// ),
// (Alignment.x*childwidth/2+childwidth/2,Alignment.y*childHeight/2+childHeight/2)
child: const Align(
alignment: Alignment(-1, 1), // 通过坐标转换公式计算偏移位置
child: Text("Hello, Flutter!"),
),
);
}
}
(5) Stack结合Align
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Stack结合Align')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
// return Stack(
// children: const [
// Align(alignment: Alignment.topLeft, child: Text("收藏")),
// Align(alignment: Alignment.topRight, child: Text("购买")),
// ],
// );
return Column(
children: [
SizedBox(
width: double.infinity,
height: 40,
child: Stack(
children: const [
Align(alignment: Alignment.topLeft, child: Text("收藏")),
Align(alignment: Alignment.topRight, child: Text("购买")),
],
),
),
SizedBox(
width: double.infinity,
height: 40,
child: Stack( // 相对于容器SizedBox来定位
children: const [
Positioned(left: 10, child: Text("收藏")),
Positioned(right: 10, child: Text("购买")),
],
),
),
],
);
}
}
5-5 比例组件
- 作用是根据设置调整子元素child的宽高比,首先在布局限制条件允许的范围内尽可能的扩展。
- Widget的高度由宽度和比率决定,类似于BoxFit中的contain,按照固定比率去尽量占满区域。
- 如果满足所有限制条件后仍无法找到一个可行尺寸,将优先适应布局限制条件,忽略设置的比率。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('比例组件')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
// 页面上显示一个容器,宽度是屏幕的宽度,高度是宽度的一半
return AspectRatio(
aspectRatio: 2/1,
child: Container(
color: Colors.red,
),
);
}
}
5-6 卡片组件
- Card是卡片组件块,内容由大多数类型组件构成,具有圆角和阴影,看起来更具立体感。
- margin(外边距)、elevation(阴影值的深度)、color(背景颜色)、shadowColor(阴影颜色)。
- child(子组件)、Shape(Card的阴影效果,默认为圆角长方形边)、clipBehavior(内容溢出的剪切方式)。
- Clip.none(不剪切)、Clip.harEdge(剪切但不抗锯齿)、Clip.antiAlias(剪切并抗锯齿)。
- Clip.antiAliasWithSaveLayer:带有抗锯齿的剪切,并在剪切之后立即保存saveLayer。
- AspectRatio(宽高比,最终可能不会根据这个值布局,具体看综合因素,只是一个参考值)。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('卡片组件')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView(
children: [
Card(
shape: RoundedRectangleBorder( // 卡片的阴影效果
borderRadius: BorderRadius.circular(30),
),
elevation: 10, // 卡片阴影值的深度
margin: const EdgeInsets.all(30), // 边距
child: Column(
children: const [
ListTile(
title: Text("Luxury", style: TextStyle(fontSize: 28)),
subtitle: Text("高级软件工程师"),
),
Divider(), // 分割线
ListTile(title: Text("电话:18856666110")),
ListTile(title: Text("地址:福建省厦门市思明区")),
],
),
),
// const SizedBox(height: 10), // 两卡片之间的距离
Card(
elevation: 10, // 卡片阴影
color: Colors.greenAccent, // 背景颜色
child: Column(
children: const [
ListTile(
title: Text("Aurora", style: TextStyle(fontSize: 28)),
subtitle: Text("高级开发工程师"),
),
Divider(), // 分割线
ListTile(title: Text("电话:18856666120")),
ListTile(title: Text("地址:福建省厦门市湖里区")),
],
),
),
],
);
}
}
(1) 圆形图片
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('圆形图片')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 20,
margin: const EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16/9,
child: Image.network("http://www.m58.link/yWnmo", fit: BoxFit.cover),
),
ListTile(
leading: ClipOval( // ClipOval实现圆形图片
child: Image.network(
"http://www.m58.link/yWnmo",
fit: BoxFit.cover,
height: 50,
width: 50,
),
),
title: const Text("桃夭"),
subtitle: const Text("桃之夭夭,灼灼其华。"),
),
],
),
),
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 20,
margin: const EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16/9,
child: Image.network("http://www.m58.link/abMGM", fit: BoxFit.cover),
),
const ListTile(
leading: CircleAvatar( // CircleAvatar实现圆形图片
radius: 25, // 设置头像半径
backgroundImage: NetworkImage("http://www.m58.link/abMGM"),
),
title: Text("蒹葭"),
subtitle: Text("蒹葭苍苍,白露为霜。"),
),
],
),
),
],
);
}
}
(2) 循环遍历
import './res/mydata.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('循环遍历')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
List<Widget> _initCardData() {
var tempList = listData.map((value) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 20,
margin: const EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16/9,
child: Image.network(value['imageUrl'], fit: BoxFit.cover),
),
ListTile(
leading: ClipOval( // ClipOval实现圆形图片
child: Image.network(
value['imageUrl'],
fit: BoxFit.cover,
height: 50,
width: 50,
),
),
title: Text(value['title']),
subtitle: Text(value['author']),
),
],
),
);
});
return tempList.toList();
}
Widget build(BuildContext context) {
return ListView(
children: _initCardData(), // 调用方法
);
}
}
5-7 按钮组件
- onPressed(必填参,按下按钮时触发回调,接收一个方法,传null表禁用,显示相关样式)。
- child(子组件)、style(通过ButtonStyle进行装饰,ButtonStyle里面的常用参数,如下所示)。
- ElevatedButton:凸起按钮,默认带有阴影和灰色背景,按下后阴影变大。
- TextButton:文本按钮,默认背景透明并且不带阴影,按下后会有背景色。
- OutlinedButton:默认带边框,不带阴影且背景透明,按下后边框颜色变亮同时出现背景和阴影。
- IconButton:是一个可点击的Icon图标,不包含文字,默认是没有背景的,点击后将会出现背景。
- 带图标的按钮:ElevatedButton、TextButton、OutlinedButton都有一个icon构造函数,可带图标。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('按钮组件')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton( // 凸起按钮
onPressed: (){print("ElevatedButton");}, // 打印ElevatedButton
child: const Text("普通按钮"),
),
TextButton( // 文本按钮
onPressed: (){},
child: const Text("文本按钮"),
),
const OutlinedButton( // 带边框的按钮
onPressed: null,
child: Text("边框按钮"),
),
IconButton(onPressed: (){}, icon: const Icon(Icons.thumb_up)),
],
),
const SizedBox(height: 20),
Row( // 调整按钮位置
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton.icon( // 带图标的按钮
onPressed: (){},
icon: const Icon(Icons.send),
label: const Text("发送"),
),
TextButton.icon(
onPressed: (){},
icon: const Icon(Icons.info),
label: const Text("消息"),
),
OutlinedButton.icon(
onPressed: (){},
icon: const Icon(Icons.add),
label: const Text("增加"),
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton( // 修改按钮和图标的颜色
style: ButtonStyle( // 背景颜色和文字颜色
backgroundColor: MaterialStateProperty.all(Colors.red),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
onPressed: (){print("ElevatedButton");},
child: const Text("普通按钮"),
),
],
),
],
);
}
}
(1) 修改宽和高
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('修改宽和高')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView(
children: [
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
height: 60,
width: 180,
child: ElevatedButton(
onPressed: (){},
child: const Text("确认"),
),
),
SizedBox(
height: 60,
width: 180,
child: ElevatedButton.icon(
onPressed: (){},
icon: const Icon(Icons.search),
label: const Text("搜索"),
),
),
],
),
],
);
}
}
(2) 自适应按钮
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('自适应按钮')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView(
children: [
const SizedBox(height: 10),
Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
flex: 1, // 占满屏幕
child: Container(
margin: const EdgeInsets.all(20),
height: 40,
// width: 180, // 没效果了
child: ElevatedButton(
onPressed: (){},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
const Color.fromARGB(220, 219, 35, 35),
),
foregroundColor: MaterialStateProperty.all(Colors.white)
),
child: const Text("登录"),
),
),
),
],
),
],
);
}
}
(3) 圆形与圆角
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('圆形与圆角')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView(
children: [
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(
height: 60,
width: 60,
child: ElevatedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all(
const CircleBorder(
side: BorderSide(
width: 3,
color: Colors.yellow,
),
),
),
),
onPressed: (){},
child: const Text("圆形"),
),
),
ElevatedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
onPressed: (){},
child: const Text("圆角"),
),
],
),
],
);
}
}
(4) 边框的修改
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('边框的修改')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView(
children: [
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton(
style: ButtonStyle(
side: MaterialStateProperty.all(
const BorderSide(width: 1, color: Colors.red),
),
),
onPressed: (){},
child: const Text("带边框的按钮"),
),
],
),
],
);
}
}
5-8 Wrap组件
- 该组件可以实现流布局,单行的Wrap和Row表现几乎一致,单列的Wrap和Column也表现几乎一致。
- Row与Column都是单行单列,Wrap则突破限制,mainAxis上空间不足时,向crossAxis上扩展显示。
- 属性说明
- direction(主轴的方向,默认水平)、spacing(主轴方向上的间距)。
- alignment(主轴的对齐方式)、textDirection(文本方向)、runSpacing(run的间距)。
- verticalDirection(定义了children的摆放顺序,默认是down,详见Flex相关属性)。
- runAlignment(run的对齐方式,run可理解为新的行或列,水平方向布局可理解为新的一行)。
(1) Wrap组件使用
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Wrap组件使用')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
// return Button("第1集", onPressed: (){});
return Padding(
padding: const EdgeInsets.all(10),
child: Wrap(
// alignment: WrapAlignment.center, // 居中显示
spacing: 5, // x轴上的间距数(水平间距)
runSpacing: 10, // y轴上的间距数(垂直间距)
// direction: Axis.vertical, // 垂直方向排列
children: [
Button("第1集", onPressed: (){}),
Button("第2集", onPressed: (){}),
Button("第3集", onPressed: (){}),
Button("第4集", onPressed: (){}),
Button("第5集", onPressed: (){}),
Button("第6集", onPressed: (){}),
Button("第7集", onPressed: (){}),
Button("第8集", onPressed: (){}),
Button("第9集", onPressed: (){}),
Button("第10集", onPressed: (){}),
Button("第11集", onPressed: (){}),
Button("第12集", onPressed: (){}),
Button("第13集", onPressed: (){}),
Button("第14集", onPressed: (){}),
Button("第15集", onPressed: (){}),
Button("第16集", onPressed: (){}),
Button("第17集", onPressed: (){}),
Button("第18集", onPressed: (){}),
],
),
);
}
}
class Button extends StatelessWidget { // 自定义按钮组件
String text; // 按钮的文字
void Function()? onPressed; // 方法
Button(this.text, {Key? key, required this.onPressed}): super(key: key);
Widget build(BuildContext context) {
return ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
const Color.fromRGBO(244, 244, 244, 244),
),
foregroundColor: MaterialStateProperty.all(Colors.black45),
),
onPressed: onPressed,
child: Text(text),
);
}
}
(2) 搜索页面布局
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('搜索页面布局')),
body: const MyApp(),
),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}): super(key: key);
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(10),
children: [
Row(
children: [
Text("热搜", style: Theme.of(context).textTheme.titleLarge),
],
),
const Divider(),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
Button("女装", onPressed: (){}),
Button("笔记本", onPressed: (){}),
Button("玩具", onPressed: (){}),
Button("文学", onPressed: (){}),
Button("时尚", onPressed: (){}),
Button("扭扭棒", onPressed: (){}),
Button("健身", onPressed: (){}),
Button("台式机", onPressed: (){}),
],
),
const SizedBox(height: 10),
Row(
children: [
Text("历史记录", style: Theme.of(context).textTheme.titleLarge),
],
),
const Divider(),
Column(
children: const [
ListTile(title: Text("女装")),
Divider(),
ListTile(title: Text("手机")),
Divider(),
ListTile(title: Text("电脑")),
Divider(),
],
),
const SizedBox(height: 40),
// OutlinedButton.icon( // 自适应
// onPressed: (){},
// icon: const Icon(Icons.delete),
// label: const Text("清空历史记录"),
// ),
Padding(
padding: const EdgeInsets.all(40),
child: OutlinedButton.icon(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black45),
),
onPressed: (){},
icon: const Icon(Icons.delete),
label: const Text("清空历史记录"),
),
),
],
);
}
}
class Button extends StatelessWidget { // 自定义按钮组件
String text; // 按钮的文字
void Function()? onPressed; // 方法
Button(this.text, {Key? key, required this.onPressed}): super(key: key);
Widget build(BuildContext context) {
return ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
const Color.fromRGBO(244, 244, 244, 244),
),
foregroundColor: MaterialStateProperty.all(Colors.black45),
),
onPressed: onPressed,
child: Text(text),
);
}
}