flutter列表中实现置顶动画

需求:列表中的第一条向上滑出移除,第二条顶到第一的位置的置顶动画。

第一位置的数据移除了需要在列表的末尾加回来。

dart 复制代码
import 'dart:async';
import 'package:atui/jade/configs/PathConfig.dart';
import 'package:atui/jade/customWidget/custom_separator_vertical.dart';
import 'package:atui/jade/utils/JadeColors.dart';
import 'package:atui/jade/utils/Utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class InstantBanner extends StatefulWidget {
  const InstantBanner({super.key});

  @override
  State<StatefulWidget> createState() {
    return _InstantBannerState();
  }
}

class _InstantBannerState extends State<InstantBanner> {
  final List<String> _data = [
    "【iPhone17Pro Max怎买最便宜,全...",
    "1分钱购买新会陈皮",
    "【1条你不得不看的装修避坑指南】",
    "【2条你不得不看的装修避坑指南】",
    "【3条你不得不看的装修避坑指南】",
    "【4条你不得不看的装修避坑指南】",
    "【5条你不得不看的装修避坑指南】",
    "【6条你不得不看的装修避坑指南】",
  ];

  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _startAnimationTimer();
  }

  @override
  void dispose() {
    _timer?.cancel(); // 销毁时取消定时器
    super.dispose();
  }

  // 启动轮播动画
  void _startAnimationTimer() {
    // 先取消之前的定时器,避免重复
    _timer?.cancel();

    // 立即执行一次动画,让用户有即时反馈
    _moveFirstToLast();

    // 然后设置周期性定时器,5秒执行一次
    _timer = Timer.periodic(const Duration(seconds: 3), (timer) {
      _moveFirstToLast();
    });
  }

  // 停止轮播动画
  void _stopAnimationTimer() {
    _timer?.cancel();
    _timer = null;
  }

  void _moveFirstToLast() {
    if (_data.isEmpty || !mounted) return;

    setState(() {
      final firstItem = _data.removeAt(0);
      _listKey.currentState?.removeItem(
        0,
            (context, animation) => SlideTransition(
          position: Tween<Offset>(
            begin: const Offset(0, -1),
            end: const Offset(0, 0), // 向上滑出
          ).animate(animation),
          child: _itemView(firstItem, 0),
        ),
        duration: const Duration(milliseconds: 300),
      );

      // 延迟插入到末尾,实现"顶上去"的视觉效果
      Future.delayed(const Duration(milliseconds: 300), () {
        if (!mounted) return;
        _data.add(firstItem);
        _listKey.currentState?.insertItem(
          _data.length - 1,
          duration: const Duration(milliseconds: 300),
        );
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 440.w,
      width: Utils().screenWidth(context),
      child: _popularityRankingView(),
    );
  }

  // 人气榜模块
  Widget _popularityRankingView() {
    return Container(
      margin: EdgeInsets.symmetric(horizontal: 20.w),
      padding: EdgeInsets.only(left: 30.w, right: 20.w, top: 30.w, bottom: 20.w),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [JadeColors.orange_17, Colors.white],
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
        ),
        borderRadius: BorderRadius.circular(10),
      ),
      child: Column(
        children: [
          Padding(
            padding: EdgeInsets.only(bottom: 20.w),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  '周边人气榜',
                  style: TextStyle(fontSize: 34.sp, fontWeight: FontWeight.bold),
                ),
                Image.asset(PathConfig.iconArrowRightBlack, width: 12.w)
              ],
            ),
          ),
          Expanded(child: _rankingListView()),
          GestureDetector(
            onTap: () {},
            child: Container(
              padding: EdgeInsets.symmetric(horizontal: 40.w, vertical: 10.w),
              decoration: BoxDecoration(
                color: JadeColors.gold_9,
                borderRadius: BorderRadius.circular(20),
              ),
              child: Text(
                '查看更多',
                style: TextStyle(color: JadeColors.gold_10, fontSize: 26.sp, fontWeight: FontWeight.bold),
              ),
            ),
          )
        ],
      ),
    );
  }

  Widget _rankingListView() {
    return AnimatedList(
      key: _listKey,
      initialItemCount: _data.length,
      itemBuilder: (context, index, animation) {
        return SlideTransition(
          position: Tween<Offset>(
            begin: const Offset(0, 1), // 从下方滑入
            end: Offset.zero,
          ).animate(animation),
          child: _itemView(_data[index], index),
        );
      },
    );
  }

  Widget _itemView(String title, int index) {
    // 修复高度问题,使用ScreenUtil单位
    return SizedBox(
      height: 60.w, // 调整为合适的高度
      child: Stack(
        children: [
          Container(
            margin: EdgeInsets.only(left: 44.w),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Container(
                  padding: EdgeInsets.all(3.w), // 使用ScreenUtil单位
                  decoration: BoxDecoration(
                    color: JadeColors.red_14,
                    borderRadius: const BorderRadius.only(
                      topLeft: Radius.circular(2),
                      topRight: Radius.circular(2),
                      bottomRight: Radius.circular(2),
                    ),
                  ),
                  child: Text(
                    '新',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 18.sp,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                SizedBox(width: 8.w), // 增加间距
                Expanded(
                  child: Text(
                    title,
                    style: TextStyle(fontSize: 26.sp, fontWeight: FontWeight.bold),
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ],
            ),
          ),
          Positioned(
            left: 0,
            width: 40.w,
            top: 0,
            bottom: 0,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ClipOval(
                  child: Container(
                    width: 12.w,
                    height: 12.w,
                    color: JadeColors.orange_14,
                  ),
                ),
                index == _data.length - 1
                    ? const SizedBox()
                    : Expanded(
                  child: SizedBox(
                    width: 1.w, // 调整竖线宽度
                    child: CustomSeparatorVertical(
                      color: JadeColors.orange_14,
                      dashWidth: 1,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
相关推荐
始持2 小时前
第十二讲 风格与主题统一
前端·flutter
始持2 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持2 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜3 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴3 小时前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区4 小时前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎4 小时前
树形选择器组件封装
前端·flutter
程序员老刘18 小时前
跨平台开发地图:金三银四你准备好了吗? | 2026年3月
flutter·客户端
恋猫de小郭19 小时前
Kotlin 在 2.0 - 2.3 都更新了什么特性,一口气带你看完这两年 Kotlin 更新
android·前端·flutter