likes
comments
collection
share

flutter之敲木鱼练习--基础案例

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

前言:

实践是检验真理的唯一标准,前期语法学的如何,一个小小demo试试手。 要想学的好,练习不可少;

一、本期敲木鱼demo主要功能介绍:

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

实现效果图如下: flutter之敲木鱼练习--基础案例

功德数增加动画效果: flutter之敲木鱼练习--基础案例

功德历史列表: flutter之敲木鱼练习--基础案例

木鱼样式选择面板: flutter之敲木鱼练习--基础案例

音效选择列表: flutter之敲木鱼练习--基础案例

以上为实现效果图

二、敲木鱼案例功能拆分

从页面角度划分来看: 主界面历史列表页面图片切换面板音效切换面板一共四大板块; 从布局的角度出发,围绕其功能来实现这些模块

2.1 首页

界面如下图所示,将其分为上中下垂直布局,其上模块再分为左右布局->为appBar Widget 中间模块为:中+右布局; 下模块:居中布局

布局清晰了,我们就很容易知道使用什么样的Widget更合适!

2.1.1 新建项目

新建一个flutter项目--名字任意取,此处使用的编辑器是VSCode,我们使用快捷键--ctrl+shift+p flutter之敲木鱼练习--基础案例 选择第一个选项后,等待片刻

flutter之敲木鱼练习--基础案例 如果你需要创建一个APP,那么选择application类型,如果是混合原生app开发,则选择Module模式;

flutter之敲木鱼练习--基础案例 选择一个存项项目的文件夹即可,点击左侧select的按钮后,输入app的项目名

flutter之敲木鱼练习--基础案例 这时项目会进行初始化,需要稍等一段时间。

flutter之敲木鱼练习--基础案例

初始化完毕,可以打开你之前装好的虚拟机运行一下项目,检查是否无误。

flutter之敲木鱼练习--基础案例

flutter之敲木鱼练习--基础案例 选择一个虚拟机,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定义

flutter之敲木鱼练习--基础案例 IconButton:图标按钮--可自定义图标按钮 flutter之敲木鱼练习--基础案例 TextStyle:文字样式Widget

flutter之敲木鱼练习--基础案例 Column,Row:从名字来理解-意为垂直 它是属于线性布局,它类似于android中的LinearLayout控件,row 和column 都继承自Flex Widget

补充: 对于线性布局,有主轴和纵轴之分,如果布局是沿水平方向,那么主轴就是指水平方向,而纵轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而纵轴就是水平方向。在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignmentCrossAxisAlignment,分别代表主轴对齐和纵轴对齐。

对于Column的更多详解可以点击这个链接: 4.3 线性布局(Row和Column) | 《Flutter实战·第二版》 (flutterchina.club) flutter之敲木鱼练习--基础案例

Expanded:意为扩展,该Widget是Flex的孩子,使用频率较高 flutter之敲木鱼练习--基础案例 Stack:层叠布局(Stack、Positioned) 层叠布局和 Web 中的绝对定位、Android 中的 Frame 布局是相似的,子组件可以根据距父容器四个角的位置来确定自身的位置。层叠布局允许子组件按照代码中声明的顺序堆叠起来。Flutter中使用StackPositioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置

flutter之敲木鱼练习--基础案例

思考:直接就在主页面上画所有内容,这样好吗?主页面的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 动画控制器

flutter之敲木鱼练习--基础案例

因此动画文字模块代码如下:

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
评论
请登录