Flutter-实现头像叠加动画效果

实现头像叠加动画效果

在这篇文章中,我们将介绍如何使用 Flutter 实现一个带有透明度渐变效果和过渡动画的头像叠加列表。通过这种效果,可以在图片切换时实现平滑的动画,使 UI 更加生动和吸引人。

需求

我们的目标是实现一个头像叠加列表,在每隔 2 秒时切换头像,并且在切换过程中,前一个头像逐渐消失,新进入的头像逐渐显示,同时有一个从右向左的移动过渡效果。

具体需求包括:

  1. 支持头像圆形显示。
  2. 支持设置头像重叠比例。
  3. 支持配置间隔时间切换一次头像。
  4. 切换时,前一个头像透明度渐变消失,后一个头像透明度渐变显示。
  5. 切换时,有平滑的移动动画。

效果

在这里插入图片描述

实现思路

为了实现这个效果,我们将使用 Flutter 的 AnimatedBuilderAnimationControllerTween 来实现过渡动画和透明度渐变效果。主要步骤包括:

  1. 创建一个 CircularImageList 组件,用于显示头像列表。
  2. 使用 AnimationController 控制动画的执行。
  3. 使用 AnimatedBuilderOpacity 实现透明度渐变效果。
  4. 使用 PositionedAnimatedBuilder 实现位置移动过渡效果。
  5. 每隔 2 秒触发一次动画,并更新显示的头像列表。

实现代码

下面是实现上述需求的完整代码:

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';

class CircularImageList extends StatefulWidget {
  final List<String> imageUrls;
  final int maxDisplayCount;
  final double overlapRatio;
  final double height;
  final Duration animDuration;
  final Duration delayedDuration;

  const CircularImageList({
    super.key,
    required this.imageUrls,
    required this.maxDisplayCount,
    required this.overlapRatio,
    required this.height,
    this.animDuration = const Duration(milliseconds: 500),
    this.delayedDuration = const Duration(seconds: 1),
  });

  
  CircularImageListState createState() => CircularImageListState();
}

class CircularImageListState extends State<CircularImageList>
    with SingleTickerProviderStateMixin {
  int _currentIndex = 0;
  List<String> _currentImages = [];
  late AnimationController _animationController;
  late Animation<double> _animation;

  int get maxDisplayCount {
    return widget.maxDisplayCount + 1;
  }

  double get circularImageWidth {
    var realCount = maxDisplayCount - 1;
    return realCount * widget.height -
        widget.height * (1 - widget.overlapRatio) * (realCount - 1);
  }

  
  void initState() {
    super.initState();
    _currentImages = widget.imageUrls.take(maxDisplayCount).toList();
    _animationController = AnimationController(
      duration: widget.animDuration,
      vsync: this,
    );

    _animation = Tween<double>(begin: 0, end: 1).animate(_animationController)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          setState(() {
            _currentIndex = (_currentIndex + 1) % widget.imageUrls.length;
            _currentImages.removeAt(0);
            _currentImages.add(widget.imageUrls[_currentIndex]);
          });
          _animationController.reset();
          Future.delayed(widget.delayedDuration, () {
            _animationController.forward();
          });
        }
      });

    Future.delayed(widget.delayedDuration, () {
      _animationController.forward();
    });
  }

  
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Container(
      clipBehavior: Clip.none,
      width: circularImageWidth,
      height: widget.height,
      child: Stack(
        clipBehavior: Clip.none,
        children: _buildImageStack(),
      ),
    );
  }

  double _opacity(int index) {
    if (index == 0) {
      return 1 - _animation.value;
    } else if (index == _currentImages.length - 1) {
      return _animation.value;
    } else {
      return 1;
    }
  }

  List<Widget> _buildImageStack() {
    List<Widget> stackChildren = [];
    for (int i = 0; i < _currentImages.length; i++) {
      double leftOffset = i * (widget.height * widget.overlapRatio);
      stackChildren.add(
        AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Positioned(
              left: leftOffset -
                  (_animation.value * widget.height * widget.overlapRatio),
              child: Opacity(
                opacity: _opacity(i),
                child: child!,
              ),
            );
          },
          child: ClipOval(
            key: ValueKey<String>(_currentImages[i]),
            child: CachedNetworkImage(
              imageUrl: _currentImages[i],
              width: widget.height,
              height: widget.height,
              fit: BoxFit.cover,
            ),
          ),
        ),
      );
    }
    return stackChildren;
  }
}

结束语

通过上述代码,我们实现了一个带有透明度渐变效果和过渡动画的头像叠加列表。在实际开发中,可以根据需求对动画的时长、重叠比例等进行调整,以达到最佳效果。希望这篇文章对您有所帮助,如果有任何问题或建议,详情见:github.com/yixiaolunhui/flutter_xy

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于顺序存储的环形队列算法库构建

学习贺利坚老师基于数组的环形队列 数据结构之自建算法库——顺序环形队列_下空队列q中依次入队列数据元素abc-CSDN博客文章浏览阅读5.2k次&#xff0c;点赞6次&#xff0c;收藏6次。本文针对数据结构基础系列网络课程(3)&#xff1a;栈和队列中第9课时环形队列的存储及基本操…

基于ESP8266串口WIFI模块ESP-01S在Station模式(即接收无线信号( WiFi))下实现STC单片机与手机端网路串口助手相互通信功能

基于ESP8266串口WIFI模块ESP-01S在Station模式(即接收无线信号( WiFi))下实现STC单片机与手机端网路串口助手相互通信功能 ESP8266_01S引脚功能图ESP8266_01S原理图ESP8266_01S尺寸图检验工作1、USB-TTL串口工具(推荐使用搭载CP2102芯片的安信可USB-T1串口)与ESP8266_01S…

会声会影2024永久破解和谐版下载 包含激活码序列号

亲爱的创作伙伴们&#xff0c;今天我要分享一个让我的影视编辑生活大放异彩的神器——会声会影2024破解版本&#xff01;&#x1f389;&#x1f31f; &#x1f308;**功能全面升级**&#xff1a;作为一款专业的视频编辑软件&#xff0c;会声会影2024破解版本不仅继承了之前版本…

KEITHLEY吉时利24611kW 脈沖模式 SourceMeter 手侧

KEITHLEY吉时利24611kW 脈沖模式 SourceMeter 手侧

【vue3|第13期】深入了解Vue3生命周期:管理组件的诞生、成长与消亡

日期&#xff1a;2024年6月22日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

【大数据 复习】第8章 Hadoop架构再探讨

一、概念 1.Hadoop1.0的核心组件&#xff08;仅指MapReduce和HDFS&#xff0c;不包括Hadoop生态系统内的Pig、Hive、HBase等其他组件&#xff09;&#xff0c;主要存在以下不足&#xff1a; &#xff08;1&#xff09;抽象层次低&#xff0c;需人工编码 &#xff08;2&#xf…

【Linux】基础IO_4

文章目录 六、基础I/O4. 动静态库 未完待续 六、基础I/O 4. 动静态库 既然我们能够成功创建静态库了&#xff0c;接下来我们将这个代码打包成动态库&#xff1a; shared: 表示生成共享库格式 fPIC&#xff1a;产生位置无关码(position independent code) 动态库库名规则&…

Swift 中的动态数组

Swift 的 Array 类型是一种强大而灵活的集合类型&#xff0c;可以根据需要自动扩展或缩减其容量。 动态数组的基本概念 Swift 中的数组是基于动态数组&#xff08;dynamic array&#xff09;的概念实现的。动态数组能够根据需要自动调整其容量&#xff0c;以容纳新增的元素&a…

第一题(伏羲六十四卦)

题目&#xff1a; 首先伏羲64卦解密 再用base64解密即可

基于SpringBoot+Vue教材订购系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

Android 14 权限等级剖析

Android 14 权限等级剖析 Android 14 引入了新的权限等级&#xff0c;为应用程序访问敏感信息和功能提供了更细粒度的控制。了解这些新的权限等级及其应用场景对于开发人员至关重要。 1. 概述 Android 一直以来都使用权限机制来控制应用程序对系统资源和用户数据的访问。随着…

Vue81-独享路由守卫

一、 独享路由守卫的定义 当只有某个特定的路由需要做校验的时候&#xff0c;可以在改路由组件规则的地方单独配置独属于改组件的路由守卫。 二、示例 1、需求 系统只在进入新闻路由页面的时候做校验。 2、代码实现 注意&#xff1a; 独享路由守卫&#xff0c;只有前置路由守…

Unity Apple Vision Pro 开发(三):visionOS 应用形态

文章目录 &#x1f4d5;教程说明&#x1f4d5;常用名词解释&#x1f4d5;visionOS 空间类型⭐Shared Space 共享空间⭐Full Space/Immersive Space 独占空间 &#x1f4d5;visionOS 渲染框架&#x1f4d5;Unity 开发 visionOS 应用的不同模式⭐**窗口模式**⭐VR 模式⭐MR 模式 …

基于matlab的高斯滤波与图像去噪

1 高斯滤波原理 1.1 原理 高斯滤波是一种线性平滑滤波技术&#xff0c;主要用于消除图像中的高斯噪声。它的工作原理可以理解为对整幅图像进行加权平均的过程&#xff0c;即每个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到。 高斯滤波实质上是一种信号的滤…

动手学深度学习(Pytorch版)代码实践 -卷积神经网络-25使用块的网络VGG

25使用块的网络VGG import torch from torch import nn import liliPytorch as lp import matplotlib.pyplot as plt# 定义VGG块 # num_convs: 卷积层的数量 # in_channels: 输入通道的数量 # out_channels: 输出通道的数量 def vgg_block(num_convs, in_channels, out_channel…

VS Code Arduino编程

①Arduino Arduino是一款便捷灵活、方便上手的开源电子原型平台。包含硬件&#xff08;各种型号的Arduino板&#xff09;和软件&#xff08;Arduino IDE)。 ②VS Code&#xff08;全称 Visual Studio Code&#xff09; 是由微软开发的一款开源、轻量级的跨平台现代代码编辑器…

昇思25天学习打卡营第4天|数据变换Transforms

学习内容复盘 1.1 数据变换 什么是数据变换、为何要数据变换 通常情况下&#xff0c;直接加载的原始数据并不能直接送入神经网络进行训练&#xff0c;此时我们需要对其进行数据预处理。MindSpore提供不同种类的数据变换&#xff08;Transforms&#xff09;&#xff0c;配合数…

Jenkins定时构建自动化(二):Jenkins的定时构建

目录 ​编辑 一、 jenkins定时构建语法&#xff1a; 1. 语法规则&#xff1a; 2. 常见用法举例 3. 再次举例 接上一篇&#xff1a;Jenkins定时构建自动化(一)&#xff1a;Jenkins下载安装配置&#xff1a;Jenkins定时构建自动化(一)&#xff1a;Jenkins下载安装配置-CSDN博客 …

32 - 判断三角形(高频 SQL 50 题基础版)

32 - 判断三角形 select *,if(xy>z and xz>y and zy > x,Yes,No) triangle fromTriangle;

[GXYCTF2019]禁止套娃1

buu打开题目进去看到&#xff0c;只有一行字 只有一行字的话&#xff0c;那么没有可用的信息&#xff0c;那么可以去查看一下源码或者是看一下network里是不是有一些提示&#xff0c;发现都没有&#xff0c;那就利用dirsearch去扫描一下目录 python dirsearch.py -u http://f87…