Flutter-實現(xiàn)頭像疊加動畫效果

在這篇文章中,我們將介紹如何使用 Flutter 實現(xiàn)一個帶有透明度漸變效果和過渡動畫的頭像疊加列表。通過這種效果,可以在圖片切換時實現(xiàn)平滑的動畫,使 UI 更加生動和吸引人。

需求

我們的目標是實現(xiàn)一個頭像疊加列表,在每隔 2 秒時切換頭像,并且在切換過程中,前一個頭像逐漸消失,新進入的頭像逐漸顯示,同時有一個從右向左的移動過渡效果。

具體需求包括:

  1. 支持頭像圓形顯示。
  2. 支持設置頭像重疊比例。
  3. 支持配置間隔時間切換一次頭像。
  4. 切換時,前一個頭像透明度漸變消失,后一個頭像透明度漸變顯示。
  5. 切換時,有平滑的移動動畫。

效果

converted_animation.gif

實現(xiàn)思路

為了實現(xiàn)這個效果,我們將使用 Flutter 的 AnimatedBuilder、AnimationControllerTween 來實現(xiàn)過渡動畫和透明度漸變效果。主要步驟包括:

  1. 創(chuàng)建一個 CircularImageList 組件,用于顯示頭像列表。
  2. 使用 AnimationController 控制動畫的執(zhí)行。
  3. 使用 AnimatedBuilderOpacity 實現(xiàn)透明度漸變效果。
  4. 使用 PositionedAnimatedBuilder 實現(xiàn)位置移動過渡效果。
  5. 每隔 2 秒觸發(fā)一次動畫,并更新顯示的頭像列表。

實現(xiàn)代碼

下面是實現(xiàn)上述需求的完整代碼:

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

  @override
  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);
  }

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

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

  @override
  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;
  }
}

結束語

通過上述代碼,我們實現(xiàn)了一個帶有透明度漸變效果和過渡動畫的頭像疊加列表。在實際開發(fā)中,可以根據(jù)需求對動畫的時長、重疊比例等進行調整,以達到最佳效果。希望這篇文章對您有所幫助,如果有任何問題或建議,詳情見:github.com/yixiaolunhui/flutter_xy

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容