今日的程序码 => GITHUB
灵感来自於我在使用某某知名外送平台的时候,突然在想有这个画面要怎麽做,因为我觉得我在 TabBar 的部分,印象也没有相关的元件用,会做不出来,於是我就开始了实作这个画面,并努力解决。
很容易的,一看就会知道他是一个 Sliver
的效果。
main
- |myapp
- |homepage
- |FAppBar
- |PromoText
- |FlutterHead
- |DiscountCard
- |FIconButton
- |HeaderClip
- |CustomShape
- |CategorySection
下面我介绍一下属性,我认为重要的属性。
FlexibleSpaceBar 属性
这边设定一个通用的 Color,设定 13 种。
/// 一组13种颜色,可用於配置大多数元件的颜色属性
const ColorScheme scheme = const ColorScheme(
background: Color(0xFFF6F6F6),
surface: Color(0xFFFFFFFF),
primary: Color(0xFFC63065),
secondary: Color(0xFF1E1E1E),
onBackground: Color(0xFF1E1E1E),
onSurface: Color(0xFF1E1E1E),
onPrimary: Color(0xFFFFFFFF),
onSecondary: Color(0xFFFFFFFF),
primaryVariant: Color(0xFFC63065),
secondaryVariant: Color(0xFF000000),
error: Color(0xFFE74C3C),
onError: Color(0xFFFFFFFF),
brightness: Brightness.light,
);
什麽事 StruStyle?
相信大家看完这一篇文章就会懂了 => https://medium.com/@najeira/control-text-height-using-strutstyle-4b9b5151668b
/// text 的样式
class Helper {
Helper._internal();
static StrutStyle buildStrutStyle(TextStyle? textStyle) {
return StrutStyle(
forceStrutHeight: true,
fontWeight: textStyle?.fontWeight,
fontSize: textStyle?.fontSize,
fontFamily: textStyle?.fontFamily,
fontStyle: textStyle?.fontStyle,
fontFamilyFallback: textStyle?.fontFamilyFallback,
debugLabel: textStyle?.debugLabel,
);
}
}
这边我就直接来介绍会用到的资料有哪一些。因为程序码有点长,所以物件的宣告我没有列出来。
完整的程序码 => 假资料
class ExampleData {
ExampleData._internal();
/// 饮料的图片
static List<String> images = [
"https://d1sag4ddilekf6.cloudfront.net/compressed/items/6-CYXCTZAEEEECJE-CZAYA3CERF5ETJ/photo/b44c9b4be5044923b3f5b8f8f6e7e55b_1581506444759847068.jpg",
"https://d1sag4ddilekf6.cloudfront.net/compressed/items/6-CY21EXXWSEV2E2-CZKKV8MFGPUTMA/photo/321adfd29ded4d9eae3488848ecfbb05_1592997965388846905.jpg",
"https://d1sag4ddilekf6.cloudfront.net/compressed/items/6-CY4ETPUKCCCYTX-CZAYA3BKLEN2KE/photo/8d2d5939ec5a42269a0d8ec3c0a97e44_1581506429557055566.jpg",
"https://d1sag4ddilekf6.cloudfront.net/item/6-CY21EXXWUFW1CN-CZAYA25ZSEUJV6/photos/c3f51cd36f2344e28abae3a91b94ef9b_1581506376835073709.jpg",
"https://d1sag4ddilekf6.cloudfront.net/compressed/items/6-CZADR6NJMB3UL6-CZADR6UYL65GSE/photo/d4e13ca45a4747b78364dcf643095124_1580377235610503360.jpg",
];
/// 全部的资料
static PageData data = PageData(
title: " 瘾茶",
deliverTime: "外送 15 分钟",
bannerText:
"指定地区使用线上支付,满\$150现折\$30,输入优惠码【AUT30】,秋高Chill爽立即点!",
backgroundUrl:
"https://www.browncoffee.com.kh/uploads/ximg/item_menus/20210515062936c2531deff29845101d3f6f5691943c98.jpg",
rate: 4.2,
rateQuantity: 331,
optionalCard: OptionalCard(
title: "折扣 30%",
subtitle: "On the entire menu",
),
categories: [
category1,
category2,
category3,
category4,
category4,
category4,
category3,
],
);
/// 每一个 section 的资料
static Category category1 = Category(
title: "人气精选",
subtitle: "大家都点这些 ? 手刀点起来",
isHotSale: true,
foods: List.generate(
5,
(index) {
return Food(
name: "冰淇淋红茶",
price: "40",
comparePrice: "\$35",
imageUrl: images[index % images.length],
isHotSale: index == 3 ? true : false,
);
},
),
);
static Category category2 = Category(
title: "明星商品",
subtitle: null,
isHotSale: false,
foods: List.generate(
3,
(index) {
return Food(
name: "耶果青茶",
price: "35",
comparePrice: "\$30",
imageUrl: images[index % images.length],
isHotSale: index == 2 ? true : false,
);
},
),
);
static Category category3 = Category(
title: "找奶茶",
subtitle: null,
isHotSale: false,
foods: List.generate(
1,
(index) {
return Food(
name: "波霸奶茶",
price: "40",
comparePrice: "\$35",
imageUrl: images[index % images.length],
isHotSale: false,
);
},
),
);
static Category category4 = Category(
title: "找拿铁",
subtitle: null,
isHotSale: false,
foods: List.generate(
5,
(index) {
return Food(
name: "红茶拿铁",
price: "40",
comparePrice: "\$35",
imageUrl: images[index % images.length],
isHotSale: index == 3 ? true : false,
);
},
),
);
}
这边的话,我在程序码里面都写很清楚了,想要补充的点是 onCollapsed
和 onTap
是两个 callBack,
利用 callback 转换传递现在的 isCollapsed、index 给 HomePage 知道。
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {});
此时如果立刻执行下面的代码,是获取不到 BuildContext,因为 widget 还没有完成绘制,addPostFrameCallback 是 StatefulWidget 渲染结束的回调,只会被调用一次,之後 StatefulWidget 需要刷新 UI 也不会被调用
/// SliverAppBar
class FAppBar extends SliverAppBar {
final PageData data;
final BuildContext context;
final bool isCollapsed;
final double? expandedHeight;
final double collapsedHeight;
final TabController tabController;
final void Function(bool isCollapsed) onCollapsed;
final void Function(int index) onTap;
FAppBar({
required this.data,
required this.context,
required this.isCollapsed,
required this.expandedHeight, // 展开的高度。
required this.collapsedHeight,
required this.onCollapsed,
required this.onTap,
required this.tabController,
}) : super(
elevation: 4.0,
pinned: true,
forceElevated: true,
expandedHeight: expandedHeight);
/// super() 是用来继承父亲 Widget 里面的属性 or function
@override
Color? get backgroundColor => scheme.surface;
/// SliverBar 的 leading
@override
Widget? get leading {
return FIconButton(
iconData: Icons.arrow_back,
onPressed: () {},
);
}
/// SliverAppBar 的 actions
@override
List<Widget>? get actions {
return [
FIconButton(iconData: Icons.share_outlined, onPressed: () {}),
FIconButton(iconData: Icons.info_outline, onPressed: () {}),
];
}
/// SliverAppBar Title 慢慢出现的动画,只有在缩小才看得到,subTitle 也写在这。
@override
Widget? get title {
var textTheme = Theme.of(context).textTheme;
// AnimatedOpacity => https://api.flutter.dev/flutter/widgets/AnimatedOpacity-class.html
return AnimatedOpacity(
// 0 == invisible, 1 == visible
opacity: this.isCollapsed ? 0 : 1, // 判断 SliverAppBar 是展开还是缩小。
duration: const Duration(milliseconds: 250),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"瘾茶",
style: textTheme.subtitle1?.copyWith(color: scheme.onSurface),
strutStyle: Helper.buildStrutStyle(textTheme.subtitle1),
),
const SizedBox(height: 4.0),
Text(
data.deliverTime,
style: textTheme.caption?.copyWith(color: scheme.primary),
strutStyle: Helper.buildStrutStyle(textTheme.caption),
),
],
),
);
}
/// AppBar 的 bottom 不会被缩小。
@override
PreferredSizeWidget? get bottom {
return PreferredSize(
preferredSize: const Size.fromHeight(48),
child: Container(
color: scheme.surface,
child: TabBar(
isScrollable: true,
// 是否可以滚动
controller: tabController,
// https://api.flutter.dev/flutter/material/TabController-class.html
indicatorPadding: const EdgeInsets.symmetric(horizontal: 16.0),
indicatorColor: scheme.primary,
// tabBar 下面一条线的颜色
labelColor: scheme.primary,
// 被选到标签颜色
unselectedLabelColor: scheme.onSurface,
// 为被选到的颜色
indicatorWeight: 3.0,
// 下面标签的高度
tabs: data.categories.map((e) {
return Tab(text: e.title);
}).toList(),
// 想要把 list 里面的 data 转换成 Widget
onTap: onTap,
),
),
);
}
/// 只有展开才看得到的 FlexibleSpaceBar 属性
@override
Widget? get flexibleSpace {
return LayoutBuilder(
builder: (
BuildContext context,
BoxConstraints constraints,
) {
// 现在整块 flexibleSpace 的高度
final top = constraints.constrainHeight();
final collapsedHight =
MediaQuery.of(context).viewPadding.top + kToolbarHeight + 48;
// 尚未展开的 flexibleSpace 高度。
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
// 此时如果立刻执行下面的代码,是获取不到 BuildContext,因为 widget 还没有完成绘制
// addPostFrameCallback 是 StatefulWidget 渲染结束的回调,只会被调用一次,之後 StatefulWidget 需要刷新 UI 也不会被调用
onCollapsed(collapsedHight != top); // 利用 callback 转换传递现在的 isCollapsed
});
return FlexibleSpaceBar(
collapseMode: CollapseMode.pin, // 展开模式
background: Column(
children: [
Stack(
children: [
PromoText(title: data.bannerText), // 粉红色部分(有点类似广告)(宣传文字)
FlutterHead(), // flutter 头像
Column(
children: [
HeaderClip(data: data, context: context),
// 餐厅上方图片,有形状的那个。
SizedBox(height: 90),
],
),
],
),
DiscountCard(
title: data.optionalCard.title,
subtitle: data.optionalCard.subtitle,
),
],
),
);
},
);
}
}
其他的元件将会在後面两天补充讲完。
>>: [DAY 26] _STM32 看门狗简介_独立看门狗(2)
Day 29: Flutter Development tags: Others Quest htt...
第12 届iT邦帮忙铁人赛系列文章 (Day28) SignalR是实现即时通讯的框架,如下图,在S...
教材网址 https://coding104.blogspot.com/2021/06/java-s...
如题,有下载 pip install pandas pip install pandas_datar...
Rust是静态型别语言,所以在编译时需要知道变数的型别是什麽 前面的程序范例很多是没有宣吿型别但是却...