Flutter弹幕显示插件fanjiao_danmu的使用
Flutter弹幕显示插件fanjiao_danmu的使用
仿bilibili弹幕 QQ:1067915021
开始使用
此项目是一个用于启动Flutter应用的起点。
如果您是第一次使用Flutter项目,以下是一些资源供您开始:
- Lab: 编写您的第一个Flutter应用
- Cookbook: 有用的Flutter示例
有关Flutter开发的帮助,请查看官方文档,该文档提供了教程、示例、移动开发指南和完整的API参考。
示例代码
import 'dart:async';
import 'dart:ui' as ui;
import 'package:fanjiao_danmu/fanjiao_danmu/adapter/fanjiao_danmu_adapter.dart';
import 'package:fanjiao_danmu/fanjiao_danmu/danmu_tooltip.dart';
import 'package:fanjiao_danmu/fanjiao_danmu/fanjiao_danmu.dart';
import 'package:fanjiao_danmu/fanjiao_danmu/widget/bubble_box_widget.dart';
import 'package:fanjiao_danmu/fanjiao_danmu/widget/stroke_text_widget.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'jushou_danmu.dart';
import 'my_danmu_model.dart';
import 'my_thumb_shape.dart';
import 'my_track_shape.dart';
import 'utils.dart';
import 'view/path_animation.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with FanjiaoDanmuTooltipMixin {
late DanmuController<MyDanmuModel> danmuController;
late TextEditingController textController;
static const Map<String, ImageProvider<Object>> imageMap = {
'[举手]': AssetImage("assets/images/ic_jy.png"),
'[bilibili]': AssetImage("assets/images/bilibili.png"),
'[饭角]': NetworkImage("https://www.fanjiao.co/h5/img/logo.12b2d5a6.png"),
};
Duration duration = const Duration(seconds: 3780);
bool isPlaying = false;
int id = 0;
String selectedText = '';
List<Widget> otherChildren = [];
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
@override
void initState() {
super.initState();
danmuController = DanmuController(
adapter: FanjiaoDanmuAdapter(
rowHeight: 50,
imageMap: imageMap,
),
onTap: (DanmuItem? danmuItem, Offset position) {
if (danmuController.isSelected) {
danmuController.clearSelection(true);
return false;
}
if (danmuItem == null) {
return false;
}
var result = checkSelect(position, danmuItem.rect, danmuController.adapter.rect);
if (result) {
setState(() {
selectedText = danmuItem.model.text;
});
}
return result;
},
buildOtherChildren: (children) {
Future.delayed(Duration.zero, () {
setState(() {
otherChildren = children;
});
});
},
);
danmuController.setDuration(duration);
textController = TextEditingController()..text = '[饭角]';
}
@override
Widget build(BuildContext context) {
double radius = params["边框圆角"] ?? 8;
double strokeWidth = params["边框宽度"] ?? 1.2;
double pointerBias = params["偏移"] ?? 0.8;
double pointerWidth = params["指针宽度"] ?? 10;
double pointerHeight = params["指针高度"] ?? 6;
double peakRadius = params["顶点圆角"] ?? 3;
double width = params["宽"] ?? 160;
double height = params["高"] ?? 36;
bool testIsUpward = params["朝上"] ?? true;
double leading = params["leading"] ?? 1;
double textLineHeight = params["行高"] ?? 1.5;
double fontSize = params["字号"] ?? 16;
double maxWidth = params["最大宽度"] ?? 200;
int maxLines = params["最大行数"] ?? 3;
double borderPadding = params["边距"] ?? 4;
return MaterialApp(
home: Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: const Text('Danmu example'),
),
body: Stack(
children: [
SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
LayoutBuilder(builder: (context, constraints) {
var maxWidth = constraints.maxWidth;
if (maxWidth == 0) {
maxWidth = ui.window.physicalSize.width / ui.window.devicePixelRatio;
}
return Container(
color: Colors.greenAccent,
height: 300,
child: RepaintBoundary(
child: DanmuWidget(
width: maxWidth,
height: 300,
danmuController: danmuController,
tooltip: tooltip,
),
),
);
}),
danmuButton,
editDanmu(),
Container(
padding: const EdgeInsets.fromLTRB(0, 20, 0, 10),
width: double.infinity,
child: const Text(
"过滤条件:",
style: TextStyle(color: Colors.black87, fontSize: 16),
),
),
filterButton,
Container(
color: Colors.greenAccent,
constraints: const BoxConstraints(minHeight: 40),
width: double.infinity,
alignment: Alignment.centerLeft,
child: Text(
selectedText,
style: const TextStyle(color: Colors.grey),
),
),
Container(
color: Colors.amber,
height: 200,
alignment: Alignment.center,
child: Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: [
Positioned.fill(
child: Image.asset(
"assets/images/image_background.webp",
fit: BoxFit.cover,
),
),
Positioned(
height: height,
width: width,
child: BubbleBox(
isUpward: testIsUpward,
pointerBias: pointerBias,
strokeWidth: strokeWidth,
borderRadius: radius,
pointerWidth: pointerWidth,
pointerHeight: pointerHeight,
peakRadius: peakRadius,
isWrapped: false,
filter: ui.ImageFilter.blur(
sigmaX: 8,
sigmaY: 8,
),
child: Padding(
padding: EdgeInsets.only(
top: testIsUpward ? pointerHeight : 0,
bottom: testIsUpward ? 0 : pointerHeight,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SizedBox(
width: 22,
height: 36,
child: OverflowBox(
maxWidth: 30,
maxHeight: 36,
alignment: Alignment.bottomCenter,
child: Image.asset(
"assets/images/ic_jy.png",
width: 30,
height: 36,
fit: BoxFit.contain,
),
),
),
Expanded(
child: Row(
children: [
Expanded(
child: Container(
alignment: Alignment.center,
child: const Text(
'加一',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: TextDecoration.none,
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: Colors.white),
),
),
),
Container(
width: 1.0,
alignment: Alignment.center,
child: Container(
width: 1.0,
height: 12.0,
color: Colors.white.withOpacity(0.19),
),
),
Expanded(
child: Container(
alignment: Alignment.center,
child: const Text(
'复制',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: TextDecoration.none,
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: Colors.white),
),
),
),
Container(
width: 1.0,
alignment: Alignment.center,
child: Container(
width: 1.0,
height: 12.0,
color: Colors.white.withOpacity(0.19),
),
),
Expanded(
child: Container(
alignment: Alignment.center,
child: const Text(
'举报',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: TextDecoration.none,
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: Colors.white),
),
),
),
],
),
),
],
),
),
),
),
],
),
),
SwitchItem(
"朝上",
(isTurnOn) {
setState(() {
params["朝上"] = isTurnOn;
});
},
isTurnOn: testIsUpward,
),
slider("偏移", pointerBias, 0, 1),
slider("顶点圆角", peakRadius, 0, 20),
slider("指针宽度", pointerWidth, 0, 50),
slider("指针高度", pointerHeight, 0, 40),
slider("边框圆角", radius, 0, 50),
slider("边框宽度", strokeWidth, 0, 10),
slider("宽", width, 0, 300),
slider("高", height, 0, 200),
Container(
height: 500,
alignment: Alignment.center,
child: StrokeTextWidget(
'1 要画在文本附"近的装"饰(如下划线)要画在asdasdasdasdasdasd文本附"近的装"饰(如下划线)要画在文本附"近的装"饰(如下划线)要画在文本附"近的装"饰(如下划线)',
textAlign: TextAlign.center,
strutStyle: StrutStyle(
// forceStrutHeight: true,
height: textLineHeight,
leading: leading),
strokeColor: const Color(0xFF836BFF),
textScaleFactor: 1.0,
strokeWidth: 1,
textDecorationPadding: borderPadding,
maxLines: maxLines.toInt(),
maxWidth: maxWidth,
textDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: const Color(0XFF000000).withOpacity(0.4)),
textStyle: TextStyle(
fontWeight: FontWeight.w500,
fontSize: fontSize,
decoration: TextDecoration.none,
color: Colors.white,
),
),
),
slider("字号", fontSize, 5, 40),
slider("行高", textLineHeight, 0, 5),
slider("leading", leading, 0, 5),
slider("最大宽度", maxWidth, 50, 300),
slider("最大行数", maxLines, 1, 10),
slider("边距", borderPadding, 0, 10),
const SizedBox(height: 100, width: 0),
],
),
),
...otherChildren,
],
),
),
);
}
SizedBox editDanmu() {
return SizedBox(
height: 40,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: TextField(
controller: textController,
textInputAction: TextInputAction.send,
onSubmitted: (text) {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
likeCount: 10,
text: text,
decoration: const BoxDecoration(
color: Color(0xCCFF9C6B),
borderRadius: BorderRadius.all(Radius.circular(12)),
border: Border.fromBorderSide(BorderSide(
color: Colors.white,
width: 1,
style: BorderStyle.solid)),
),
startTime: danmuController.progress,
textStyle: rngTextStyle,
flag: DanmuFlag.announcement | DanmuFlag.collisionFree,
));
},
),
),
SizedBox(
width: 100,
height: 40,
child: TextButton(
onPressed: () {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: textController.text,
flag: DanmuFlag.scroll | DanmuFlag.collisionFree,
decoration: const BoxDecoration(
color: Color(0xCCFF9C6B),
borderRadius: BorderRadius.all(Radius.circular(12)),
border: Border.fromBorderSide(BorderSide(
color: Colors.white,
width: 1,
style: BorderStyle.solid)),
),
startTime: danmuController.progress,
));
},
child: const Text("发送"),
),
),
],
),
);
}
Map<String, dynamic> params = {};
Widget slider<T extends num>(String name, T initialValue, double min, double max) {
double? currentValue = params[name]?.toDouble();
if (currentValue == null) {
params[name] = initialValue;
currentValue = initialValue.toDouble();
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"$name: ${currentValue.toStringAsFixed(2)}",
style: const TextStyle(color: Colors.black87),
),
SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayColor: const Color(0x1A836BFE),
overlayShape: const RoundSliderOverlayShape(overlayRadius: 16),
trackShape: const MyTrackShape(),
thumbShape: const MyThumbShape(),
trackHeight: 4,
rangeThumbShape: const RoundRangeSliderThumbShape(enabledThumbRadius: 8),
),
child: Slider(
value: currentValue,
onChanged: (double value) {
setState(() {
if (initialValue is int) {
params[name] = value.toInt();
} else {
params[name] = value;
}
});
},
label: params[name].toString(),
min: min,
max: max,
),
),
],
),
);
}
Widget get danmuButton => Wrap(
spacing: 8.0, // gap between adjacent chips
runSpacing: 4.0, //
alignment: WrapAlignment.start, //p between lines
children: [
getButton(
"测试",
() {
int likeCount = 101;
String text = rngText;
danmuController.addDanmu(MyDanmuModel(
id: ++id,
likeCount: likeCount,
text: text,
alignment: Alignment.bottomCenter,
margin: const EdgeInsets.only(top: 6, right: 10),
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 8),
decoration: const BoxDecoration(
color: Color(0x66000000),
border: Border(
left: BorderSide(
color: Color(0x66FFFFFF),
strokeAlign: BorderSide.strokeAlignOutside),
top: BorderSide(
color: Color(0x66FFFFFF),
strokeAlign: BorderSide.strokeAlignOutside),
right: BorderSide(
color: Color(0x66FFFFFF),
strokeAlign: BorderSide.strokeAlignOutside),
bottom: BorderSide(
color: Color(0x66FFFFFF),
strokeAlign: BorderSide.strokeAlignOutside),
),
borderRadius: BorderRadius.all(Radius.circular(8)),
),
spans: buildTestItemSpans(text, id, likeCount),
startTime: danmuController.progress,
));
},
),
getButton(
"普通",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: rngText,
opacity: 0.8,
startTime: danmuController.progress,
));
},
),
getButton(
"随机彩色",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: rngText,
startTime: danmuController.progress,
textStyle: rngTextStyle,
flag: DanmuFlag.colorful | DanmuFlag.scroll | DanmuFlag.clickable,
));
},
),
getButton(
"全屏弹幕",
() {
danmuController.clearDanmu(DanmuFlag.otherStage);
var list = globalDanmus("来啦来啦!!期待下一集!", danmuController.progress);
danmuController.addAllDanmu(list);
},
),
getButton(
"顶部",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: rngText,
startTime: danmuController.progress,
flag: DanmuFlag.top | DanmuFlag.clickable,
));
},
),
getButton(
"底部",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: rngText,
startTime: danmuController.progress,
flag: DanmuFlag.bottom | DanmuFlag.clickable,
));
},
),
getButton(
"高级",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: rngText,
startTime: danmuController.progress,
textStyle: rngTextStyle,
flag: DanmuFlag.advanced,
));
},
),
getButton(
"我的",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: rngText,
decoration: const BoxDecoration(
color: Color(0xCCFF9C6B),
borderRadius: BorderRadius.all(Radius.circular(12)),
border: Border.fromBorderSide(BorderSide(
color: Colors.white, width: 1, style: BorderStyle.solid)),
),
startTime: danmuController.progress,
textStyle: rngTextStyle,
flag: DanmuFlag.collisionFree,
));
},
),
getButton(
"公告",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: rngText,
startTime: danmuController.progress,
textStyle: rngTextStyle,
flag: DanmuFlag.announcement,
));
},
),
getButton(
"3秒前",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: '3秒前',
startTime: danmuController.progress - const Duration(seconds: 3),
textStyle: rngTextStyle,
));
},
),
getButton(
"[bilibili]",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: '[bilibili]',
startTime: danmuController.progress,
textStyle: rngTextStyle,
));
},
),
getButton(
"[饭角]",
() {
danmuController.addDanmu(MyDanmuModel(
id: ++id,
text: '[饭角]',
startTime: danmuController.progress,
textStyle: rngTextStyle,
));
},
),
getButton(
"1倍速",
() {
danmuController.rate = 1;
},
),
getButton(
"1.5倍速",
() {
danmuController.rate = 1.5;
},
),
getButton(
"2倍速",
() {
danmuController.rate = 2;
},
),
getButton(
"3倍速",
() {
danmuController.rate = 3;
},
),
],
);
List<InlineSpan> buildTestItemSpans(String text, int id, int likeCount, [bool isJushou = false]) {
onTap() {
var danmuItem = danmuController.getItem(id);
if (danmuItem != null) {
if (!danmuController.isSelected && danmuItem.flag.isClickable) {
updatePlusOneItem(danmuItem);
HapticFeedback.vibrate();
}
}
}
return [
TextSpan(
text: text,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Container(
width: 30,
height: 30,
margin: const EdgeInsets.symmetric(horizontal: 4),
alignment: Alignment.bottomCenter,
child: GestureDetector(
onTap: onTap,
child: OverflowBox(
maxWidth: 30,
maxHeight: 36,
alignment: Alignment.bottomCenter,
child: isJushou
? JushouDanmu()
: Image.asset(
"assets/images/ic_jy.png",
width: 30,
height: 36,
fit: BoxFit.contain,
),
),
),
),
),
TextSpan(
text: "+$likeCount",
recognizer: TapGestureRecognizer()..onTap = onTap,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
];
}
void updatePlusOneItem(DanmuItem<MyDanmuModel> danmuItem) {
var model = danmuItem.model;
var id = model.id;
var likeCount = model.likeCount + 1;
danmuItem.pause();
Future.delayed(const Duration(seconds: 3), () {
danmuItem.play();
});
danmuItem.flag = danmuItem.flag.removeClickable.addOverlay;
var danmuModel = model.copyWith(
likeCount: likeCount,
isLiked: true,
alignment: Alignment.bottomCenter,
margin: const EdgeInsets.only(top: 6, right: 10),
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 8),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFC09DF6), Color(0xFF836BFF)],
),
border: Border(
left: BorderSide(
color: Color(0x66FFFFFF),
strokeAlign: BorderSide.strokeAlignOutside),
top: BorderSide(
color: Color(0x66FFFFFF),
strokeAlign: BorderSide.strokeAlignOutside),
right: BorderSide(
color: Color(0x66FFFFFF),
strokeAlign: BorderSide.strokeAlignOutside),
bottom: BorderSide(
color: Color(0x66FFFFFF),
strokeAlign: BorderSide.strokeAlignOutside),
),
borderRadius: BorderRadius.all(Radius.circular(8)),
),
spans: buildTestItemSpans(danmuItem.model.text, id, likeCount, true),
);
var time = danmuItem.simulation.duration / 2;
danmuController.updateItem(danmuItem, danmuModel, time: time);
}
Widget get filterButton => GridView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
childAspectRatio: 4,
),
children: [
SwitchItem(
"滚动",
(isTurnOn) {
danmuController.changeFilter(DanmuFlag.scroll);
},
isTurnOn: danmuController.filter.isScroll,
),
SwitchItem(
"顶部",
(isTurnOn) {
danmuController.changeFilter(DanmuFlag.top);
},
isTurnOn: danmuController.filter.isTop,
),
SwitchItem(
"底部",
(isTurnOn) {
danmuController.changeFilter(DanmuFlag.bottom);
},
isTurnOn: danmuController.filter.isBottom,
),
SwitchItem(
"彩色",
(isTurnOn) {
danmuController.changeFilter(DanmuFlag.colorful);
},
isTurnOn: danmuController.filter.isColorful,
),
SwitchItem(
"高级",
(isTurnOn) {
danmuController.changeFilter(DanmuFlag.advanced);
},
isTurnOn: danmuController.filter.isAdvanced,
),
SwitchItem(
"重复",
(isTurnOn) {
danmuController.markRepeated();
danmuController.changeFilter(DanmuFlag.repeated);
},
isTurnOn: danmuController.filter.isRepeated,
),
SwitchItem(
isPlaying ? "播放" : "暂停",
(isTurnOn) {
isPlaying = isTurnOn;
if (isTurnOn) {
danmuController.start();
} else {
danmuController.pause();
}
},
isTurnOn: isPlaying,
),
],
);
Widget getButton(String name, Function() onTap, {Color color = Colors.redAccent}) {
return GestureDetector(
onTap: onTap,
child: Container(
constraints: BoxConstraints.loose(const Size(82, 40)),
color: color,
alignment: Alignment.center,
padding: const EdgeInsets.all(8),
child: Text(
name,
style: const TextStyle(color: Colors.white),
),
),
);
}
@override
Size get tooltipSize => const Size(160, 36);
@override
Widget get tooltipContent => Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
GestureDetector(
onTap: () {
if (danmuController.isSelected) {
var danmuItem = danmuController.selected!;
updatePlusOneItem(danmuItem);
danmuController.clearSelection();
}
},
child: SizedBox(
width: 26,
height: 36,
child: OverflowBox(
maxWidth: 30,
maxHeight: 36,
alignment: Alignment.bottomRight,
child: Image.asset(
"assets/images/ic_jy.png",
width: 30,
height: 36,
fit: BoxFit.contain,
),
),
),
),
Expanded(
child: Row(
children: [
Expanded(
child: GestureDetector(
child: Container(
alignment: Alignment.center,
child: const Text(
'加一',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: TextDecoration.none,
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: Colors.white),
),
),
onTap: () {
if (danmuController.isSelected) {
var danmuItem = danmuController.selected!;
updatePlusOneItem(danmuItem);
danmuController.clearSelection();
}
},
behavior: HitTestBehavior.opaque,
),
),
Container(
width: 1.0,
alignment: Alignment.center,
child: Container(
width: 1.0,
height: 12.0,
color: Colors.white.withOpacity(0.19),
),
),
Expanded(
child: GestureDetector(
child: Container(
alignment: Alignment.center,
child: const Text(
'复制',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: TextDecoration.none,
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: Colors.white),
),
),
onTap: () {
danmuController.clearSelection(true);
},
behavior: HitTestBehavior.opaque,
),
),
Container(
width: 1.0,
alignment: Alignment.center,
child: Container(
width: 1.0,
height: 12.0,
color: Colors.white.withOpacity(0.19),
),
),
Expanded(
child: GestureDetector(
child: Container(
alignment: Alignment.center,
child: const Text(
'举报',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: TextDecoration.none,
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: Colors.white),
),
),
onTap: () {
danmuController.clearSelection(true);
},
behavior: HitTestBehavior.opaque,
),
),
],
),
),
],
);
}
class SwitchItem extends StatefulWidget {
final String text;
final bool isTurnOn;
final Function(bool) onTap;
const SwitchItem(
this.text,
this.onTap, {
Key? key,
this.isTurnOn = true,
}) : super(key: key);
@override
State<SwitchItem> createState() => _SwitchItemState();
}
class _SwitchItemState extends State<SwitchItem> {
bool isTurnOn = true;
@override
void initState() {
super.initState();
isTurnOn = widget.isTurnOn;
}
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxWidth: 140),
color: isTurnOn ? Colors.amber : Colors.grey,
alignment: Alignment.center,
padding: const EdgeInsets.all(4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.text,
style: const TextStyle(color: Colors.white),
),
Switch(
value: isTurnOn,
onChanged: (value) {
widget.onTap.call(value);
setState(() {
isTurnOn = value;
});
})
],
),
);
}
}
class PathAnimationDemo extends StatefulWidget {
@override
_PathAnimationDemoState createState() => _PathAnimationDemoState();
}
class _PathAnimationDemoState extends State<PathAnimationDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 5),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 1).animate(_controller);
_controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Positioned.fill(
child: CustomPaint(
painter: PathPainter(_animation),
),
);
}
}
完整示例 Demo
以上代码展示了一个完整的Flutter应用,其中包括弹幕功能。通过调用danmuController.addDanmu
方法可以添加不同类型的弹幕,如普通弹幕、公告、点击事件等。通过不同的参数设置,可以实现丰富的弹幕效果。
关键步骤解释
-
初始化控制器
danmuController = DanmuController( adapter: FanjiaoDanmuAdapter( rowHeight: 50, imageMap: imageMap, ), onTap: (DanmuItem? danmuItem, Offset position) { if (danmuController.isSelected) { danmuController.clearSelection(true); return false; } if (danmuItem == null) { return false; } var result = checkSelect(position, danmuItem.rect, danmuController.adapter.rect); if (result) { setState(() { selectedText = danmuItem.model.text; }); } return result; }, buildOtherChildren: (children) { Future.delayed(Duration.zero, () { setState(() { otherChildren = children; }); }); }, );
-
添加弹幕
danmuController.addDanmu(MyDanmuModel( id: ++id, text: text, decoration: const BoxDecoration( color: Color(0xCCFF9C6B), borderRadius: BorderRadius.all(Radius.circular(12)), border: Border.fromBorderSide(BorderSide( color: Colors.white, width: 1, style: BorderStyle.solid)), ), startTime: danmuController.progress, textStyle: rngTextStyle, flag: DanmuFlag.announcement | DanmuFlag.collisionFree, ));
-
调整弹幕速度
danmuController.rate = 1.5;
更多关于Flutter弹幕显示插件fanjiao_danmu的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter弹幕显示插件fanjiao_danmu的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
fanjiao_danmu
是一个 Flutter 插件,用于在应用中实现弹幕功能。弹幕通常用于视频播放场景,用户可以在视频上方发送实时评论或消息,这些消息会以滚动的方式显示在屏幕上。
以下是如何使用 fanjiao_danmu
插件的基本步骤:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 fanjiao_danmu
插件的依赖:
dependencies:
flutter:
sdk: flutter
fanjiao_danmu: ^1.0.0 # 请检查最新版本
然后运行 flutter pub get
来获取依赖。
2. 导入插件
在你的 Dart 文件中导入插件:
import 'package:fanjiao_danmu/fanjiao_danmu.dart';
3. 使用 DanmuView
fanjiao_danmu
提供了一个 DanmuView
组件,你可以在你的应用中使用它来显示弹幕。
class MyDanmuPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('弹幕演示'),
),
body: DanmuView(
danmuList: [
DanmuItem(content: '第一条弹幕', color: Colors.red),
DanmuItem(content: '第二条弹幕', color: Colors.blue),
DanmuItem(content: '第三条弹幕', color: Colors.green),
],
speed: 100, // 弹幕滚动速度
fontSize: 16, // 弹幕字体大小
lineHeight: 24, // 弹幕行高
onTap: (DanmuItem item) {
// 弹幕点击事件
print('点击了弹幕: ${item.content}');
},
),
);
}
}
4. 动态添加弹幕
你可以通过 DanmuController
动态添加弹幕:
class MyDanmuPage extends StatefulWidget {
[@override](/user/override)
_MyDanmuPageState createState() => _MyDanmuPageState();
}
class _MyDanmuPageState extends State<MyDanmuPage> {
DanmuController _controller;
[@override](/user/override)
void initState() {
super.initState();
_controller = DanmuController();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('弹幕演示'),
),
body: DanmuView(
controller: _controller,
speed: 100,
fontSize: 16,
lineHeight: 24,
onTap: (DanmuItem item) {
print('点击了弹幕: ${item.content}');
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 动态添加弹幕
_controller.addDanmu(DanmuItem(content: '新弹幕', color: Colors.orange));
},
child: Icon(Icons.add),
),
);
}
}
5. 自定义弹幕样式
你可以通过 DanmuItem
的 color
和 fontSize
等属性来自定义弹幕的样式。
6. 控制弹幕的显示与隐藏
你可以通过 DanmuController
来控制弹幕的显示与隐藏:
_controller.pause(); // 暂停弹幕
_controller.resume(); // 恢复弹幕
_controller.clear(); // 清空所有弹幕
7. 其他配置
DanmuView
还提供了其他一些配置选项,例如弹幕的行间距、弹幕的显示区域高度等,你可以根据需要进行调整。
8. 处理弹幕事件
你可以通过 onTap
回调来处理弹幕的点击事件,或者通过 DanmuController
来监听弹幕的状态变化。
9. 销毁控制器
在页面销毁时,记得销毁 DanmuController
以避免内存泄漏:
[@override](/user/override)
void dispose() {
_controller.dispose();
super.dispose();
}