flutter之敲木鱼练习--基础案例
前言:
实践是检验真理的唯一标准,前期语法学的如何,一个小小demo试试手。 要想学的好,练习不可少;
一、本期敲木鱼demo主要功能介绍:
- 1.敲击木鱼,加功德数,并伴有文字动画由深变浅、由大到小的动画文字效果;
- 2.界面功德数实时变化,随着敲击的次数增加;
- 3.点击右侧音乐按钮,底部弹出音效列表面板,可切换敲木鱼音效;
- 4.点击右侧图片按钮,底部弹出木鱼面板,可切换,不同木鱼所代表功德数值不同;
- 5.点击导航栏右侧历史icon,弹出抽屉层,记录功德数历史列表,列表信息有id、敲击时间、增加的功德数,当前木鱼的图片
实现效果图如下:
功德数增加动画效果:
功德历史列表:
木鱼样式选择面板:
音效选择列表:
以上为实现效果图
二、敲木鱼案例功能拆分
从页面角度划分来看:
主界面
、历史列表页面
、图片切换面板
、音效切换面板
一共四大板块;
从布局的角度出发,围绕其功能来实现这些模块
2.1 首页
界面如下图所示,将其分为上中下垂直布局,其上模块再分为左右布局->为appBar Widget 中间模块为:中+右布局; 下模块:居中布局
布局清晰了,我们就很容易知道使用什么样的Widget更合适!
2.1.1 新建项目
新建一个flutter项目--名字任意取,此处使用的编辑器是VSCode,我们使用快捷键--ctrl+shift+p
选择第一个选项后,等待片刻
如果你需要创建一个APP,那么选择application类型,如果是混合原生app开发,则选择Module模式;
选择一个存项项目的文件夹即可,点击左侧select的按钮后,输入app的项目名
这时项目会进行初始化,需要稍等一段时间。
初始化完毕,可以打开你之前装好的虚拟机运行一下项目,检查是否无误。
选择一个虚拟机,run起来就可以了。
回归敲木鱼案例:
2.1.2 首页界面绘制
此前分析了大致布局,这里将细分的使用Widget 进行一一列举;
Scaffold
页面骨架
Scaffold 是一个路由页的骨架,拟人化可以看作是人体骨架,头【appbar
】左右胳膊【抽屉drawer
】
身体【body
】脚【bottomNavigationBar
】
基本语法
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( //导航栏
title: Text("App Name"), //标题名称
actions: <Widget>[ //导航栏右侧菜单
IconButton(icon: Icon(Icons.share), onPressed: () {}),//右侧icon按钮
],
),
drawer: MyDrawer(), //抽屉
bottomNavigationBar: BottomNavigationBar( // 底部导航
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
],
currentIndex: _selectedIndex,
fixedColor: Colors.blue,
onTap: _onItemTapped,
),
floatingActionButton: FloatingActionButton( //悬浮按钮
child: Icon(Icons.add),
onPressed:_onAdd
),
body:FirstPage(),//
);
}
AppBar
:App头部bar定义
IconButton
:图标按钮--可自定义图标按钮
TextStyle
:文字样式Widget
Column,Row
:从名字来理解-意为垂直 它是属于线性布局,它类似于android中的LinearLayout
控件,row 和column 都继承自Flex Widget
补充:
对于线性布局,有主轴和纵轴之分,如果布局是沿水平方向,那么主轴就是指水平方向,而纵轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而纵轴就是水平方向。在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignment
和CrossAxisAlignment
,分别代表主轴对齐和纵轴对齐。
对于Column的更多详解可以点击这个链接:
4.3 线性布局(Row和Column) | 《Flutter实战·第二版》 (flutterchina.club)
Expanded
:意为扩展,该Widget是Flex的孩子,使用频率较高
Stack
:层叠布局(Stack、Positioned)
层叠布局和 Web 中的绝对定位、Android 中的 Frame 布局是相似的,子组件可以根据距父容器四个角的位置来确定自身的位置。层叠布局允许子组件按照代码中声明的顺序堆叠起来。Flutter中使用Stack
和Positioned
这两个组件来配合实现绝对定位。Stack
允许子组件堆叠,而Positioned
用于根据Stack
的四个角来确定子组件的位置
思考:直接就在主页面上画所有内容,这样好吗?主页面的Widget是否很多,后期数据状态变化是否好做交互呢?
答案当然是否的,如果在一个页面上将功德数和木鱼图片的内容画到一起,后期change木鱼图片时,功德数动画变化时都会存在一些问题。 应用分而治之的思想,拆分主页模块为小组件,功德数上半部分、木鱼图片、敲木鱼动画效果、
模块化、组件化的思想在任何语言开发中都是相通的。
First componment:功德数上半部分组件
新建一个页面,countpanel.dart【上半部分模块】,使用st快捷输入,这里使用的是无状态Widget【StatelessWidget】
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,
),
)
],
)),
],
);
}
}
Second Componment:木鱼图片组件
主要内容为图片
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,
),
));
}
}
Third Componment:敲木鱼动画文字效果组件
动画效果:是点击向上飘,放大又缩小 、文字由深色逐渐变淡
ScaleTransition 弹性动画,scale:放大数 SlideTransition,根据位置变化的Widget FadeTransition 淡出的动画效果, AnimationController 动画控制器
因此动画文字模块代码如下:
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}'),
)),
);
}
}
下期在续...
转载自:https://juejin.cn/post/7348002324495155254