flutter 仿抖音的进场特效效果

从右边平移到最左边,停留两秒再向左移出屏幕
1.这个要通过eventBus通知,收到通知就往列表添加数据
2.这个要做一个队列,显示完一个(删除)再显示下一个
3.添加数据的时候就调开始执行动画;一个动画执行完后就开始调下一个的开始执行动画
4.如果动画正在播放,就不允许调开始执行动画,防止上面两个地方让队列混乱
5.因为是多个动画,使用到TickerProviderStateMixin
6.创建两个控制器和动画

示例代码

import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../common/avatar.dart';
import '../../../common/globalEventBus.dart';
import '../../../models/group/groupUserBean.dart';
import '../../../utils/printUtil.dart';

class DirectSeedingEnterRoomAnimation extends StatefulWidget {
    
    
  const DirectSeedingEnterRoomAnimation({
    
    Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => DirectSeedingEnterRoomAnimationState();
}

class DirectSeedingEnterRoomAnimationState
    extends State<DirectSeedingEnterRoomAnimation>
    with TickerProviderStateMixin {
    
    
  String tip = '进场动画效果----';

  //动画控制器,可控制前进后退,重置
  late AnimationController _controllerOne;

  //第二个动画控制器
  late AnimationController _controllerTwo;

  //用于引导第一个动画:从右到左
  late Animation<Offset> _animationOne;

  //用于引导第二个动画:向左滑动
  late Animation<Offset> _animationTwo;

  late StreamSubscription<LiveEnterRoomAnimation>? _liveEnterRoomAnimation;

  //是否播放第一个动画
  bool firstAnimation = true;

  //动画显示的队列
  List<GroupUserBean> userEnterList = [];

  //默认是空闲false,有任务就是true
  bool animationState = false;

  @override
  void initState() {
    
    
    //第一个动画:从右往左执行,动画的时间
    _controllerOne = AnimationController(
        duration: const Duration(milliseconds: 500), vsync: this);

    _controllerTwo = AnimationController(
        duration: const Duration(milliseconds: 1000), vsync: this);

    _animationOne = Tween(begin: const Offset(2.5, 0), end: const Offset(0, 0))
        .animate(_controllerOne);

    //第二个动画:向左滑出去
    _animationTwo = Tween(begin: const Offset(0, 0), end: const Offset(-2.5, 0))
        .animate(_controllerTwo);

    //第一个动画监听
    _animationOne.addListener(() {
    
    
      if (_animationOne.status == AnimationStatus.completed) {
    
    
        //第一个动画结束
        PrintUtil.prints('$tip 第一个动画结束');
        //播放第二个动画
        firstAnimation = false;
        if (_controllerTwo.isCompleted || _controllerTwo.isDismissed) {
    
    
          Future.delayed(const Duration(seconds: 2), () {
    
    
            _controllerTwo.forward();
            PrintUtil.prints('$tip 第二个动画开始');
          });
        }
        mySetState(() {
    
    });
      } else if (_animationOne.status == AnimationStatus.forward) {
    
    
        PrintUtil.prints('$tip 第一个动画进行中');
        //第一个动画进行中
        animationState = true;
        //动画开始,任务状态为忙碌
        mySetState(() {
    
    });
      }
    });

    //第二个动画监听
    _animationTwo.addListener(() {
    
    
      if (_animationTwo.status == AnimationStatus.completed) {
    
    
        //第二个动画结束,重置位置
        PrintUtil.prints('$tip 第二个动画结束');
        //回到第一个动画
        firstAnimation = true;
        _controllerOne.reset();
        _controllerTwo.reset();
        //移除第一个数据
        if (userEnterList.isNotEmpty) {
    
    
          userEnterList.removeAt(0);
        }
        //任务闲下来了
        animationState = false;
        //展示下一个数据
        showAnimation();
        mySetState(() {
    
    });
      } else if (_animationTwo.status == AnimationStatus.forward) {
    
    
        PrintUtil.prints('$tip 第二个动画进行中');
        //动画开始,任务状态为忙碌
        mySetState(() {
    
    });
      }
    });

    _liveEnterRoomAnimation = EventBusUtil.listen((event) {
    
    
      //收到一个就添加一个数据,然后开始展示
      userEnterList.add(event.userInfo);
      PrintUtil.prints('$tip 收到event数据${
    
    userEnterList.length}');
      showAnimation();
    });
    super.initState();
  }

  //取第一个数据展示,第二个动画播放完就删除数据
  showAnimation() {
    
    
    if (userEnterList.isNotEmpty) {
    
    
      PrintUtil.prints('$tip 展示数据时当前状态是$animationState  false是空闲');
      //空闲状态才给播放,防止添加数据是播放和播放完又调这个方法这两个混着了
      if (animationState == false) {
    
    
        if (_controllerOne.isCompleted || _controllerOne.isDismissed) {
    
    
          mySetState(() {
    
    });
          Future.delayed(Duration.zero, () {
    
    
            _controllerOne.forward();
            PrintUtil.prints('$tip 第一个动画启动 ${
    
    userEnterList.length}');
          });
        }
      }
    }
  }

  getAvatar() {
    
    
    String avatar = '';
    if (userEnterList.isNotEmpty) {
    
    
      if (userEnterList[0].avatarThumb != null) {
    
    
        avatar = userEnterList[0].avatarThumb ?? '';
      }
    }
    return avatar;
  }

  getNickName() {
    
    
    String name = '';
    if (userEnterList.isNotEmpty) {
    
    
      if (userEnterList[0].nickName != null) {
    
    
        name = userEnterList[0].nickName ?? '';
      }
    }
    return name;
  }

  //判断是否显示动画
  canShowAnimation() {
    
    
    bool show = false;
    if (getAvatar() != '' || getNickName() != '') {
    
    
      show = true;
    }
    return show;
  }

  mySetState(callBack) {
    
    
    if (mounted) {
    
    
      setState(() {
    
    
        callBack();
      });
    }
  }

  @override
  void dispose() {
    
    
    _controllerOne.dispose();
    _controllerTwo.dispose();
    if (_liveEnterRoomAnimation != null) {
    
    
      _liveEnterRoomAnimation!.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    
    
    return Positioned(
      bottom: 300.r,
      left: 0,
      child: SlideTransition(
          position: firstAnimation ? _animationOne : _animationTwo,
          child: canShowAnimation()
              ? Container(
                  padding: const EdgeInsets.only(
                      left: 8, right: 8, top: 3, bottom: 3),
                  decoration: BoxDecoration(
                      color: Colors.grey,
                      borderRadius: BorderRadius.circular(50)),
                  child: Row(
                    children: [
                      Avatar(
                          width: 25,
                          height: 25,
                          avatarUrl: getAvatar(),
                          radius: 50),
                      LimitedBox(
                        maxWidth: 130,
                        child: Text(
                          '${getNickName()}',
                          style: const TextStyle(
                              color: Colors.black, fontSize: 15),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                      Container(
                        margin: const EdgeInsets.only(left: 5),
                        child: const Text(
                          '进入直播间1',
                          style: TextStyle(color: Colors.black, fontSize: 15),
                        ),
                      )
                    ],
                  ),
                )
              : Container()),
    );
  }
}

猜你喜欢

转载自blog.csdn.net/weixin_44911775/article/details/130029686