Flutter从入门到实战
一共分为23个系列
①(Flutter、Dart环境搭建篇) 共3个内容 已更新
②(Dart语法1 篇) 共4个内容 已更新
③(Dart语法2 篇) 共2个内容 已更新
④(Flutter案例开发篇) 共4个内容 已更新
⑤(Flutter的StatelessWidget 共3个内容 已更新
⑥(Flutter的基础Widget篇) 共2个内容 已更新
⑦(布局Widget篇) 共1个内容 已更新
⑧(Flex、Row、Column以及Flexible、Stack篇) 共1个内容 已更新
⑨(滚动的Widget篇) 共4个内容 已更新
⑩(Dart的Future和网络篇) 共3个内容 已更新
⑪(豆瓣案例-1篇) 共3个内容 已更新
⑫(豆瓣案例-2篇) 共3个内容 已更新
⑬(Flutter渲染流程篇) 共3个内容 已更新
⑭(状态管理篇) 共3个内容 已更新
⑮(Flutter事件监听-以及路由使用篇) 共2个内容 已更新
⑯(Flutter的动画篇) 共4个内容 已更新
⑰(Flutter的主题、屏幕适配、测试篇) 共4个内容 已更新
⑱(Flutter的项目实战-美食广场篇) 共8个内容 已更新
官方文档说明
官方视频教程
Flutter的YouTube视频教程-小部件
⑱Flutter的项目实战-美食广场篇
①、Flutter的Extension
1、Extension的使用
Extension
的作用 : 可以为系统或者自定义的类扩展一些方法。和Swift的Extension
类似。
Flutter的Extension
是基于Dart SDK 2.6
版本以上才支持的
所以使用Extension
先需要修改项目的Dart SDK版本
1、Extension的使用 - 给上一篇文章的屏幕适配做一些扩展
之前屏幕适配的使用是
YHSizeFit.setPx(200.0)
我们能不能更加简洁一点
比如200.0.px()
、或者200.0.px
我们可以给系统的double、或者int 提供扩展方法
或者get方法
double_extension.dart
屏幕适配的写法
200.0.px
import '../shared/size_fit.dart';
extension DoubleFit on double {
// 写法 200.0.px()
// double px(){
// return YHSizeFit.setPx(this);
// }
// 写法 200.0rpx()
// double rpx(){
// return YHSizeFit.setRpx(this);
// }
// 写法 200.0.px
double get px {
return YHSizeFit.setPx(this);
}
// 写法 200.0.rpx
double get rpx {
return YHSizeFit.setRpx(this);
}
}
int_extension.dart
屏幕适配的写法
200.px
import '../shared/size_fit.dart';
extension IntFit on int {
// 写法 200.px()
// double px(){
// return YHSizeFit.setPx(this);
// }
// 写法 200.rpx()
// double rpx(){
// return YHSizeFit.setRpx(this);
// }
// 写法 200.px
double get px {
// 因为Dart里面没有转int类型。所以使用转double类型
return YHSizeFit.setPx(this.toDouble());
}
// 写法 200.rpx
double get rpx {
return YHSizeFit.setRpx(this.toDouble());
}
}
main.dart
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:learn_flutter/day14_screenFit/shared/size_fit.dart';
import 'package:learn_flutter/day14_screenFit/extension/double_extension.dart';
import 'package:learn_flutter/day14_screenFit/extension/int_extension.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// // 1. 手机的物理分辨率
// final physicalWidth = window.physicalSize.width;
// final physicalHeight = window.physicalSize.height;
// print('分辨率: $physicalWidth * $physicalHeight');
//
// // // 2.手机屏幕的大小(逻辑分辨率)
// // final width = MediaQuery.of(context).size.width;
// // final height = MediaQuery.of(context).size.height;
// // print('手机屏幕的大小(逻辑分辨率): $width * $height');
//
// // 绕过MediaQuery获取屏幕的物理分辨率
// // 2. 获取dpr - 屏幕分辨率的比例系数
// final dpr = window.devicePixelRatio;
//
// // 3. 宽度和高度
// final width = physicalWidth / dpr;
// final height = physicalHeight / dpr;
//
// print('宽度和高度: $width * $height');
//
// // 4. 状态栏高度
// final statusHeight = window.padding.top / dpr;
// print('状态栏高度: $statusHeight');
YHSizeFit.initialize(standardSize: 750);
return MaterialApp(
title: "学习模板 Demo",
theme: ThemeData(
primaryColor: Colors.red,
splashColor: Colors.orange,
),
home: YHHomePage(),
);
}
}
class YHHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// // 1. 手机的物理分辨率
// final physicalWidth = window.physicalSize.width;
// final physicalHeight = window.physicalSize.height;
// print('分辨率: $physicalWidth * $physicalHeight');
//
// 2.手机屏幕的大小(逻辑分辨率)
// final width = MediaQuery.of(context).size.width;
// final height = MediaQuery.of(context).size.height;
// print('手机屏幕的大小(逻辑分辨率): $width * $height');
//
print("状态栏的高度 ${YHSizeFit.statusHeight}");
print(200.0.px);
print(400.0.px);
return Scaffold(
appBar: AppBar(
title: Text("Flutter Widget"),
),
body: Center(
child: Container(
// 使用extension扩展更加简洁的写法
// YHSizeFit.setPx(200) ==> 200.0.px() - px()是方法
// YHSizeFit.setPx(200) ==> 200.0.px - 提供get方法
width: 200.px,
height: 200.0.px,
color: Colors.red,
alignment: Alignment.center,
child: Text("Hello World",style: TextStyle(fontSize: 20.0.px),)),
),
);
}
}
②、Flutter的项目实战 - 美食
最终效果图
1、新建项目
- 使用终端创建项目
指令为flutter create
项目名称
2、对项目进行配置
对移动端进行配置
- appid
- 应用名称
- icon(应用图标)
- launcher启动图
2.1、对项目进行配置(安卓-Android)
- appid
- 应用名称
- icon(应用图标)
应用图标大小
- launcher启动图
2.1、效果图 - 对项目进行配置(安卓-Android)
2.2、对项目进行配置(苹果-iOS)
- appid
- 应用名称
- icon(应用图标)
- 启动图片
2.2、效果图 - 对项目进行配置(苹果-iOS)
③、Flutter的项目目录结构划分
- pages
- widgets
- servers/network
- router
- viewmodel
- model
- shared
抽取 Core、UI
④、Flutter的主题相关
第17篇已经讲过了主题相关的东西 所以直接copy过来即可
YHAppTheme.dart
import 'package:flutter/material.dart';
// 封装主题
class YHAppTheme {
static const double smallFontSize = 16;
static const double normalFontSize = 22;
static const double largeFontSize = 24;
static final Color norTextColors = Colors.red;
static final Color darkTextColors = Colors.green;
// 由于需要传递context值 所以使用方法进行处理
ThemeData norTheme(BuildContext context){
return ThemeData(
// canvasColor 每个Scaffold的背景颜色
canvasColor: Color.fromRGBO(255, 254, 222, 1),
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: Colors.red
),
textTheme: TextTheme(
bodyText2: TextStyle(fontSize: smallFontSize),
),
);
}
ThemeData darkTheme(BuildContext context){
return ThemeData(
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: Colors.orange
),
textTheme: TextTheme(
bodyText2: TextStyle(fontSize: smallFontSize, color: darkTextColors),
),
);
}
}
⑤、Flutter的路由配置
路由类的封装
路由以及主题的使用class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { YHAppTheme appTheme = YHAppTheme(); return MaterialApp( title: "美食广场", // 主题 theme: appTheme.norTheme(context), // 路由 initialRoute: YHRoute.initialRoute, routes: YHRoute.routes, onGenerateRoute: YHRoute.generateRoute, onUnknownRoute: YHRoute.unknownRoute, ); } }
YHRoute.dart
import 'package:flutter/material.dart';
import '../../ui/pages/main/main.dart';
class YHRoute {
// 首页初始化路由
// 默认启动页面
static final String initialRoute = YHMainScreen.routeName;
// 路由映射
static final Map<String,WidgetBuilder> routes = {
YHMainScreen.routeName: (ctx) => YHMainScreen()
};
// 自己扩展
static final RouteFactory generateRoute = (settings) {
};
static final RouteFactory unknownRoute = (settings){
};
}
Main的MyApp封装
Main的MyApp封装 作用是用于方便管理
这样main.dart代码就很简洁了
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
YHAppTheme appTheme = YHAppTheme();
return MaterialApp(
title: "美食广场",
// 主题
theme: appTheme.norTheme(context),
// 路由
initialRoute: YHRoute.initialRoute,
routes: YHRoute.routes,
onGenerateRoute: YHRoute.generateRoute,
onUnknownRoute: YHRoute.unknownRoute,
);
}
}
⑥、Flutter的屏幕适配
size_fit.dart
第17篇已经讲过了 所以直接复制过来
import 'dart:ui';
class YHSizeFit {
static double physicalWidth = 0.0;
static double physicalHeight = 0.0;
static double screentWidth = 0.0;
static double screentHeight = 0.0;
static double dpr = 0.0;
static double statusHeight = 0.0;
static double rpx = 0.0 ;
static double px = 0.0; // 比如200的宽度高大小 在700 应该是乘以2
// standardSize 因为有些公司的屏幕适配的规范基数不一样 所以单独抽取出来
static void initialize({double standardSize = 750}){
// 1. 手机的物理分辨率
physicalWidth = window.physicalSize.width;
physicalHeight = window.physicalSize.height;
// print('分辨率: $physicalWidth * $physicalHeight');
// // 2.手机屏幕的大小(逻辑分辨率)
// final width = MediaQuery.of(context).size.width;
// final height = MediaQuery.of(context).size.height;
// print('手机屏幕的大小(逻辑分辨率): $width * $height');
// 绕过MediaQuery获取屏幕的物理分辨率
// 2. 获取dpr - 屏幕分辨率的比例系数
dpr = window.devicePixelRatio;
// 3. 宽度和高度
screentWidth = physicalWidth / dpr;
screentHeight = physicalHeight / dpr;
// print('宽度和高度: $screentWidth * $screentHeight');
// 4. 状态栏高度
statusHeight = window.padding.top / dpr;
// print('状态栏高度: $statusHeight');
// 5. 计算rpx的大小 - 以小程序的比例计算
// 以iPhone6为基准 不管什么屏幕 整体都按照750等分分割
rpx = screentWidth / standardSize;
px = screentWidth / standardSize * 2;
}
// 抽取方法 设置
static double setRpx(double size){
return rpx * size;
}
static double setPx(double size){
return px * size;
}
}
扩展 - double_extension.dart
import '../../ui/shared/size_fit.dart';
extension DoubleFit on double {
// 写法 200.0.px()
// double px(){
// return YHSizeFit.setPx(this);
// }
// 写法 200.0rpx()
// double rpx(){
// return YHSizeFit.setRpx(this);
// }
// 写法 200.0.px
double get px {
return YHSizeFit.setPx(this);
}
// 写法 200.0.rpx
double get rpx {
return YHSizeFit.setRpx(this);
}
}
扩展 - int _extension.dart
import '../../ui/shared/size_fit.dart';
extension IntFit on int {
// 写法 200.px()
// double px(){
// return YHSizeFit.setPx(this);
// }
// 写法 200.rpx()
// double rpx(){
// return YHSizeFit.setRpx(this);
// }
// 写法 200.px
double get px {
// // 因为Dart里面没有转int类型。所以使用转double类型
return YHSizeFit.setPx(this.toDouble());
}
// 写法 200.rpx
double get rpx {
return YHSizeFit.setRpx(this.toDouble());
}
}
⑦、Flutter的Json解析
json解析 工具类封装
json to dart 转换网站
https://javiercbk.github.io/json_to_dart/
加载json文件 进行解析是一个异步操作 。需要使用async
、await
、Future
json_parse.dart
import 'dart:convert';
import 'package:favorcate/core/model/category_model.dart';
import 'package:flutter/services.dart';
class JsonParse{
// static void getCatgoryData() async{
static Future<List<YHCategoryModel>> getCatgoryData() async{
// 1. 加载json文件
final jsonString = await rootBundle.loadString("assets/json/category.json");
// 2. 将JsonString 转成 Map/List
// json.decode(source) // 将字符串转成Map或者list
// json.encode(value) // 将json对象也就是Map或者List 转成 json字符串
// 因为转换是异步操作 所以要使用 async
final result = json.decode(jsonString);
// 3. 将Map中的内容转换成一个个对象
// 转换工具 https://javiercbk.github.io/json_to_dart/
final resultList = result["category"];
List<YHCategoryModel> categorys = [];
for (var json in resultList)
{
categorys.add(YHCategoryModel.fromJson(json));
}
return categorys;
}
}
model - category_model.dart 将颜色进行16进制封装
import 'package:flutter/material.dart';
import 'dart:ui';
class YHCategoryModel {
String? id;
String? title;
String? color;
Color cColor = Colors.red;
YHCategoryModel({this.id, this.title, this.color});
YHCategoryModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
color = json['color'];
// 1. 将color 转成16进制的数字
final colorInt = int.parse(color!,radix: 16);
// 2.将透明度加进去
cColor = Color(colorInt | 0xFF000000);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['color'] = this.color;
return data;
}
}
⑧、首页的抽取 - 渐变颜色
设置
YHHomeScreen.dart
import 'package:flutter/material.dart';
import 'home_content.dart';
class YHHomeScreen extends StatelessWidget {
// const YHHomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("美食广场"),
),
body: YHHomeContent(),
);
}
}
YHHomeContent.dart
加载首页内容。本地json数据 在初始化的时候 就进行解析
import 'package:favorcate/core/extension/int_extension.dart';
import 'package:flutter/material.dart';
import '../../../core/model/category_model.dart';
import '../../../core/services/json_parse.dart';
class YHHomeContent extends StatefulWidget {
const YHHomeContent({Key? key}) : super(key: key);
@override
State<YHHomeContent> createState() => _YHHomeContentState();
}
class _YHHomeContentState extends State<YHHomeContent> {
List<YHCategoryModel> _categorys = [];
@override
void initState() {
// TODO: implement initState
super.initState();
// 加载数据
JsonParse.getCatgoryData().then((res){
setState(() {
_categorys = res;
});
// print(res);
});
}
@override
Widget build(BuildContext context) {
return GridView.builder(
padding: EdgeInsets.all(20.px),
itemCount: _categorys.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 20.px,
mainAxisSpacing: 20.px,
childAspectRatio: 1.5,// 宽高比
), itemBuilder: (ctx,index){
final bgColor = _categorys[index].cColor;
return Container(
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12),
// 渐变颜色
gradient: LinearGradient(
colors: [
bgColor.withOpacity(.5),
bgColor
]
)
),
alignment: Alignment.center,
child: Text(_categorys[index].title!,style: TextStyle(fontWeight: FontWeight.bold),));
});
}
}