当前位置: 首页 > article >正文

Flutter:跑马灯公告栏

在这里插入图片描述

组件

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

页面调用

// 模拟数据
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,
)

http://www.kler.cn/a/584540.html

相关文章:

  • 如何使用logrotete定时切割mysql的慢日志
  • “量超融合”突破 澳Quantum Brilliance融资2000万美元探索量子与超算协同
  • 如何用AI轻松制作高效又专业的PPT演示文稿
  • 【WPF】在System.Drawing.Rectangle中限制鼠标保持在Rectangle中移动?
  • 电商网站价格监控:动态价格数据的实时抓取案例
  • XMI(XML Metadata Interchange)和XML之间的关系
  • 【工具】jdk与jmeter下载与安装
  • [ NodeJs ] worker pool
  • TCP网络协议
  • MySQL(社区版)安装过程
  • 上下文微调(Contextual Fine-Tuning, CFT)提高大型语言模型(LLMs)在特定领域的学习和推理能力
  • 高级java每日一道面试题-2025年2月22日-数据库篇[Redis篇]-Redis是什么?
  • LangGraph 构建的工作流调用数据库的时候怎么添加重试机制
  • 基于Spring Boot的牙科诊所管理系统的设计与实现(LW+源码+讲解)
  • 上下文学习思维链COTPrompt工程
  • ClickHouse SQL优化:从原理到实战的深度指南
  • 【Grok3 deepseek-R1】利用伪逆方法求解机械臂速度级的运动方案
  • Unity知识总结——算法
  • 源IP泄露后如何涅槃重生?高可用架构与自动化防御体系设计
  • 计算机网络之应用层(控制连接与数据连接)