likes
comments
collection
share

Flutter封装:跑马灯 MarqueeWidget

作者站长头像
站长
· 阅读数 41

一、来源

项目中遇到跑马灯需求,看看了一些第三方的,觉得不够优雅,随想自己实现一个极简且支持多数据的组件。

Flutter封装:跑马灯 MarqueeWidget

二、使用demo

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';

import 'package:flutter_templet_project/basicWidget/marquee_widget.dart';

class MarqueeWidgetDemo extends StatefulWidget {

  MarqueeWidgetDemo({ Key? key, this.title}) : super(key: key);

  final String? title;

  @override
  _MarqueeWidgetDemoState createState() => _MarqueeWidgetDemoState();
}

class _MarqueeWidgetDemoState extends State<MarqueeWidgetDemo> {
  final _globalKey = GlobalKey();

  final offset = ValueNotifier(0.0);

  Timer? _timer;

  var items = [
    Tuple4(
      'https://avatar.csdn.net/8/9/A/3_chenlove1.jpg',
      '标题|无边界厨房',
      '跳转url',
      true,
    ),
    Tuple4(
      'https://pic.616pic.com/bg_w1180/00/04/08/G5Bftx5ZDI.jpg!/fw/1120',
      '标题|无边界客厅',
      '跳转url',
      false,
    ),
    Tuple4(
      'https://cdn.pixabay.com/photo/2018/02/01/21/00/tree-3124103_1280.jpg',
      '标题|无边界厨房',
      '跳转url',
      false,
    ),
  ];

  get itemWidgets => items.map((e) => Text("${items.indexOf(e)}_${e.item2}")).toList();


  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title ?? "$widget"),
        actions: [
          IconButton(
            onPressed: () {
              setState(() {});
            },
            icon: Icon(Icons.all_inclusive),
          ),
        ],
      ),
      body: Column(
        children: [
          Container(
            height: 30,
            padding: EdgeInsets.symmetric(horizontal: 16),
            decoration: BoxDecoration(
              border: Border.all(),
            ),
            child: MarqueeWidget(
              itemCount: itemWidgets.length,
              itemBuilder: (BuildContext context, int index, BoxConstraints constraints) {
                return Container(
                  // color: Colors.green,
                  // child: itemWidgets[index],
                  child: Text("itemBuilder: $index"),
                );
              },
              separatorBuilder: (BuildContext context, int index, BoxConstraints constraints) {
                return Container(
                  width: 100,
                  // decoration: BoxDecoration(
                  //   color: Colors.blue,
                  //   border: Border.all(color: Colors.red),
                  // ),
                  // child: Text("$index"),
                );
              },
              edgeBuilder: (BuildContext context, int index, BoxConstraints constraints) {
                // print("MarqueeWidget edgeBuilder: $index ${index % 2 == 0}");
                return Container(
                  width: constraints.maxWidth,
                  // decoration: BoxDecoration(
                  //   color: Colors.yellow,
                  //   border: Border.all(color: Colors.red),
                  // ),
                  // child: Text("$index"),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

三、源码:MarqueeWidget

//
//  MarqueeWidget.dart
//  flutter_templet_project
//
//  Created by shang on 1/18/23 10:48 AM.
//  Copyright © 1/18/23 shang. All rights reserved.
//

import 'dart:async';

import 'package:flutter/material.dart';


/// 跑马灯 Builder 类型
typedef MarqueeWidgetBuilder = Widget Function(BuildContext context, int index, BoxConstraints constraints);

/// 跑马灯
class MarqueeWidget extends StatefulWidget {
  /// 跑马灯
  MarqueeWidget({
    Key? key,
    this.title,
    this.controller,
    this.duration = const Duration(milliseconds: 350),
    this.durationOffset = 30,
    required this.itemCount,
    required this.itemBuilder,
    required this.separatorBuilder,
    required this.edgeBuilder,
  }) : super(key: key);

  String? title;

  int itemCount;
  /// item builder
  MarqueeWidgetBuilder itemBuilder;
  /// 边界(前后新增) builder
  MarqueeWidgetBuilder edgeBuilder;
  /// item 间距 builder
  MarqueeWidgetBuilder separatorBuilder;
  /// 控制器
  ScrollController? controller;
  /// 定时器运行间隔
  Duration? duration;
  /// 定时器运行间隔移动偏移量
  double? durationOffset;

  @override
  _MarqueeWidgetState createState() => _MarqueeWidgetState();
}

class _MarqueeWidgetState extends State<MarqueeWidget>{
  ScrollController? _scrollController;

  final _globalKey = GlobalKey();

  // final offset = ValueNotifier(0.0);

  Timer? _timer;

  @override
  void initState() {
    _scrollController = widget.controller ?? ScrollController();
    _initTimer();
    super.initState();
  }

  @override
  void dispose() {
    _cancelTimer();
    _scrollController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (widget.itemCount == 0) {
      return Container();
    }

    final totalCount = widget.itemCount + 2;

    return LayoutBuilder(
      builder: (context, constraints) {
        return ListView.separated(
          key: _globalKey,
          controller: _scrollController,
          scrollDirection: Axis.horizontal,
          padding: EdgeInsets.all(0),
          itemCount: totalCount,
          // cacheExtent: 10,
          itemBuilder: (context, index) {
            // return widget.itemBuilder(context, index, constraints);
            final isEdge = (index == 0 || index == totalCount - 1);
            if (isEdge) {
              return widget.edgeBuilder(context, index, constraints);
            }
            return widget.itemBuilder(context, index - 1, constraints);
          },
          separatorBuilder: (context, index) {
            if (index == 0 || index == totalCount - 2) {
              return Container();
            }
            return widget.separatorBuilder(context, index, constraints);
          },
        );
      }
    );
  }

  /// 取消定时器
  _cancelTimer({bool isContinue = false}) {
    if (_timer != null) {
      _timer?.cancel();
      _timer = null;
      if (isContinue){
        _initTimer();
      }
    }
  }

  /// 初始化定时任务
  _initTimer() {
    if (_timer == null) {
      final duration = widget.duration ?? Duration(milliseconds: 350);
      _timer = Timer.periodic(duration, (t) {
        if (_scrollController == null) {
          return;
        }
        final val = _scrollController!.offset + (widget.durationOffset ?? 30);
        _scrollController!.animateTo(val, duration: duration, curve: Curves.linear);
        if(_scrollController!.position.outOfRange){
          // print("atEdge:到边界了");
          _scrollController!.jumpTo(0);
        }
      });
    }
  }
}

最后

核心源码很简单,不满足的二次开发即可: github

转载自:https://juejin.cn/post/7190207767247290429
评论
请登录