Flutter 环境搭建
1. 安装Flutter SDK 配置环境变量
2. 配置两个用户环境变量
变量名: FLUTTER_STORAGE_BASE_URL
变量值: https://storage.flutter-io.cn
变量名: PUB_HOSTED_URL
3. 安装Android Studio 顺带安装Android SDK
脚手架组件
Scaffold
1. appBar: 显示在界面顶部的导航栏
2. body: 当前页面所显示的主要内容
3. bottomNavigationBar: 显示在底部的导航栏
无状态组件
// 无状态组件 必须要继承 StatelessWidget
class Component extends StatelessWidget {
// 创建组件 创建一个方法
@override
Widget build(BuildContext context) {
// 返回一个组件
return Container(
color: Colors.white,
alignment: Alignment.center,
child: Text('一个帅气的前端'),
);
}
}
效果如下
有状态组件:
1. 在组件内部需要维护可变的状态来重构界面的组件
2. 使用 setState() 方法改编状态的值来触发组件重新构建界面
3. 有状态组件也可以展示构造函数传入的数据
定义方式:状态组件包含两个类
一个类继承自 StatefulWidget , 作为页面结构的一部分 , 用作外部展示
一另一个类继承自State , 用于记录组件状态 , 并根据状态的变化 , 重新构建组件
// 对外作展示
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
// 控制逻辑
class _MyWidgetState extends State<MyWidget> {
int index = 0;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: TextButton(
child: Text('一共点击$index了多少次'),
onPressed: () {
setState(() {
index++;
});
},
),
);
}
}
生命周期
无状态组件
build() : 该方法是用来创建组件
状态组件
statefulWidget.createState() : 创建逻辑组件
initState() : 状态组件初始化
didChangeDependencies() : 依赖状态更新时调用
build() : 组件创建
addPostFrameCallback() : 组件渲染完成
deactivate() : 卸载前
dispose() : 卸载后
特殊
didUpdateWidget(): 父组件发生变化可以监听到 , 重新构建组件
// 对外作展示
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
// 控制逻辑
class _MyWidgetState extends State<MyWidget> {
int index = 0;
@override
void initState() {
//监听组件是否渲染完成
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: TextButton(
child: Text('一共点击$index了多少次'),
onPressed: () {
setState(() {
index++;
});
},
),
);
}
}
Container组件是一个用户绘制 , 定位 , 调整大小的组件
// 对外作展示
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
// 控制逻辑
class _MyWidgetState extends State<MyWidget> {
int index = 0;
@override
void initState() {
//监听组件是否渲染完成
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter初体验')),
body: Container(
alignment: Alignment.center,
color: Colors.white,
child: Container(
width: 200,
height: 200,
padding: const EdgeInsets.only(
left: 25.0, right: 25.0, top: 30, bottom: 15),
// 如果设置了decoration属性 , 那么Container组件的color就不能独立设置
color: Colors.blue[100],
child: Container(
decoration: BoxDecoration(color: Colors.red[100]),
),
)),
);
}
}
效果图如下
布局
纵向布局
// 控制逻辑
class _MyWidgetState extends State<MyWidget> {
int index = 0;
@override
void initState() {
//监听组件是否渲染完成
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter初体验')),
body: Center(
child: Container(
width: 200,
height: 300,
padding: const EdgeInsets.only(
left: 25.0, right: 25.0, top: 30, bottom: 15),
// 如果设置了decoration属性 , 那么Container组件的color就不能独立设置
color: Colors.blue[100],
child: Column(
// 主轴对其方式
// mainAxisAlignment: MainAxisAlignment.start,
// 副轴对齐方式
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/* 本地图片使用 要在pubspec.yaml 配置assets根路径
/ assets:
/ - assets/
*/
Image.asset('assets/qyc.png'),
const Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
'犬夜叉',
style: TextStyle(color: Color.fromARGB(255, 231, 115, 156)),
),
),
const Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
'戈薇',
style: TextStyle(color: Color.fromARGB(255, 231, 115, 156)),
),
),
],
)),
),
);
}
}
横向布局
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('横向布局'),
),
body: Center(
child: Container(
padding:
const EdgeInsets.only(left: 10, top: 10, bottom: 10, right: 40),
height: 100,
color: Colors.blue[100],
child: Row(
// 主轴方式对齐
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Image.asset(
'assets/qyc.png',
width: 100,
height: 100,
),
const Text(
'犬夜叉',
style: TextStyle(color: Colors.pink),
)
],
),
)));
}
}
弹性布局
解决文字溢出问题
Expanded
body: Center(
child: Container(
padding:
const EdgeInsets.only(left: 10, top: 10, bottom: 10, right: 40),
height: 100,
color: Colors.blue[100],
child: Row(
// 主轴方式对齐
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(
'assets/qyc.png',
width: 100,
height: 100,
),
const Expanded(
child: Text(
'犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬夜叉',
style: TextStyle(color: Colors.pink),
))
],
),
)));
效果如下
如果只是用Text组件的话 就会出现下面的情况
Expanded有flex属性 类似css子盒子flex
定位
使用Stack组件搭配Positioned组件
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('定位'),
),
body: Center(
child: Stack(alignment: Alignment.center, children: [
Positioned(
top: 20,
child: Container(
height: 100,
width: 100,
color: Colors.blue[100],
),
),
Positioned(
top: 0,
child: Image.asset(
'assets/qyc.png',
width: 80,
height: 80,
))
]),
));
}
}
一个小小的案例
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('商品列表'),
),
body: Container(
padding: const EdgeInsets.all(10),
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
'2021-05-15 21:49:48',
style: TextStyle(color: Color(0xff666666), fontSize: 13),
),
Text(
'代发货',
style: TextStyle(color: Color(0xffff9240), fontSize: 14),
)
]),
Padding(
padding: const EdgeInsets.only(top: 5),
child:
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
Image.asset(
'assets/qyc.png',
width: 86,
height: 86,
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Expanded(
child: Text(
'犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬夜叉',
style: TextStyle(
color: Color(0xff262626), fontSize: 13),
)),
Padding(
padding: EdgeInsets.only(left: 10),
child: Text('X 1',
style: TextStyle(
color: Color(0xff262626), fontSize: 15)),
)
],
),
Padding(
padding: const EdgeInsets.only(top: 5),
child: Container(
decoration: BoxDecoration(
color: const Color(0xfff7f7f8),
borderRadius: BorderRadius.circular(2.0)),
padding: const EdgeInsets.only(
top: 3, right: 5, bottom: 3),
child: const Text('规格:粉色,小狗状',
style: TextStyle(
color: Color(0xff888888), fontSize: 11)),
),
),
const Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
'¥520',
style:
TextStyle(color: Color(0xff262626), fontSize: 14),
),
),
Padding(
padding: const EdgeInsets.only(top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: const [
Text(
'合计: ¥520',
style: TextStyle(
color: Color(0xff262626), fontSize: 16),
),
],
)),
],
),
))
]),
)
]),
));
}
}
效果图
滚动组件
ListView 构建方式
1. 默认构造函数
会一次性创建所有item 适用于item的情形
常用属性:
controller: 滚动控制器 , 用于监听列表滚动
shrinkWrap: 是否根据item得总长度来设置ListVirew的长度
Physics: 滚动效果, 比如:禁用滚动 , 滚动效果回弹等等
scrollDirection: 滚动方向
ListView(
children: [
// *10
Container(
color: Colors.blue,
height: 120,
),
],
));
2. 命名构造函数 builder
展示动态的长列表时
使用 itemBuilder 指定列表要展示的item
使用 itemCount 指定列表item的个数
ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
color: Colors.pink[100],
);
},
));
3. 命名构造函数 separated
列表中需要分割线时 可以使用
separated 自定义分割线
ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return Container(
height: 2,
color: const Color(0xfff7f7f8),
);
},
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
color: Colors.pink[100],
);
},
));
GridView 网格列表滚动
构建方式
默认构造函数
使用 gridDelegate 控制GridView子组件的布局方式
SliveGridDelegateWithFixedCrossAxisCount | 固定次轴子元素数量 |
SliverGridDelegateWithMaxCrossAxisExtent | 固定次轴子元素的最大长度 |
固定次轴子元素数量
GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
// 每行三个
crossAxisCount: 3,
// 主轴间距
mainAxisSpacing: 5,
// 次轴间距
crossAxisSpacing: 5,
// 次轴和主轴长度比列 宽高比
childAspectRatio: 4 / 3),
children: [
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
],
));
固定次轴子元素的最大长度
GridView(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
// 每个子元素宽度
maxCrossAxisExtent: 120,
// 主轴间距
mainAxisSpacing: 5,
// 次轴间距
crossAxisSpacing: 5,
// 次轴和主轴长度比列 宽高比
childAspectRatio: 4 / 3),
children: [
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
Container(
color: Colors.blue[100],
),
],
));
设置宽度后, 每行能放多少放多少
builder 构造函数
用于动态创建grid滚动列表
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 5,
crossAxisSpacing: 5,
childAspectRatio: 4 / 3),
// 指定个数
itemCount: 50,
itemBuilder: (BuildContext context, int index) {
return Container(height: 120, color: Colors.blue[100]);
}));
命名构造函数 count 和 extent
对上面两种布局的封装
GridView.count(
crossAxisCount: 3,
mainAxisSpacing: 5,
crossAxisSpacing: 5,
childAspectRatio: 4 / 3,
children: [
Container(
color: Colors.blue[100],
)
]));
CustomScrollView
相当于一个容易 把多个滚动组件放在一个盒子里 成为一个滚动组件 不然是独立的
CustomScrollView(
// 放子组件
slivers: [
// 网格组件
SliverGrid.count(
crossAxisCount: 3,
mainAxisSpacing: 5,
crossAxisSpacing: 5,
childAspectRatio: 4 / 3,
children: [],
),
// 列表组件 delegate类似itemBuilder
SliverList(
delegate: SliverChildBuilderDelegate(
childCount: 10,
(BuildContext context, int index) {
return Container(
padding: const EdgeInsets.only(bottom: 3),
height: 100,
child: Container(height: 120, color: Colors.blue[100]),
);
},
))
],
));
动画组件
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
// with SingleTickerProviderStateMixin 保证状态组件可以监听屏幕刷新
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
// 动画控制器
late AnimationController _animationController;
// 动画对象
late Animation<Offset> _animation;
@override
void initState() {
// 创建动画控制器
// vsync: 监听屏幕刷新 避免屏幕外动画
_animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 10));
// 创建动画对象
// Tween 专门定义起始和结束动画
_animation = Tween<Offset>(begin: Offset.zero, end: const Offset(1, -1))
.animate(_animationController);
// 启动动画
_animationController.fling();
super.initState();
}
@override
void dispose() {
// 卸载动画
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('商品列表'),
),
body: Center(
child: SlideTransition(
position: _animation,
child: Container(
width: 80,
height: 80,
color: Colors.pink[100],
),
),
));
}
}
dio 插件 网络请求
reques.dart
import 'package:dio/dio.dart';
class RequestsManager {
// 声明Dio
Dio? _dio;
// 私有静态属性
static RequestsManager? _instance;
// 私有命名构造函数
RequestsManager._initManager() {
// 配置请求
BaseOptions baseOptions = BaseOptions(
// 连接超时时间
connectTimeout: 5000,
// 响应超时时间
receiveTimeout: 3000,
// 根路径
baseUrl: 'https://pcapi-xiaotuxian-front.itheima.net/');
_dio = Dio(baseOptions);
// 请求拦截器
_dio!.interceptors.add(InterceptorsWrapper(onRequest: (
RequestOptions options,
RequestInterceptorHandler handler,
) {
// 发送请求前的拦截操作
// 设置请求头信息
options.headers = {
'Authorization': '',
'source-client': 'app',
};
return handler.next(options);
},
// 接收响应前拦截参数
onResponse: (response, handler) {
return handler.next(response);
},
// 捕获异常信息
onError: (DioError e, handler) {
return handler.next(e);
}));
}
// 创建单例对象并向外界提供单例对象的方法
factory RequestsManager() {
// 判断单例对象是否存在
_instance ??= RequestsManager._initManager();
return _instance!;
}
// 处理请求公共方法 低耦合
handleRequest(
String path,
String method, {
data,
Map<String, dynamic>? queryParmeters,
}) {
return _dio!.request(path,
data: data,
queryParameters: queryParmeters,
options: Options(method: method));
}
}
home_request.dart
import 'package:dio/dio.dart';
import 'package:erabbit_app_flutter/models/home_models.dart';
import 'package:erabbit_app_flutter/service/requests.dart';
class HomeApi {
// 获取首页数据
static Future<HomeModel> homeFetch() async {
Response response =
await RequestsManager().handleRequest('home/index', 'get');
dynamic ret = response.data['result'];
// 将result字段对应的首页网络数据(字典类型)传给首页总模型
HomeModel homeModel = HomeModel.fromJson(ret);
return homeModel;
}
}
home.dart
// 轮播图数据
List<ImageBannersModel>? imageBanners;
// 分类商品
List<CategoryGridsModel>? categoryGrids;
// 获取数据
void _loadData() async {
try {
HomeModel homeModel = await HomeApi.homeFetch();
setState(() {
imageBanners = homeModel.imageBanners;
categoryGrids = homeModel.categoryGrids;
categoryGrids!.addAll(categoryGrids!.sublist(1, 6));
});
debugPrint('$homeModel');
} catch (e) {
debugPrint('#e');
}
}
点击组件
GestureDetector(
child: Image.asset('assets/home_login.png'),
onTap: () {
// 逻辑
},
)
判断设备 设置状态栏
// 判断设备
if (Platform.isAndroid) {
// 设置状态栏透明
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(statusBarColor: Colors.transparent));
}