前言:
实践是检验真理的唯一标准,前期语法学的如何,一个小小demo试试手。 要想学的好,练习不可少;
一、本期敲木鱼demo主要功能介绍:
主界面木鱼可以敲击,加功德
功德历史列表
木鱼样式功能切换
木鱼音效功能切换
实现效果图如下:
功德数增加动画效果:
功德历史列表:
木鱼样式选择面板:
音效选择列表:
以上为实现效果图
二、敲木鱼案例功能拆分
2.1 主页面的部分划分
页面布局角度: 主页面:上下布局--各封装一个模块,上布局 :Stack+Center+Positioned 下布局 :Center, 整体布局为:Column+Expanded

布局样式我们进行上下划分,功德数、音效按钮、图片按钮为上半部分,木鱼图片为下半部分
上半部分为图片、音效功德数这些模块设计到后续功能,我们做一个单独的模块封装。
新建一个页面,countpanel.dart【上半部分模块】,使用st快捷输入,这里使用的是无状态Widget【StatelessWidget】
js
import 'package:flutter/material.dart';
class CountPanel extends StatelessWidget {
final int count;//定义功德总数
final VoidCallback onTapSwitchAudio;//定义切换音效方法
final VoidCallback onTapSwitchImage;//定义切换木鱼图片方法
const CountPanel({
super.key,
required this.count,
required this.onTapSwitchAudio,
required this.onTapSwitchImage,
});
@override
Widget build(BuildContext context) {
//自定义按钮内容样式
final ButtonStyle style = ElevatedButton.styleFrom(
minimumSize: const Size(50, 50),
padding: EdgeInsets.zero,
backgroundColor: Colors.green,
elevation: 0,
);
//stack 组件 层叠布局和 Web 中的绝对定位、Android 中的 Frame 布局是相似的,子组件可以根据距父容器四个角的位置来确定自身的位置。
return Stack(
children: [
//center 组件顾名思义,居中内容的组件
Center(
child: Text(
'功德数: $count',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
//右侧音效、图片切换按钮--使用定位来实现 Positioned
Positioned(
right: 10,
top: 10,
//# Wrap 流失布局,当一行超出屏幕流失布局会自动拆分,进行换行
child: Wrap(
spacing: 8,
direction: Axis.vertical,
children: [
//`ElevatedButton` 即"漂浮"按钮,自带点击事件:onPressed
ElevatedButton(
style: style,
onPressed: onTapSwitchAudio,
child: const Icon(
Icons.music_note_outlined,
color: Colors.white,
),
),
ElevatedButton(
style: style,
onPressed: onTapSwitchImage,
child: const Icon(
Icons.image,
color: Colors.white,
),
)
],
)),
],
);
}
}
下半部分图片看似只有一个,但是其实背后是包含着木鱼图片和文字动画效果,这里也封装为一个单独的模块 切换图片时,便于接收不同的imgUrl.
图片组件模块如下:
// 存放木鱼在首页点击图片
js
class MuyuAssetsImage extends StatelessWidget {
final String image;
final VoidCallback onTap; //接收一个回调方法
const MuyuAssetsImage({super.key, required this.image, required this.onTap});
@override
Widget build(BuildContext context) {
return Center(
// gestureDetector 组件监听手势回调
child: GestureDetector(
onTap: onTap,
child: Image.asset(
image,//接收父组件传递的url值
height: 200,
),
));
}
}
那么图片模块有了,我们来看一下文字动画效果如何实现
动画效果:是点击向上飘,放大又缩小 、文字由深色逐渐变淡
ScaleTransition 弹性动画,scale:放大数 SlideTransition ,根据位置变化的Widget
FadeTransition 淡出的动画效果, AnimationController 动画控制器

因此动画文字模块代码如下:
js
import 'package:flutter/material.dart';
import 'package:flutter_app_seven/muyu/fishenity.dart';
// 敲木鱼动画文字组件 --之前有bug 点击切换图片样式时会触发动画
class AnimateText extends StatefulWidget {
final MeritRecord record;
const AnimateText({super.key, required this.record});
@override
State<AnimateText> createState() => _AnimateTextState();
}
// 加入这个用于加入动画控制器
class _AnimateTextState extends State<AnimateText>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> opacity;
late Animation<Offset> position;
late Animation<double> scale;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
// 动画器属性配置,从开始
opacity = Tween(begin: 1.0, end: 0.0).animate(controller);//透明度
scale = Tween(begin: 1.0, end: 0.9).animate(controller);//放大到缩小值
//位置变化
position = Tween<Offset>(
begin: const Offset(0, 5),
end: Offset.zero,
).animate(controller);
controller.forward();
}
@override
// 在父widget中调用setState
void didUpdateWidget(covariant AnimateText oldWidget) {
super.didUpdateWidget(oldWidget);
// 避免功德未变化的情况下,启动动画场景
if (oldWidget.record.id != widget.record.id) {
controller.forward(from: 0);
}
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
//过渡消失组件 还缺少缩放动画
// 旋转
return ScaleTransition(
scale: scale,
// 移动
child: SlideTransition(
position: position,
child: FadeTransition(
opacity: opacity,
child: Text('功德数+${widget.record.value}'),
)),
);
}
}
下期在续...