Flutter:跑马灯公告栏

组件

js 复制代码
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';

class MarqueeNotice extends StatefulWidget {
  /// 公告数据列表,每条公告包含title和desc
  final List<Map<String, String>> notices;
  
  /// 滚动速度,数值越小滚动越快(像素/秒)
  final double scrollSpeed;
  
  /// 背景颜色
  final Color backgroundColor;
  
  /// 公告栏高度
  final double height;
  
  /// 公告图标
  final Widget? icon;

  /// 公告文本颜色
  final Color textColor;
  
  /// 公告字体大小
  final double fontSize;
  
  /// 公告条目间距
  final double itemSpacing;

  const MarqueeNotice({
    super.key,
    required this.notices,
    this.scrollSpeed = 50.0,
    this.backgroundColor = Colors.transparent,
    this.height = 40,
    this.icon,
    this.textColor = Colors.white,
    this.fontSize = 24,
    this.itemSpacing = 60,
  });

  @override
  State<MarqueeNotice> createState() => _MarqueeNoticeState();
}

class _MarqueeNoticeState extends State<MarqueeNotice> with SingleTickerProviderStateMixin {
  late ScrollController _scrollController;
  late Timer _timer;
  late List<Map<String, String>> _extendedNotices;
  double _scrollOffset = 0.0;
  double _maxScrollExtent = 0.0;
  
  @override
  void initState() {
    super.initState();
    
    // 扩展公告列表使其可以循环滚动(复制一份公告列表)
    _extendedNotices = [...widget.notices, ...widget.notices];
    
    _scrollController = ScrollController();
    
    // 在布局完成后开始滚动
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _startScrolling();
    });
  }
  
  // 开始滚动动画
  void _startScrolling() {
    // 测量滚动区域总宽度的一半(原始公告列表的宽度)
    _maxScrollExtent = _scrollController.position.maxScrollExtent / 2;
    
    // 设置定时器,实现滚动效果
    _timer = Timer.periodic(const Duration(milliseconds: 50), (timer) {
      _scrollOffset += 1.0;
      
      // 当滚动到第一份公告列表的末尾时,重置到开头位置
      if (_scrollOffset >= _maxScrollExtent) {
        _scrollOffset = 0.0;
        _scrollController.jumpTo(_scrollOffset);
      } else {
        _scrollController.animateTo(
          _scrollOffset,
          duration: const Duration(milliseconds: 50),
          curve: Curves.linear,
        );
      }
    });
  }
  
  @override
  void dispose() {
    _timer.cancel();
    _scrollController.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget.height.w,
      color: widget.backgroundColor,
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        controller: _scrollController,
        physics: const NeverScrollableScrollPhysics(), // 禁用手动滚动
        child: Row(
          children: _buildNoticeItems(),
        ),
      ),
    );
  }
  
  List<Widget> _buildNoticeItems() {
    return _extendedNotices.map((notice) {
      return Container(
        padding: EdgeInsets.symmetric(horizontal: widget.itemSpacing.w / 2),
        child: Row(
          children: [
            // 如果提供了图标,则显示图标
            if (widget.icon != null) ...[
              widget.icon!,
              SizedBox(width: 10.w),
            ],
            
            // 公告文本
            RichText(
              text: TextSpan(
                children: [
                  TextSpan(
                    text: "${notice['title']} ",
                    style: TextStyle(
                      color: widget.textColor,
                      fontSize: widget.fontSize.sp,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  TextSpan(
                    text: notice['desc'],
                    style: TextStyle(
                      color: widget.textColor,
                      fontSize: widget.fontSize.sp,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      );
    }).toList();
  }
} 

页面调用

js 复制代码
// 模拟数据
final List notices = [
  {
    'title': '用户**1564**',
    'desc': '获得了 20.06 USDT 佣金',
  },
  {
    'title': '用户**3721**',
    'desc': '获得了 35.82 USDT 佣金',
  },
  {
    'title': '用户**9823**',
    'desc': '获得了 15.45 USDT 佣金',
  },
  {
    'title': '用户**6471**',
    'desc': '获得了 42.18 USDT 佣金',
  },
  {
    'title': '用户**2389**',
    'desc': '获得了 28.67 USDT 佣金',
  },
];
MarqueeNotice(
  noti
  ces: List<Map<String, String>>.from(controller.notices),
  height: 80,
  backgroundColor: Colors.transparent,
  textColor: AppTheme.color999,
  fontSize: 24,
  itemSpacing: 100,
)
相关推荐
liulian09162 小时前
【Flutter for OpenHarmony】原生卡片 Widget 集成实战:从零构建待办清单桌面组件
flutter·华为·学习方法·harmonyos
2601_949593653 小时前
Flutter OpenHarmony 三方库 video_player 视频播放器适配详解
flutter·音视频
liulian09163 小时前
Flutter 三方库 connectivity_plus 的鸿蒙化适配与网络状态管理实战
网络·flutter·华为·学习方法·harmonyos
MonkeyKing4 小时前
InheritedWidget 原理与性能
flutter
liulian09165 小时前
【Flutter For OpenHarmony】Flutter 三方库 flutter_secure_storage 的鸿蒙化适配指南
flutter·华为·学习方法·harmonyos
liulian09165 小时前
【Flutter For OpenHarmony】Flutter 三方库 flutter_local_notifications 的鸿蒙化适配指南
flutter·华为·学习方法·harmonyos
IntMainJhy6 小时前
【Flutter 三方库 Provider 】flutter for open harmony的鸿蒙化适配与实战指南✨
flutter·华为·harmonyos
weixin_443478517 小时前
Flutter学习之自定义组件
javascript·学习·flutter
Lanren的编程日记17 小时前
Flutter鸿蒙应用开发:数据统计与分析功能集成实战
flutter·华为·harmonyos
于慨1 天前
mac安装flutter
javascript·flutter·macos