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的YouTube视频教程-小部件
项目搭建前配置
由于我的工程项目用于学习操作
为了方便管理 我直接在项目里面创建豆瓣项目的案例
但是不能同时启动两个main.dart
项目配置其他启动的main.dart
①、Flutter 星星评分Widget
封装成一个Widget 考虑用的是
StatelessWidget还是StateFulWidget
考虑点
- 这个Widget是否可以控制的。比如点击改变状态
- 如果是能点击、改变状态的话 就封装成FulWidget
- 如果只是单纯做展示的话 就封装成lessWidget
考虑点
需要传递什么
- 评分的图片
- 评分的评分数 当前分数、最大分数
- 评分的数量
- 评分的尺寸大小
- 评分之间的间距
- 评分的颜色 未选中、已选中
- 评分的点击事件
需要用到什么系统的Widget
Stack (全部星星的包裹)
Row (每个星星的整体)
Icon (星星的图标)
Text (星星的评分数)
CustomClipper
需要用到系统的裁剪功能比如 0.2星星的情况
Flutter 星星评分Widget 带注释的代码
// 1.星星评分Widget
class YHStarRating extends StatefulWidget {
final double? rating; // 评分数
final double maxRating; // 最大评分数
final int count; // 评分总数量
final double size; // 评分大小
final Color unselectedColor; // 未选中颜色
final Color selectedColor; // 选中的颜色
final Widget unselectedImage; // 未选中图片
final Widget selectedImage; // 选中图片
YHStarRating({
// 必传参数 this.rating 评分数
@required this.rating,
this.maxRating = 10,
this.count = 5,
this.size = 30,
this.unselectedColor = const Color(0xffbbbbbb),
this.selectedColor = const Color(0xffff0000),
Widget? unselectedImage,
Widget? selectedImage,
}): this.unselectedImage = unselectedImage ?? Icon(Icons.star_border,color: unselectedColor,size: size),
this.selectedImage = selectedImage ?? Icon(Icons.star, color: selectedColor, size: size);// 判断有没有图片传递进行。如果没有使用默认的Icon创建的星星
// const YHStarRating({Key? key}) : super(key: key);
@override
State<YHStarRating> createState() => _YHStarRatingState();
}
class _YHStarRatingState extends State<YHStarRating> {
@override
Widget build(BuildContext context) {
return Stack(
children: [
builderStarRow(buildUnselectedStar()),
builderStarRow(buildSelectedStar()),
// Row(
// // 由于内容是填充尽可能大 。所以需要设置内容为最小
// mainAxisSize: MainAxisSize.min,
// // 动态决定多少个星星 并且封装函数
// children:buildUnselectedStar(),
// ),
// Row(
// // 由于内容是填充尽可能大 。所以需要设置内容为最小
// mainAxisSize: MainAxisSize.min,
// children: buildSelectedStar(),
// )
],
);
}
// 封装函数 提高阅读代码
// 默认星星
List<Widget> buildUnselectedStar(){
return List.generate(widget.count, (index) {
return widget.unselectedImage;
// return Icon(Icons.star_border,color: widget.unselectedColor,size: widget.size);
});
}
// 选中星星的函数
List<Widget> buildSelectedStar() {
// 案例
// 比如一共是10分 那么 评分 8分 就是4个满星 1个空星
// 比如一共是10分 那么 评分是 5 分 那么就是2个满星 一个半星
// 先计算一个星是多少分
// 总分数 / 总个数 = 每个星的分数
// 10 / 5 = 每个星的分数
// 1.创建stars
List<Widget> stars = [];
// final star = Icon(Icons.star, color: widget.selectedColor, size: widget.size);
final star = widget.selectedImage;
// 2. 构建满填充的 star
double oneValue = widget.maxRating / widget.count;
int entireCount = (widget.rating! / oneValue).floor(); // 使用向下取整 不管是4.9 都只能是4个
for (var i = 0; i < entireCount; i++)
{
stars.add(star);
}
// 3. 构建部分填充的 star 进行裁剪(ClipRect)
// 计算部分填充的值
// 总星星个数 - 总整个填充的
// 比如总评分数是3.5 - 3 = 0.5
// (widget.rating! / oneValue) - entireCount
// 然后计算占据的比例
// ((widget.rating! / oneValue) - entireCount) *widget.size
double leftWidth = ((widget.rating! / oneValue) - entireCount) * widget.size;
final halfStar = ClipRect(
clipper: YHStarClipper(leftWidth),
child:star);
stars.add(halfStar);
// 满分超出范围的处理
if(stars.length > widget.count)
{
return stars.sublist(0,widget.count);// 裁剪
}
return stars;
}
// Row的函数抽取
Row builderStarRow(List<Widget> children){
return Row(mainAxisSize: MainAxisSize.min,children:children);
}
}
// 自定义裁剪
// 我们星星当做一个矩形来进行裁剪 所以需要在CustomClipper后面进行一个声明 Rect
class YHStarClipper extends CustomClipper<Rect>{
double width;
YHStarClipper(this.width);
// 自定义裁剪 必须实现两个抽象方法
@override
Rect getClip(Size size) {
// TODO: implement getClip
// 裁剪的位置
return Rect.fromLTRB(0, 0, width, size.height); // size.height 是Widget的高度
}
// 重新裁剪
@override
bool shouldReclip(covariant YHStarClipper oldClipper) {
// TODO: implement shouldReclip
// 当旧的宽度 和 当前的宽度不一致才需要重新裁剪
return oldClipper.width != this.width;
}
}
Flutter 星星评分Widget 不带注释的代码
// 1.星星评分Widget
class YHStarRating extends StatefulWidget {
final double? rating; // 评分数
final double maxRating; // 最大评分数
final int count; // 评分总数量
final double size; // 评分大小
final Color unselectedColor; // 未选中颜色
final Color selectedColor; // 选中的颜色
final Widget unselectedImage; // 未选中图片
final Widget selectedImage; // 选中图片
YHStarRating({
@required this.rating,
this.maxRating = 10,
this.count = 5,
this.size = 30,
this.unselectedColor = const Color(0xffbbbbbb),
this.selectedColor = const Color(0xffff0000),
Widget? unselectedImage,
Widget? selectedImage,
}): this.unselectedImage = unselectedImage ?? Icon(Icons.star_border,color: unselectedColor,size: size),
this.selectedImage = selectedImage ?? Icon(Icons.star, color: selectedColor, size: size);
@override
State<YHStarRating> createState() => _YHStarRatingState();
}
class _YHStarRatingState extends State<YHStarRating> {
@override
Widget build(BuildContext context) {
return Stack(
children: [
builderStarRow(buildUnselectedStar()),
builderStarRow(buildSelectedStar()),
],
);
}
// 封装函数 提高阅读代码
// 默认星星
List<Widget> buildUnselectedStar(){
return List.generate(widget.count, (index) {
return widget.unselectedImage;
// return Icon(Icons.star_border,color: widget.unselectedColor,size: widget.size);
});
}
// 选中星星的函数
List<Widget> buildSelectedStar() {
List<Widget> stars = [];
final star = widget.selectedImage;
double oneValue = widget.maxRating / widget.count;
int entireCount = (widget.rating! / oneValue).floor();
for (var i = 0; i < entireCount; i++)
{
stars.add(star);
}
double leftWidth = ((widget.rating! / oneValue) - entireCount) * widget.size;
final halfStar = ClipRect(
clipper: YHStarClipper(leftWidth),
child:star);
stars.add(halfStar);
// 满分超出范围的处理
if(stars.length > widget.count)
{
return stars.sublist(0,widget.count);
}
return stars;
}
Row builderStarRow(List<Widget> children){
return Row(mainAxisSize: MainAxisSize.min,children:children);
}
}
// 自定义裁剪
class YHStarClipper extends CustomClipper<Rect>{
double width;
YHStarClipper(this.width);
@override
Rect getClip(Size size) {
return Rect.fromLTRB(0, 0, width, size.height);
}
@override
bool shouldReclip(covariant YHStarClipper oldClipper) {
return oldClipper.width != this.width;
}
}
Flutter 星星评分Widget使用方式
class YHiOSHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("豆瓣"),
),
body: Center(
// 评分数、最大评分数、星星个数
child: YHStarRating(rating: 7,maxRating: 5, count: 5),
),
);
}
}
效果图 - Flutter 星星评分Widget 带注释的代码
②、Flutter 虚线Widget
考虑点
- 虚线的方向
- 虚线的方向的宽度
- 虚线的方向的高度
- 虚线的颜色
使用到系统的Widget
- 方向
- sabox 每一根虚线
class YHiOSHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("豆瓣"),
),
body: Center(
child:Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 200,
child: YHDasheLine(dashedWidth: 8,count: 10),
),
Container(
height: 200,
child: YHDasheLine(axis: Axis.vertical, dashedHeight: 5,count: 15),
),
],
),
),
);
}
}
// 1.虚线
// 只负责展示 所以直接使用StatelessWidget 即可
class YHDasheLine extends StatelessWidget {
// 1.提供的方向
final Axis axis;
final double dashedWidth; // 虚线的宽度
final double dashedHeight; // 虚线的高度
final int count; // 虚线的数量
final Color color; // 虚线的颜色
YHDasheLine({
this.axis = Axis.horizontal,
this.dashedWidth = 1,
this.dashedHeight = 1,
this.count = 10,
this.color = Colors.red,
});
@override
Widget build(BuildContext context) {
return Flex(
direction: axis,
// 间距 使用 主轴对齐方式
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// 用不上索引 使用 _ ti
children: List.generate(count, ( _ ) {
return SizedBox(
width: dashedWidth,
height: dashedHeight,
// SizedBox没有 颜色属性 使用 Child 给SizedBox进行一个装饰
child: DecoratedBox(
decoration: BoxDecoration(color: color),
),
);
}),
);
}
}
效果图 Flutter 虚线Widget
③、Flutter TabbarWidget
考虑点
使用系统哪些Widget去实现
- Stack 能将内容进行包裹
- 使用 IndexedStack 进行包裹page页面
- 进行main、初始化配置、多个page页面进行抽取
总结
item的封装、页面和页面内容的封装、初始化列表的封装
如果有网络请求、UI 建议多个文件夹管理(core,UI)
1.main.dart
import 'package:flutter/material.dart';
import 'package:learn_flutter/douban/pages/main/bottom_bar_item.dart';
// import 'package:learn_flutter/douban/pages/main/initialize_item.dart';// 绝对引入
import 'initialize_item.dart';// 相对引入 相当于在main.dart这个文件夹 引入initialize_item.dart 说明 initialize_item.dart和main.dart在同一个文件夹
class YHMainPage extends StatefulWidget {
@override
State<YHMainPage> createState() => _YHMainPageState();
}
class _YHMainPageState extends State<YHMainPage> {
int _currentIndex = 0; // 记录当前tabbar点击的索引
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: config_pages
),
bottomNavigationBar: BottomNavigationBar(
// 点击选择默认字体会有一个放大和缩小
// 因为系统设置 选中为 14 默认为 12
selectedFontSize: 14,
unselectedFontSize: 14,
// selectedItemColor: Colors.red,
// unselectedItemColor: Colors.blue,
// item点击 记录切换
currentIndex: _currentIndex,
// 当item 超过4个就需要设置type
type: BottomNavigationBarType.fixed,
items: config_items,
onTap: (index){
setState(() {
_currentIndex = index;
});
},
),
);
}
//1.BottomNavigationBarItem 封装函数
BottomNavigationBarItem buildBottomItem(iconName,title){
return BottomNavigationBarItem(
label: title,
icon: Image.asset("assets/images/tabbar/$iconName.png",width: 32),
activeIcon: Image.asset("assets/images/tabbar/${iconName}_active.png",width: 32)
);
}
}
// 2.BottomNavigationBarItem 封装类
2. BottomNavigationBarItem 封装
import 'package:flutter/material.dart';
class YHBottomBarItem extends BottomNavigationBarItem {
YHBottomBarItem(String iconName,String title):super(
label: title,
icon: Image.asset("assets/images/tabbar/$iconName.png",width: 32),
activeIcon: Image.asset("assets/images/tabbar/${iconName}_active.png",width: 32),
);
}
3. 多Page页面封装
3.1 home.dart
import 'package:flutter/material.dart';
import 'home_content.dart';
class YHHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: YHHomeContent(),
);
}
}
3.2 home_content.dart
import 'package:flutter/material.dart';
class YHHomeContent extends StatefulWidget {
@override
State<YHHomeContent> createState() => _YHHomeContentState();
}
class _YHHomeContentState extends State<YHHomeContent> {
@override
Widget build(BuildContext context) {
return Center(
child: Text("首页内容",style: TextStyle(fontSize: 30,color: Colors.red)),
);
}
}
3.3 subject.dart
import 'package:flutter/material.dart';
import 'subject_content.dart';
class YHSubjectPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: YHSubjectContent(),
);
}
}
3.4 subject_content.dart
import 'package:flutter/material.dart';
class YHSubjectContent extends StatefulWidget {
@override
State<YHSubjectContent> createState() => _YHHomeContentState();
}
class _YHHomeContentState extends State<YHSubjectContent> {
@override
Widget build(BuildContext context) {
return Center(
child: Text("书影音内容",style: TextStyle(fontSize: 30,color: Colors.red)),
);
}
}
4.初始化配置
import 'package:flutter/cupertino.dart';
import 'package:learn_flutter/douban/pages/home/home.dart';
import '../subject/subject.dart';
import 'bottom_bar_item.dart';
// 初始化抽取
// 1. tabbar item
List<YHBottomBarItem> config_items = [
YHBottomBarItem("home", "首页"),
YHBottomBarItem("subject", "书影音"),
YHBottomBarItem("group", "小组"),
YHBottomBarItem("mall", "市集"),
YHBottomBarItem("profile", "我的"),
];
// 1. 每个tabbar的页面
List<Widget> config_pages = [
YHHomePage(),
YHSubjectPage(),
YHHomePage(),
YHSubjectPage(),
YHHomePage(),
];