timebar_widget 0.0.2
timebar_widget: ^0.0.2 copied to clipboard
A highly customizable timeline/timebar widget for Flutter. Ideal for video surveillance, CCTV, and live stream playback navigation with zoom and multi-segment support.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:timebar_widget/timebar_widget.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TimeBarDemoPage(),
);
}
}
class TimeBarDemoPage extends StatefulWidget {
const TimeBarDemoPage({super.key});
@override
State<TimeBarDemoPage> createState() => _TimeBarDemoPageState();
}
class _TimeBarDemoPageState extends State<TimeBarDemoPage> {
late int _currentTimeMs;
late List<RecordSegment> _segments;
bool _isPlaying = false;
Timer? _playbackTimer;
final bool _isLandscape = false;
@override
void initState() {
super.initState();
// 设置当前时间为今天中午
final now = DateTime.now();
_currentTimeMs =
DateTime(now.year, now.month, now.day, 12, 0, 0).millisecondsSinceEpoch;
_generateMockSegments();
}
@override
void dispose() {
_playbackTimer?.cancel();
super.dispose();
}
/// 生成模拟数据
void _generateMockSegments() {
final now = DateTime.now();
final startOfDay =
DateTime(now.year, now.month, now.day, 0, 0, 0).millisecondsSinceEpoch;
_segments = [
// 早上 08:00 - 10:00 普通录像
RecordSegment(
startTimeMs: startOfDay + 8 * 3600000,
endTimeMs: startOfDay + 10 * 3600000,
type: SegmentType.normal,
),
// 10:00 - 10:30 移动侦测
RecordSegment(
startTimeMs: startOfDay + 10 * 3600000,
endTimeMs: startOfDay + 10 * 3600000 + 1800000,
type: SegmentType.motion,
),
// 11:00 - 12:00 报警录制
RecordSegment(
startTimeMs: startOfDay + 11 * 3600000,
endTimeMs: startOfDay + 12 * 3600000,
type: SegmentType.alert,
),
// 13:00 - 15:00 普通录像
RecordSegment(
startTimeMs: startOfDay + 13 * 3600000,
endTimeMs: startOfDay + 15 * 3600000,
type: SegmentType.normal,
),
// 16:00 - 16:15 自定义
RecordSegment(
startTimeMs: startOfDay + 16 * 3600000,
endTimeMs: startOfDay + 16 * 3600000 + 900000,
type: SegmentType.custom,
),
// 20:00 - 22:00 普通录像
RecordSegment(
startTimeMs: startOfDay + 20 * 3600000,
endTimeMs: startOfDay + 22 * 3600000,
type: SegmentType.normal,
),
];
}
void _togglePlayback() {
setState(() {
_isPlaying = !_isPlaying;
if (_isPlaying) {
_playbackTimer =
Timer.periodic(const Duration(milliseconds: 100), (timer) {
setState(() {
_currentTimeMs += 100;
});
});
} else {
_playbackTimer?.cancel();
}
});
}
void _jumpToTime(int hour, int minute) {
final now = DateTime.now();
setState(() {
_currentTimeMs = DateTime(now.year, now.month, now.day, hour, minute, 0)
.millisecondsSinceEpoch;
});
}
String _formatTime(int timeMs) {
final dt = DateTime.fromMillisecondsSinceEpoch(timeMs);
return "${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}";
}
@override
Widget build(BuildContext context) {
// 查找当前时间所属的段
RecordSegment? currentSegment;
try {
currentSegment = _segments.firstWhere((s) => s.contains(_currentTimeMs));
} catch (_) {}
return Scaffold(
appBar: AppBar(
title: const Text('录像时间轴演示'),
backgroundColor: Colors.blueAccent,
),
body: Column(
children: [
// 视频预览占位
Expanded(
child: Container(
color: Colors.black,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.videocam, color: Colors.white, size: 64),
const SizedBox(height: 16),
Text(
_formatTime(_currentTimeMs),
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
if (currentSegment != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
"正在播放: ${currentSegment.type.name.toUpperCase()} 录像",
style: TextStyle(
color: currentSegment.type.getDefaultColor(),
fontSize: 16),
),
),
],
),
),
),
),
// 控制栏
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow,
size: 36),
onPressed: _togglePlayback,
),
ElevatedButton(
onPressed: () => _jumpToTime(8, 0),
child: const Text('08:00'),
),
ElevatedButton(
onPressed: () => _jumpToTime(12, 0),
child: const Text('12:00'),
),
ElevatedButton(
onPressed: () => _jumpToTime(20, 0),
child: const Text('20:00'),
),
],
),
),
// 时间轴组件
TimeBarWidget(
segments: _segments,
currentTimeMs: _currentTimeMs,
onTimeChanged: (timeMs) {
setState(() {
_currentTimeMs = timeMs;
});
},
// 自定义配置示例
maxScale: 100.0,
timeBarOption: TimeBarOption(
height: _isLandscape ? 64 : 80,
backgroundColor: Colors.transparent,
),
videoAreaOption: VideoAreaOption(
height: _isLandscape ? 12.0 : 14.0,
topOffset: _isLandscape ? 26.0 : 28.0,
backgroundColor: Colors.black12,
),
middleIndicatorOption: MiddleIndicatorOption(
fillColor: const Color(0xFF0A59F7),
timeTextStyle: TextStyle(
color: const Color(0xFF0A59F7),
fontSize: 12.0,
fontWeight: FontWeight.bold,
),
),
timeScaleOption: TimeScaleOption(
scaleTextColor: Colors.blue,
scaleLineColor: Colors.blue,
),
),
// 图例
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildLegend(SegmentType.normal, '普通'),
const SizedBox(width: 16),
_buildLegend(SegmentType.motion, '移动'),
const SizedBox(width: 16),
_buildLegend(SegmentType.alert, '报警'),
const SizedBox(width: 16),
_buildLegend(SegmentType.custom, '自定义'),
],
),
),
const SizedBox(height: 16),
],
),
);
}
Widget _buildLegend(SegmentType type, String label) {
return Row(
children: [
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: type.getDefaultColor(),
borderRadius: BorderRadius.circular(2))),
const SizedBox(width: 4),
Text(label, style: const TextStyle(fontSize: 12)),
],
);
}
}