环境搭建
看下面的链接 flutter环境搭建
runApp
runApp是内部flutter内部提供的函数,启动一个flutter应用就从调用这个函数开始(类比React的main.tsx)
dart
void main() {
runApp(const MyApp())
}
其中,MyApp是入口widget,那widget是什么?
widget
widget类似于React中的组件,React中皆有组件构成,flutter中皆由widget组成

Material库
Material库是google设计的一套设计风格库,比如文字排版、颜色、动画等,目的是为 安卓、IOS、鸿蒙等多个平台提供统一的交互和视觉体验
Material库是flutter自带的,不需要额外安装。并且Material包含很多拆箱可用的widget

基础组件
MaterialApp
整个flutter应用需要被MaterialApp包裹。它有一些常用属性
- title: 窗口标题
- theme: 应用主题
- home: 窗口主题内容
dart
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(
MaterialApp(
title: "Flutter组件初体验",
// ThemeData主题类
theme: ThemeData(scaffoldBackgroundColor: Colors.blue),
// Scaffold骨架组件
home: Scaffold(),
),
);
}
Scaffold
Scaffold是构建 Material Design 风格页面的核心布局组件,即为页面骨架组件。比较常用的几个属性如下
- appBar: 页面顶部
- body:页面内容区域
- bottomNavigationBar: 底部导航栏
- backgroundColor:设置整个 Scaffold 的背景颜色
- floatingActionButton: 悬浮按钮,常用于触发页面的主要动作
- ....
dart
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(
MaterialApp(
title: "Flutter组件初体验",
home: Scaffold(
appBar: AppBar(title: Text("头部区域")),
body: Container(child: Center(child: Text("中部区域"))),
bottomNavigationBar: Container(
height: 80,
child: Center(child: Text('底部区域')),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.blue,
child: Icon(Icons.add),
),
),
),
);
}
其中 Text是显示文本的组件,Container是用来作为容器(类似div),设置高度以及child,FloatingActionButton是作为悬浮按钮容器的组件,Center是让组件水平、垂直居中的组件

无状态组件和有状态组件
无状态组件 StatelessWidget
无状态组件是单个类,用于静态内容展示,外观由配置参数决定,创建后内部状态不可变,生命周期只有build
dart
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MainPage()); // Widget
}
// 无状态组件 -自定义组件中的一种
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
title: "Flutter组件初体验-无状态组件",
home: Scaffold(
appBar: AppBar(
title: Text("头部区域"),
),
body: Container(
child: Center(
child: Text("中部区域"),
),
),
bottomNavigationBar: Container(
height: 80,
child: Center(
child: Text("底部区域"),
),
),
));
}
}
- 创建一个新的类,继承
StatelessWidget类并重写build方法 - build需要返回一个Widget
- 纯展示型组件,无交互
- 无状态组件在被创建或者父组件状态变化时,会重新调用build
有状态组件 StatefulWidget
交互式组件,包含状态创建、更新、销毁等,Widget本身和单独的State类相关联。生命周期包含 createState、initState、didChangeDependencies、didUpdateWidget、deactivate、dispose、build
dart
import 'package:flutter/material.dart';
void main(List<String> args) {
// runApp(MainPage()); // Widget
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() {
print("createState阶段执行");
return _MainPageState();
}
}
class _MainPageState extends State<MainPage> {
@override
void initState() {
print("initState阶段执行");
// TODO: implement initState
super.initState();
}
@override
void didChangeDependencies() {
print("didChangeDependencies阶段执行");
// TODO: implement didChangeDependencies
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant MainPage oldWidget) {
print("didUpdateWidget阶段执行");
// TODO: implement didUpdateWidget
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print("deactivate阶段执行");
// TODO: implement deactivate
super.deactivate();
}
@override
void dispose() {
print("dispose阶段执行");
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
print("build阶段执行");
return Container(
child: null,
);
}
}
- 创建两个类,第一个继承
StatefulWidget类,接受和定义参数,创建State对象 - 第二个类继承
State<第一个类类名>,负责管理状态与业务逻辑,并实现build方法(返回一个Widget)
有状态组件生命周期:



事件
通过 GestureDetector包裹组件
dart
child: GestureDetector(
onTap: () {
print("轻触了该区域");
},
child: Text("中部区域"),
),
更多事件可以查看 GestureDetector 类
除了上述类包裹外,也可以使用专用组件触发事件

状态更新
在有状态组件中,在第二个类里面定义状态
我们以一个计数器来理解
dart
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Row(
children: [
TextButton(
onPressed: () {
setState(() {
count--;
});
},
child: Text("减")),
Text(count.toString()),
TextButton(
onPressed: () {
count += 1;
print(count);
setState(() {});
},
child: Text("加"))
],
),
),
),
);
}
}
你可以直接在 setState 里面写count的加减,也可以先写count加减,在通过 setState 去更新页面。setState会造成build重新执行
布局组件

Container
- Container是个容器组件
- 可通过多种方式定义大小,但有
优先级限制 - 优先级规则:明确宽高
width、height> constraints约束 > 父组件约束 > 自适应组件大小 - 通过
decoration属性实现装饰效果,即一些样式效果,但是与color互斥 - 布局通过
margin、padding和aligment控制 - 支持矩阵变化,如
transform实现倾斜、旋转、平移等


Center
- Center是居中布局组件
- 将子组件
水平、垂直居中在父组件中 - 不能设置宽高,Center的大小取决于
父组件传递给他的约束 - Center一般是去包裹一个具备宽高的子组件。比如包裹一个宽高固定的Container/SizeBox

Align
- Align是控制组件对齐方式的组件。
- Center是属于Align的一个特例,相当于一个将aligment属性居中的Aligen.center
alignment: 子组件在父组件中的对齐方式widthFactor()宽度因子: Align的宽度是子组件的宽度乘以该属性heightFactor()高度因子:Align的高度是子组件的宽度乘以该属性

Padding
- 为组件添加内边距
- Container组件也可以设置padding,单一需求用Padding,复杂的用Container



Column
- 在垂直方向布局的组件(类似aligen-item)




Row
- 水平方向布局的组件(类似justfiy-content)



Flex
- Flex 跟css的flex类似,是Column和Row的组合


比如顶部底部高度固定,中间自适应

dart
import 'package:flutter/material.dart';
void main(List<String> args) {
// runApp(MainPage()); // Widget
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
color: Colors.amber,
child: Flex(
direction: Axis.vertical,
children: [
Container(color: Colors.blue, height: 100),
Expanded(child: Container(color: Colors.blueGrey)),
Container(color: Colors.red, height: 100),
],
),
),
),
);
}
}
Wrap
- 流式布局组件,子组件在主轴上排列不下时,自动换行
- 可以理解为 css中 flex 加了 flex-wrap:wrap


Stack/Positioned


其实 Stack/Position 类似于css子绝父相。 Stack 理解为父元素绝对定位,Position理解为子元素相对定位

Text/TextSpan



Image




TextField



滚动组件

SingleChildScrollView


dart
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main(List<String> args) {
// runApp(MainPage()); // Widget
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
ScrollController _controller = ScrollController(); //滚动条控制器
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("登录"),
),
body: Stack(
children: [
SingleChildScrollView(
controller: _controller,
padding: EdgeInsets.all(20),
child: Column(
children: List.generate(100, (index) {
return Container(
margin: EdgeInsets.only(top: 10),
width: double.infinity,
color: Colors.blue,
height: 100,
child: Text("我是第${index + 1}个",
style:
TextStyle(color: Colors.white, fontSize: 30)),
alignment: Alignment.center,
);
}),
),
),
// 放置堆叠组件
Positioned(
right: 10,
top: 10,
child: GestureDetector(
onTap: () {
// print("去底部");
// _controller.jumpTo(
// _controller.position.maxScrollExtent); // 滚动到底部
_controller.animateTo(
_controller.position.maxScrollExtent,
duration: Duration(seconds: 1),
curve: Curves.easeIn);
},
child: Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(40)),
width: 80,
height: 80,
alignment: Alignment.center,
child:
Text("去底部", style: TextStyle(color: Colors.white)),
)),
),
Positioned(
right: 10,
bottom: 10,
child: GestureDetector(
onTap: () {
// print("去顶部");
// _controller.jumpTo(0);
_controller.animateTo(0,
duration: Duration(seconds: 1),
curve: Curves.bounceIn);
},
child: Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(40)),
width: 80,
height: 80,
alignment: Alignment.center,
child:
Text("去顶部", style: TextStyle(color: Colors.white)),
)),
)
],
)));
}
}

ListView



GridView




CustomScrollView




PageView


dart
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main(List<String> args) {
// runApp(MainPage()); // Widget
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0; // 当前激活索引
PageController _controller = PageController();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("登录"),
),
body: CustomScrollView(
slivers: [
// 包裹普通Widget的东西
SliverToBoxAdapter(
child: Stack(
children: [
Container(
color: Colors.blue,
alignment: Alignment.center,
height: 260,
child: PageView.builder(
controller: _controller,
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Container(
alignment: Alignment.center,
child: Text("轮播图${index + 1}",
style: TextStyle(
color: Colors.white, fontSize: 20)),
);
}),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
height: 40,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(10, (index) {
return GestureDetector(
onTap: () {
// 切换到具体的page
// _controller.jumpToPage(index);
_controller.animateToPage(index,
duration: Duration(milliseconds: 300),
curve: Curves.linear);
_currentIndex = index;
setState(() {});
},
child: Container(
margin: EdgeInsets.only(left: 10),
width: 10,
height: 10,
decoration: BoxDecoration(
color: _currentIndex == index
? Colors.red
: Colors.white,
borderRadius: BorderRadius.circular(5)),
),
);
}),
),
)
],
)),
SliverToBoxAdapter(
child: SizedBox(
height: 10,
)),
SliverPersistentHeader(
delegate: _StickyCategory(),
pinned: true, // 固定吸顶
),
SliverToBoxAdapter(
child: SizedBox(
height: 10,
)),
SliverGrid.count(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: List.generate(100, (index) {
return Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text('列表项${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 20)),
);
}),
)
],
)));
}
}
class _StickyCategory extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
// TODO: implement build
return Container(
color: Colors.white,
child: ListView.builder(
itemCount: 30,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return Container(
width: 100,
margin: EdgeInsets.symmetric(horizontal: 10),
color: Colors.blue,
alignment: Alignment.center,
child:
Text('分类${index + 1}', style: TextStyle(color: Colors.white)),
);
}),
);
}
@override
// TODO: implement maxExtent
double get maxExtent => 80; // 最大展开高度
@override
// TODO: implement minExtent
double get minExtent => 40; // 最小折叠高度
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
// TODO: implement shouldRebuild
return false; // 不需要重建
}
}
组件通信

父传子
子组件通过 final 关键字定义父组件传来的数据,因为属性是父组件决定的,子组件不能更改

子传父

网络请求DIO
dart
import 'package:dio/dio.dart';
void main(List<String> args) {
Dio().get("https://geek.itheima.net/v1_0/channels").then((res) {
print(res);
}).catchError((error) {});
}
通常是在 initState 中初始化状态
dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
void initState() {
// TODO: implement initState
super.initState();
// 发起网络请求
_getChannels(); // 获取频道数据
}
List<Map<String, dynamic>> _list = []; // 用来接收数据的
void _getChannels() async {
DioUtils util = DioUtils(); // 创建实例化对象
Response<dynamic> result = await util.get("channels");
Map<String, dynamic> res = result.data as Map<String, dynamic>;
// print(res["data"]["channels"] as List<Map<String, dynamic>>);
List data = res["data"]["channels"] as List;
_list = data.cast<Map<String, dynamic>>() as List<Map<String, dynamic>>;
// cast方法强制转化列表项的类型
setState(() {}); // 执行方法 UI才会更新
print(_list);
// channels是一个后端支持前端跨域访问的接口 cors 支持任何的域名进行访问
// www.baidu.com
// localhost:60791
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("频道管理")),
body: GridView.extent(
padding: EdgeInsets.all(10),
maxCrossAxisExtent: 140,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 3,
children: List.generate(_list.length, (index) {
return ChannelItem(item: _list[index]);
}),
),
),
);
}
}
// 用来绘制每个频道的UI内容
class ChannelItem extends StatelessWidget {
final Map<String, dynamic> item;
const ChannelItem({Key? key, required this.item}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text(
item["name"] ?? "空",
style: TextStyle(color: Colors.white, fontSize: 14),
),
);
}
}
// 封装一个工具类
class DioUtils {
final Dio _dio = Dio(); // 内部Dio实例对象
DioUtils() {
// 做些基本的操作
// 配置基础地址 和超时时间
// _dio.options.baseUrl = "https://geek.itheima.net/v1_0/";
// _dio.options.connectTimeout = Duration(seconds: 10); // 连接超时
// _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时
// _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时
// 简写 ..连续赋值的写法
_dio.options
..baseUrl = "https://geek.itheima.net/v1_0/"
..connectTimeout = Duration(seconds: 10)
..sendTimeout = Duration(seconds: 10)
..receiveTimeout = Duration(seconds: 10);
// 拦截器
_addInterceptor(); // 注册添加拦截器
}
void _addInterceptor() {
_dio.interceptors.add(InterceptorsWrapper(
// 请求拦截器
onRequest: (context, handler) {
// handler.next(requestOptions) 放过请求
// handler.reject(error) 拦截请求
handler.next(context);
},
// 响应拦截器
onResponse: (context, handler) {
// http状态吗 2xx 成功 3 4 5
// handler.reject(error)
if (context.statusCode! >= 200 && context.statusCode! < 300) {
handler.next(context); // 放过
return;
}
// 说明出异常
handler.reject(DioException(requestOptions: context.requestOptions));
// 抛出异常
},
// 错误拦截器
onError: (context, handler) {
handler.reject(context); // 直接抛出异常
}));
}
// 向外暴露一个get方法
Future<Response<dynamic>> get(String url, {Map<String, dynamic>? params}) {
return _dio.get(url, queryParameters: params);
}
}
路由








