flutter系列之:做一个下载按钮的动画

news2024/11/17 14:20:15

文章目录

  • 简介
  • 定义下载的状态
  • 定义DownloadButton的属性
  • 让DownloadButton的属性可以动态变化
  • 定义downloadController
  • 定义DownloadButton的细节
  • 总结

简介

我们在app的开发过程中经常会用到一些表示进度类的动画效果,比如一个下载按钮,我们希望按钮能够动态显示下载的进度,这样可以给用户一些直观的印象,那么在flutter中一个下载按钮的动画应该如何制作呢?

一起来看看吧。

定义下载的状态

我们在真正开发下载按钮之前,首先定义几个下载的状态,因为不同的下载状态导致的按钮展示样子也是不一样的,我们用下面的一个枚举类来设置按钮的下载状态:

enum DownloadStatus {
  notDownloaded,
  fetchingDownload,
  downloading,
  downloaded,
}

基本上有4个状态,分别是没有下载,准备下载但是还没有获取到下载的资源链接,获取到下载资源正在下载中,最后是下载完毕。

定义DownloadButton的属性

这里我们需要自定义一个DownloadButton组件,这个组件肯定是一个StatelessWidget,所有的状态信息都是由外部传入的。

我们需要根据下载状态来指定DownloadButton的样式,所以需要一个status属性。下载过程中还有一个下载的进度条,所以我们需要一个downloadProgress属性。

另外在点击下载按钮的时候会触发onDownload事件,下载过程中可以触发onCancel事件,下载完毕之后可以出发onOpen事件。

最后因为是一个动画组件,所以还需要一个动画的持续时间属性transitionDuration。

所以我们的DownloadButton需要下面一些属性:

class DownloadButton extends StatelessWidget {
  ...
  const DownloadButton({
    super.key,
    required this.status,
    this.downloadProgress = 0.0,
    required this.onDownload,
    required this.onCancel,
    required this.onOpen,
    this.transitionDuration = const Duration(milliseconds: 500),
  });

让DownloadButton的属性可以动态变化

上面提到了DownloadButton是一个StatelessWidget,所有的属性都是由外部传入的,但是对于一个动画的DownloadButton来说,status,downloadProgress这些信息都是会动态变化的,那么怎么才能让变化的属性传到DownloadButton中进行组件的重绘呢?

因为涉及到复杂的状态变化,所以简单的AnimatedWidget已经满足不了我们的需求了,这里就需要用到flutter中的AnimatedBuilder组件了。

AnimatedBuilder是AnimatedWidget的子类,它有两个必须的参数,分别是animation和builder。

其中animation是一个Listenable对象,它可以是Animation,ChangeNotifier或者等。

AnimatedBuilder会通过监听animation的变动情况,来重新构建builder中的组件。buidler方法可以从animation中获取对应的变动属性。

这样我们创建一个Listenable的DownloadController对象,然后把DownloadButton用AnimatedBuilder封装起来,就可以实时监测到downloadStatus和downloadProgress的变化了。

如下所示:

Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('下载按钮')),
      body: Center(
        child: SizedBox(
          width: 96,
          child: AnimatedBuilder(
            animation: _downloadController,
            builder: (context, child) {
              return DownloadButton(
                status: _downloadController.downloadStatus,
                downloadProgress: _downloadController.progress,
                onDownload: _downloadController.startDownload,
                onCancel: _downloadController.stopDownload,
                onOpen: _downloadController.openDownload,
              );
            },
          ),
        ),
      ),
    );
  }

定义downloadController

downloadController是一个Listenable对象,这里我们让他实现ChangeNotifier接口,并且定义了两个获取下载状态和下载进度的方法,同时也定义了三个点击触发事件:

abstract class DownloadController implements ChangeNotifier  {
  DownloadStatus get downloadStatus;
  double get progress;

  void startDownload();
  void stopDownload();
  void openDownload();
}

接下来我们来实现这个抽象方法:

class MyDownloadController extends DownloadController
    with ChangeNotifier {
  MyDownloadController({
    DownloadStatus downloadStatus = DownloadStatus.notDownloaded,
    double progress = 0.0,
    required VoidCallback onOpenDownload,
  })  : _downloadStatus = downloadStatus,
        _progress = progress,
        _onOpenDownload = onOpenDownload;

startDownload,stopDownload这两个方法是跟下载状态和下载进度相关的,先看下stopDownload:

  void stopDownload() {
    if (_isDownloading) {
      _isDownloading = false;
      _downloadStatus = DownloadStatus.notDownloaded;
      _progress = 0.0;
      notifyListeners();
    }
  }

可以看到这个方法最后需要调用notifyListeners来通知AnimatedBuilder来进行组件的重绘。

startDownload方法会复杂一点,我们需要模拟下载状态的变化和进度的变化,如下所示:

  Future<void> _doDownload() async {
    _isDownloading = true;
    _downloadStatus = DownloadStatus.fetchingDownload;
    notifyListeners();

    // fetch耗时1秒钟
    await Future<void>.delayed(const Duration(seconds: 1));

    if (!_isDownloading) {
      return;
    }

    // 转换到下载的状态
    _downloadStatus = DownloadStatus.downloading;
    notifyListeners();

    const downloadProgressStops = [0.0, 0.15, 0.45, 0.8, 1.0];
    for (final progress in downloadProgressStops) {
      await Future<void>.delayed(const Duration(seconds: 1));
      if (!_isDownloading) {
        return;
      }
      //更新progress
      _progress = progress;
      notifyListeners();
    }

    await Future<void>.delayed(const Duration(seconds: 1));
    if (!_isDownloading) {
      return;
    }
    //切换到下载完毕状态
    _downloadStatus = DownloadStatus.downloaded;
    _isDownloading = false;
    notifyListeners();
  }
}

因为下载是一个比较长的过程,所以这里用的是异步方法,在异步方法中进行通知。

定义DownloadButton的细节

有了可以动态变化的状态和进度之后,我们就可以在DownloadButton中构建具体的页面展示了。

在未开始下载之前,我们希望downloadButton是一个长条形的按钮,按钮上的文字显示GET,下载过程中希望是一个类似CircularProgressIndicator的动画,可以根据下载进度来动态变化。

同时,在下载过程中,我们希望能够隐藏之前的长条形按钮。 下载完毕之后,再次展示长条形按钮,这时候按钮上的文字显示为OPEN。

因为动画比较复杂,所以我们将动画组件分成两部分,第一部分就是展示和隐藏长条形的按钮,这里我们使用AnimatedOpacity来实现文字的淡入淡出的效果,并将AnimatedOpacity封装在AnimatedContainer中,实现decoration的动画效果:

  return AnimatedContainer(
      duration: transitionDuration,
      curve: Curves.ease,
      width: double.infinity,
      decoration: shape,
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 6),
        child: AnimatedOpacity(
          duration: transitionDuration,
          opacity: isDownloading || isFetching ? 0.0 : 1.0,
          curve: Curves.ease,
          child: Text(
            isDownloaded ? 'OPEN' : 'GET',
            textAlign: TextAlign.center,
            style: Theme.of(context).textTheme.button?.copyWith(
              fontWeight: FontWeight.bold,
              color: CupertinoColors.activeBlue,
            ),
          ),
        ),
      ),
    );

实现效果如下所示:

接下来再处理CircularProgressIndicator的部分:

 Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 1,
      child: TweenAnimationBuilder<double>(
        tween: Tween(begin: 0, end: downloadProgress),
        duration: const Duration(milliseconds: 200),
        builder: (context, progress, child) {
          return CircularProgressIndicator(
            backgroundColor: isDownloading
                ? CupertinoColors.lightBackgroundGray
                : Colors.white.withOpacity(0),
            valueColor: AlwaysStoppedAnimation(isFetching
                ? CupertinoColors.lightBackgroundGray
                : CupertinoColors.activeBlue),
            strokeWidth: 2,
            value: isFetching ? null : progress,
          );
        },
      ),
    );
  }

这里使用的是TweenAnimationBuilder来实现CircularProgressIndicator根据不同progress的动画效果。

因为在下载过程中,还有停止的功能,所以我们在CircularProgressIndicator上再放一个stop icon,最后将这个stack封装在AnimatedOpacity中,实现整体的一个淡入淡出功能:

         Positioned.fill(
            child: AnimatedOpacity(
              duration: transitionDuration,
              opacity: _isDownloading || _isFetching ? 1.0 : 0.0,
              curve: Curves.ease,
              child: Stack(
                alignment: Alignment.center,
                children: [
                  ProgressIndicatorWidget(
                    downloadProgress: downloadProgress,
                    isDownloading: _isDownloading,
                    isFetching: _isFetching,
                  ),
                  if (_isDownloading)
                    const Icon(
                      Icons.stop,
                      size: 14,
                      color: CupertinoColors.activeBlue,
                    ),
                ],
              ),
            ),

总结

这样,我们一个动画的下载按钮就制作完成了,效果如下:

本文的例子:https://github.com/ddean2009/learn-flutter.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/563644.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI歌手:新晋挑战者还是未来主流的替代者?

AI歌手&#xff1a;新晋挑战者还是未来主流的替代者&#xff1f; 近日&#xff0c;一款名为“AI孙燕姿”的AI歌手火遍全网&#xff0c;其翻唱的林俊杰的《她说》、周杰伦的《爱在西元前》、赵雷的《成都》等歌曲让网友纷纷表示&#xff1a;“听了一晚上&#xff0c;出不去了。…

《信息技术时代》期刊简介及投稿要求

《信息技术时代》&#xff08;半月刊&#xff09;本刊是由国家新闻总署批准&#xff0c;深圳湾科技发展有限公司主管主办的信息类期刊&#xff0c;国内统一刊号CN&#xff1a;44-1536/TN&#xff0c;国际标准刊号ISSN&#xff1a;1671-153x。本刊旨在为全集团的信息工作者提供交…

Hadoop部署完全分布式

​ 在真实的企业环境中&#xff0c;服务器集群会使用到多台机器&#xff0c;共同配合&#xff0c;来构建一个完整的分布式文件系统。而在这样的分布式文件系统中&#xff0c;HDFS相关的守护进程也会分布在不同的机器上&#xff0c;例如: NameNode守护进程&#xff0c;尽可能的…

【OpenCv • c++】几何检测 —— 霍夫变换 | 霍夫直线检测 | 霍夫线变化

&#x1f680; 个人简介&#xff1a;CSDN「博客新星」TOP 10 &#xff0c; C/C 领域新星创作者&#x1f49f; 作 者&#xff1a;锡兰_CC ❣️&#x1f4dd; 专 栏&#xff1a;【OpenCV • c】计算机视觉&#x1f308; 若有帮助&#xff0c;还请关注➕点赞➕收藏&#xff…

浅谈YUV444、YUV422、YUV420

本期将会给大家介绍YUV相关基础知识&#xff0c;同时也介绍威创网络分布式系统的卓越色彩处理技术。 1.什么是YUV色彩空间 2.YUV采样格式 3.YUV不同采样格式对图像画质的影响分析 一、什么是YUV色彩空间? YUV是视频、图片、相机等应用中常常使用的一类图像格式&#xff0c;是…

【python案例】获取IP代理数据,筛选出符合需求的IP

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 我们为什么要IP代理呢&#xff1f; 当采集数据, 批量采集数据, 请求速度过快, 网站可能会把你IP封掉 <你的网络进不去这个网站> IP代理换一个IP, 再去采集请求数据 开发环境: 解释器版本: python 3.8 代码编辑…

Vicuna:斯坦福开源一个性能相当于90%ChatGPT的聊天机器人

自从Meta公司发布LLaMA以来&#xff0c;围绕它微调和开发的模型越来越多&#xff0c;这得益于它的性能和效果&#xff0c;2023年3月份发布的草泥马&#xff08;Alpaca&#xff09;是由Meta的LLaMA 7B微调而来的全新模型&#xff0c;仅用了52k数据&#xff0c;性能约等于GPT-3.5…

欧奈尔杯柄形态选股公式,突破杯柄高点发出信号

杯柄形态是由美国著名的成长股投资大师威廉欧奈尔提出的&#xff0c;在其经典著作《笑傲股市》中有详细介绍。 根据欧奈尔在书中的描述&#xff1a; 从侧面观察其轮廓时&#xff0c;最重要的价格形态之一看起来就像是一个带柄的茶杯。杯状形态可以持续7~56周不等&#xff0c;但…

数据结构选择题(难题题解) 第五章 树

5.2 14. 法1&#xff1a;少一个叶子再多一个叶子&#xff0c;欠考虑了 选B 法2&#xff1a; 法三&#xff1a; 5.3 23.选D A. 如果是根 直接找孩子就能找到后继&#xff1b;如果是左孩子&#xff0c;有孩子就找孩子&#xff0c;无孩子&#xff0c;指针是空&#xff0c…

ROS2 msg和srv CMake编写注意事项

ROS2使用msg和srv可以参考官网&#xff1a; 工程文件cpp所包含的头文件&#xff0c;其中 cpp_pubsub_self/msg/test.hpp 为msg消息消息生成的头文件&#xff0c;具体的可以参考官网 注意事项&#xff1a; 在 ROS2 当中&#xff0c;使用 msg 和 srv 消息文件格式的时候&#…

单点登录三:添加RBAC权限校验模型功能理解及实现demo

1、RBAC权限模型 RBAC&#xff08;Role-Based Access Control&#xff09;是一种基于角色的访问控制模型&#xff0c;用于管理系统中用户的权限和访问控制。它将用户、角色和权限之间的关系进行了明确的定义&#xff0c;以实现灵活的权限管理和控制。 1.1、RBAC模型主要包括以…

这才是CSDN最系统的网络安全学习路线(建议收藏)

01 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

异步复位同步释放

(1条消息) 芯片设计全流程知识点总结_GGbao_的博客-CSDN博客 异步复位同步释放在always语句块里为&#xff1a; 当异步复位信号到来时&#xff0c;寄存器 reg_1 会被直接拉低&#xff0c;这是不会产生问题的&#xff0c;当rstn下降沿和clk上升沿同时到来时&#xff0c;如果rstn…

EduSoho 网校系统部署

目录 一、初始化环境二、安装 Nginx三、安装 MySQL四、安装 PHP五、上线 EduSoho六、验证FAQ 一、初始化环境 1、安装wget 如果系统已安装wget,请跳过此步骤 yum install wget2、关闭防火墙/Selinux systemctl stop firewalld.service systemctl disable firewalld.service…

Win32通用控件,加载进程(PE查看器)项目初步

在本专栏上一篇文章中带领大家学习了对话框的创建&#xff0c;并且在项目中创建出了对话框。在这一篇文章中&#xff0c;我将带领大家学习Win32通用控件&#xff0c;了解_WM_NOTIFY消息&#xff0c;并且带领大家初步写出课程中加载Windows所有进程的应用程序的雏形&#xff0c;…

因为懒,我用了“低代码”打下手

目录 一、前言 二、工具介绍 三、平台特点 四、如何使用JNPF&#xff1f; 五、低代码平台的选择参考 六、总结 一、前言 「一个优秀的开发者&#xff0c;一定是会利用各种工具来提升自己的开发效率。」 前段时间&#xff0c;体验了很多国内前沿的低代码平台&#xff0c;在…

Fiddler抓包工具之Fiddler界面主菜单功能介绍

Fiddler界面主菜单功能介绍 File菜单 File菜单中的命令主要支持完成通过Fiddler来启动和停止web流量的捕获&#xff08;capture&#xff09;,也可以加载或存储捕获的流量 &#xff08;1&#xff09;Capture Traffic&#xff1a;默认勾选&#xff0c;勾选此项才可抓包&#xff…

Linux ALSA音频工具

参考&#xff1a; ALSA 音频工具 amixer、aplay、arecord Linux Alsa ALSA的配置文件 音频录制——arecord 音频播放——aplay 音频配置——amixer alsamixer与amixer的区别 alsamixer是Linux音频框架ALSA工具之一&#xff0c;用于配置音频各个参数; alsamixer是基于文本图形…

APP中有html5页面的时候,怎么进行元素定位

测试app的时候&#xff0c;我们知道可以通过UI Automator Viewer进行元素定位 但是很多app中都会内嵌h5页面&#xff0c;这个时候定位就会变成下图这样&#xff1a; 第一步&#xff1a; 在手机中打开当前app的h5界面&#xff0c;使用usb连接电脑后&#xff0c; 第二步&#x…

聚焦金融行业网络安全|安全狗亮相知虎科技生态大会

5月18日&#xff0c;知虎科技生态大会在上海顺利举办。作为国内云原生安全领导厂商&#xff0c;安全狗也受邀出席此次活动。 据悉&#xff0c;此次大会邀请了来自国内顶级安全厂商的安全专家介绍从网络安全、应用安全、业务安全、数据安全、资金安全、通信安全等六个方面全方位…