接着上篇文章,动画部分的完成--我们来组装一下已经写好的几个模块
首先,需要建一个新页面 muyu_home.dart 有状态组件
js
import 'package:flutter/material.dart';
// 主页面
class Muyu_Home extends StatefulWidget {
const Muyu_Home({super.key});
@override
State<Muyu_Home> createState() => _Muyu_HomeState();
}
class _Muyu_HomeState extends State<Muyu_Home> {
@override
Widget build(BuildContext context) {
return Container();
}
}
作为主页面,我们使用Scaffold widget包裹内容,关于Scaffold Widget 实际化起来我们可以看做一个基本骨架 有头、身体、胳膊、脚,这些构成页面的基本。
那么来看一下scaffold 的基础语法,flutter 的语法是样式和函数是组合在一起的,不同于前端中vue的样式和函数拆分开来,一开始会感觉这种糅合在一起的有点不太舒服,但写的多了就会有所适应, 它们各有千秋!
js
import 'package:flutter/material.dart';
// 主页面
class Muyu_Home extends StatefulWidget {
const Muyu_Home({super.key});
@override
State<Muyu_Home> createState() => _Muyu_HomeState();
}
class _Muyu_HomeState extends State<Muyu_Home> {
@override
Widget build(BuildContext context) {
return Scaffold(
// 导航栏--头部
appBar: AppBar(
// 頁面標題
title: Text('One More'),
// 背景颜色
backgroundColor: Colors.white,
// 文字样式
titleTextStyle: const TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
//图标主题
iconTheme: const IconThemeData(color: Colors.black),
//导航栏右侧菜单
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.history))
]),
//底部导航栏
bottomNavigationBar: BottomNavigationBar(
// 底部导航
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.business), label: 'Business'),
BottomNavigationBarItem(icon: Icon(Icons.school), label: 'School'),
],
),
// 页面内容--此处为一个center组件,内容会居中
body: const Center(
child: Text('Muyu'),
),
);
}
}
那么回到小案例,组装写好的各个模块,代码如下
js
import 'package:flutter/material.dart';
import 'package:flutter_app_seven/muyu/animate_text.dart';
import 'package:flutter_app_seven/muyu/countpanel.dart';
import 'package:flutter_app_seven/muyu/muyuImage.dart';
// 主页面
class Muyu_Home extends StatefulWidget {
const Muyu_Home({super.key});
@override
State<Muyu_Home> createState() => _Muyu_HomeState();
}
class _Muyu_HomeState extends State<Muyu_Home> {
@override
Widget build(BuildContext context) {
var activeImage;
var _cruRecord;
var _counter;
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
titleTextStyle: const TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
iconTheme: const IconThemeData(color: Colors.black),
title: const Text('电子木鱼'),
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.history))
]),
body: Column(
children: [
Expanded(
//功德数+右侧切木鱼样式和音乐按钮组件,传递三个参数给组件 1.功德数总值 切换音乐 切换图片
child: CountPanel(
count: _counter,
onTapSwitchAudio: () {},
onTapSwitchImage: () {})),
//下半部分组件--木鱼图片
Expanded(
child: Stack(
alignment: Alignment.topCenter,
children: [
MuyuAssetsImage(image: activeImage, onTap: () {}),
if (_cruRecord != null)
AnimateText(
record: _cruRecord!,
)
],
))
],
),
);
}
}
此时运行一下程序,会看到主页面只有功德数和右侧按钮,木鱼图片可以先放一个本地图片。 组装完成主界面如下:
再次回顾整体功能点:
- 1.敲击木鱼,加功德数,并伴有文字动画由深变浅、由大到小的动画文字效果;
- 2.界面功德数实时变化,随着敲击的次数增加;
- 3.点击右侧音乐按钮,底部弹出音效列表面板,可切换敲木鱼音效;
- 4.点击右侧图片按钮,底部弹出木鱼面板,可切换,不同木鱼所代表功德数值不同;
- 5.点击导航栏右侧历史icon,弹出抽屉层,记录功德数历史列表,列表信息有id、敲击时间、增加的功德数,当前木鱼的图片
样式好了,那么该写方法了,让我们敲起木鱼来! 在木鱼图片组件中封装了一个onKnock方法,敲击实现很简单,定义两个变量,一个为实时增加的值,一个为总数,敲一次总数进行累加,传值给功德数组件,实时增加数传值给动画组件;
点击时将当前木鱼信息存起来,针对需求点,需要存取id,木鱼图片样式、当前音效、当前时间、功德增加数;历史列表的信息会频繁使用,后续木鱼图片、木鱼音效也存进此类中。
js
// 木鱼记录信息 封装成一个类 一个数据模型
class MeritRecord {
final String id;
final int timestamp;
final int value;
final String image;
final String audio;
// 这个类指向它的实例
MeritRecord(this.id, this.audio, this.image, this.timestamp, this.value);
Map<String, dynamic> toJson() => {
"id": id, //记录的唯一标识
"timestamp": timestamp, //记录的时间戳
"image": image, //图片资源
"audio": audio, //音效名称
"value": value, //功德数
};
}
// 敲木鱼音效和样式的选择
class AudioOption {
final String name;
final String src;
const AudioOption(this.name, this.src);
}
// 维护不同样式敲木鱼的数据
class ImageOption {
final String name;
final String src;
///资源路径
final int min; //每次点击功德最小值
final int max; //每次点击积功德最大值
const ImageOption(this.max, this.min, this.name, this.src);
}
关于这里的id,我们使用flutter 的一个uuid 插件,它可以生成不同的id, 链接:pub.dev 使用方法:
js
//即可生成一個id
String id = uuid.v4();
当前时间获取:
js
DateTime.now().millisecondsSinceEpoch,
木鱼图片面板组合木鱼音效面板也需要进行封装成单独模块
1.木鱼图片面板模块
界面分析:上下布局,内容左右布局-column+row+expand
这里主要分析一下实现切换图片的一个思路:
在主界面定义图片数据源--初始化时默认使用第一张图片,图片的index值作为切换图片的关键参数,
点击右侧图片按钮,从底部弹出面板->这里用到一个弹框类似以vue里面的showModal------flutter中的 showCupertinoModalPopup,
基本语法如下:
js
showCupertinoModalPopup(
context: context,
builder: (BuildContext context) {
//此处ImageOptionPanel 是封装好的木鱼选择面板组件
return ImageOptionPanel(
imageOptions: imageOptions,
activeIndex: _activeIndex,
onSelect: _onSelectImage,
);
},
);
在这个弹框里面定义一个select切换方法,当前图片的index、及图片列表数据。此时index值已传入子组件中,将index赋值给当前组件中激活的index,得到一个当前被选中的木鱼--在点击关闭或者切换时,做一个监听回调,将当前的index值回调给父组件-由父组件来判断回调之后的index是否与原来一致,不一致则用新的index传入木鱼图片组件之中,一致则返回。
切换木鱼音效的思路也是大致的,木鱼音效这里使用一个插件:flame_audio 本案例中的语法
js
void _tempPlay(String src) async {
AudioPool pool = await FlameAudio.createPool(src, maxPlayers: 1);
pool.start();
}
历史列表实现思路: 点击时将存储列表传递进去,这里要对列表进行一个倒序处理,将数字转化为List才可以哦!
由于代码有点多,就不再一一分析了,需要源码可以滴滴我,自行分析。嗯对于初学者来说还是一个很有意义的项目,里面设计到了组件的封装思想、界面布局的widget、定义数据类等等。