likes
comments
collection
share

flutter之敲木鱼练习跟练--续2

作者站长头像
站长
· 阅读数 62

接着上篇文章,动画部分的完成--我们来组装一下已经写好的几个模块

首先,需要建一个新页面 muyu_home.dart 有状态组件

 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();
  }
}

flutter 的语法是样式和函数是组合在一起的,不同于前端中vue的样式和函数拆分开来,一开始会感觉这种糅合在一起的有点不太舒服,但写的多了就会有所适应, 它们各有千秋! 那么回到小案例,组装写好的各个模块,代码如下

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!,
                )
            ],
          ))
        ],
      ),
    );
  }
}

此时运行一下程序,会看到主页面只有功德数和右侧按钮,木鱼图片可以先放一个本地图片。 组装完成主界面如下: flutter之敲木鱼练习跟练--续2

页面样式搞定了,需要考虑数据交互的问题!

再次回顾整体功能点:

  • 1.敲击木鱼,加功德数,并伴有文字动画由深变浅、由大到小的动画文字效果;
  • 2.界面功德数实时变化,随着敲击的次数增加;
  • 3.点击右侧音乐按钮,底部弹出音效列表面板,可切换敲木鱼音效;
  • 4.点击右侧图片按钮,底部弹出木鱼面板,可切换,不同木鱼所代表功德数值不同;
  • 5.点击导航栏右侧历史icon,弹出抽屉层,记录功德数历史列表,列表信息有id、敲击时间、增加的功德数,当前木鱼的图片

先来列举出数据:

1.功能点1里面,功德值总数、增加的功德值,

2.音效列表,存放木鱼音效

3.图片列表,存放图片地址,图片所代表的功德值

4.历史列表,存放当前敲击木鱼时间、木鱼id、木鱼样式、木鱼增加功德值

5.当前图片index,当前音效index,默认index

为了后续不在重复定义数据,直接封装数据类 woodenfishenity.dart

代码如下:

// 木鱼记录信息 封装成一个类 一个数据模型
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 使用方法:

flutter之敲木鱼练习跟练--续2

//即可生成一個id
  String id = uuid.v4();

当前时间获取:

 DateTime.now().millisecondsSinceEpoch,

在木鱼图片组件中封装了一个onKnock方法,敲击实现很简单,定义两个变量,一个为实时增加的值,一个为总数,敲一次总数进行累加,传值给功德数组件,实时增加数传值给动画组件;但别忘了历史面板,敲击时也需要将信息存入到历史列表中;_onkOCK方法如下: flutter之敲木鱼练习跟练--续2

在木鱼图片组件接收一个onTap方法,将存入的敲击木鱼信息传递给AnimateText[动画组件],

flutter之敲木鱼练习跟练--续2

动画组件接收父组件传递的值

flutter之敲木鱼练习跟练--续2

由于子组件有状态的Widget,所以不能直接使用 flutter之敲木鱼练习跟练--续2

动画效果有了,来看更为简单的功德总数

flutter之敲木鱼练习跟练--续2

功德面板代码如下:

import 'package:flutter/material.dart';

// 子widget 封装了功德数总值和 音效切换 图片切换
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,
    );
    return Stack(
      children: [
        Center(
          child: Text(
            '功德数: $count',
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        Positioned(
            right: 10,
            top: 10,
            child: Wrap(
              spacing: 8,
              direction: Axis.vertical,
              children: [
                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,
                  ),
                )
              ],
            )),
      ],
    );
  }
}

小贴士:有状态组件与无状态组件调用父组件的值的方式不同!

flutter之敲木鱼练习跟练--续2

接下来,重难点来了!!!

木鱼图片样式切换功能木鱼音效切换功能

思考:它两实现思路是一致的,只要会其一,其二变通

First:在敲木鱼的主页面中定义好木鱼图片列表、木鱼音效列表,以及默认音效的index,默认木鱼图片的index flutter之敲木鱼练习跟练--续2

这样数据流向是从上到下

Second:定义数据源后,对于音效需要进行初始化一下

flutter之敲木鱼练习跟练--续2

补充:音效播放插件-》 flutter之敲木鱼练习跟练--续2 木鱼图片面板组合木鱼音效面板也需要进行封装成单独模块

Third:木鱼图片面板模块 flutter之敲木鱼练习跟练--续2

界面分析:上下布局,内容左右布局-column+row+expand

这里主要分析一下实现切换图片的一个思路:

在主界面定义图片数据源--初始化时默认使用第一张图片,图片的index值作为切换图片的关键参数,

点击右侧图片按钮,从底部弹出面板->这里用到一个弹框类似以vue里面的showModal——flutter中的 showCupertinoModalPopup,

基本语法如下:

 showCupertinoModalPopup(
      context: context,
      builder: (BuildContext context) {
      //此处ImageOptionPanel 是封装好的木鱼选择面板组件
        return ImageOptionPanel(
          imageOptions: imageOptions,
          activeIndex: _activeIndex,
          onSelect: _onSelectImage,
        );
      },
    );

在这个弹框里面定义一个select切换方法,当前图片的index、及图片列表数据。

此时index值已传入子组件中,将index赋值给当前组件中激活的index,得到一个当前被选中的木鱼-

-在点击关闭或者切换时,做一个监听回调,将当前的index值回调给父组件-由父组件来判断回调之后的

index是否与原来一致,不一致则用新的index传入木鱼图片组件之中,一致则返回。

木鱼切换面板代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_app_seven/muyu/fishenity.dart';
import 'package:flutter_app_seven/muyu/imageoption.dart';

//切换图片面板
class ImageOptionPanel extends StatelessWidget {
  final List<ImageOption> imageOptions;
  // 在子widget写value changed 把数据回调给父widget的使用处
  final ValueChanged<int> onSelect;
  final int activeIndex;
  const ImageOptionPanel(
      {super.key,
      required this.imageOptions,
      required this.onSelect,
      required this.activeIndex});

  Widget _buildByIndex(int index) {
    // 传入一个参数index
    bool active = index == activeIndex;
    //监听回调手势
    return GestureDetector(
      onTap: () => onSelect(index),
      // 单条ImageItem option
      child: ImageOptionItem(
        option: imageOptions[index],
        active: active,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    // 自定义标签样式
    const TextStyle labelStyle =
        TextStyle(fontSize: 16, fontWeight: FontWeight.bold);
    const EdgeInsets padding =
        EdgeInsets.symmetric(horizontal: 8.0, vertical: 16);
    return Material(
      child: SizedBox(
        height: 300,
        child: Column(
          children: [
            Container(
                height: 46,
                alignment: Alignment.center,
                child: const Text("选择木鱼", style: labelStyle)),
            Expanded(
                child: Padding(
              padding: padding,
              child: Row(
                children: [
                  Expanded(child: _buildByIndex(0)),
                  const SizedBox(width: 10),
                  Expanded(child: _buildByIndex(1)),
                ],
              ),
            ))
          ],
        ),
      ),
    );
  }
}

切换木鱼音效的思路也是一致的,不做重复叙述

历史列表功能ending

点击右侧历史icon跳转到历史列表中,先定义个toHistory()方法

flutter之敲木鱼练习跟练--续2 flutter Navigator + MaterialPageRoute 实现页面跳转、跳转并传值、返回、返回并传值、返回到指定页面 flutter之敲木鱼练习跟练--续2

Widget使用ListView、ListTile、CircleAvatar等等,这些Widget可以学习一下。 历史列表代码如下:

// 敲木鱼历史记录
import 'package:flutter/material.dart';
import 'package:flutter_app_seven/muyu/fishenity.dart';
import 'package:intl/intl.dart';

// 分析一波:历史记录界面 listview 构成
class RecordHistoryList extends StatelessWidget {
  final List<MeritRecord> records;

  const RecordHistoryList({super.key, required this.records});

  @override
  Widget build(BuildContext context) {
    //  时间格式处理
    DateFormat format = DateFormat('yyyy年MM月dd日 HH:mm:ss');

    Widget? _buildItem(BuildContext context, int index) {
      MeritRecord merit = records[index]; //下标
      String date =
          format.format(DateTime.fromMillisecondsSinceEpoch(merit.timestamp));
      return ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.blue,
          backgroundImage: AssetImage(merit.image),
        ),
        title: Text('功德+${merit.value}'),
        subtitle: Text(merit.audio),
        trailing: Text(
          date,
          style: const TextStyle(fontSize: 12, color: Colors.grey),
        ),
      );
    }

    return Scaffold(
      appBar: _buildAppBar(),
      body: ListView.builder(
        itemBuilder: _buildItem,
        itemCount: records.length,
      ),
    );
  }
}

// buildAppBar    PreferredSizeWidget 作用是用修改appBard的尺寸
PreferredSizeWidget _buildAppBar() => AppBar(
      automaticallyImplyLeading: true,
      iconTheme: const IconThemeData(color: Colors.white),
      centerTitle: true, //标题居中
      title: const Text('功德记录',
          style: TextStyle(color: Colors.black, fontSize: 16)),
      elevation: 0,
      backgroundColor: Colors.white,
    );

每天输出一点,收获就多一点; 路漫漫其修远兮,吾将上下而求索

转载自:https://juejin.cn/post/7349052422059622436
评论
请登录