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