123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- import 'package:flutter/material.dart';
- import 'package:flyinsonolite/controls/text/fistext.dart';
- import 'package:flyinsonolite/infrastructure/scale.dart';
- typedef void ValueCallback<T>(T value);
- /// 音量调节控件 0-100
- class VolumeSlider extends StatefulWidget {
- final void Function(int) onVolumeChanged;
- final Widget child;
- final int initVolume;
- final double extraOffsetX;
- const VolumeSlider(
- {super.key,
- this.initVolume = 100,
- this.extraOffsetX = 0,
- required this.child,
- required this.onVolumeChanged});
- String get pageName => "VolumeSlider";
- @override
- State<StatefulWidget> createState() {
- return _VolumeSliderState();
- }
- }
- class _VolumeSliderState extends State<VolumeSlider> {
- /// 音量调节控件高度
- double get _height => 180.s;
- /// 音量调节控件宽度
- double get _width => 30.s;
- OverlayEntry? _entry;
- int _volume = 100;
- /// 滑动时最后一次的音量值
- int _lastChangedVolume = 100;
- /// 当前是否移入target
- bool _isEnterTarget = false;
- @override
- Widget build(BuildContext context) {
- return MouseRegion(
- cursor: SystemMouseCursors.click,
- child: GestureDetector(
- onTap: onTap,
- child: widget.child,
- ));
- }
- @override
- void dispose() {
- super.dispose();
- if (_entry != null) {
- _entry?.remove();
- _entry = null;
- }
- }
- @override
- void initState() {
- super.initState();
- _volume = widget.initVolume;
- _lastChangedVolume = widget.initVolume;
- }
- /// 创建 Overlay 组件
- Widget _buildOverlay(Offset offset) {
- return Directionality(
- textDirection: Directionality.of(context),
- child: Positioned(
- left: offset.dx,
- bottom: offset.dy,
- child: Container(
- decoration: BoxDecoration(
- color: Colors.black.withOpacity(0.5),
- borderRadius: BorderRadius.circular(8.s),
- ),
- margin: EdgeInsets.only(bottom: 5.s),
- width: _width * 2,
- height: _height,
- child: StatefulSlider(
- initVolume: _volume,
- onVolumeChanged: (value) {
- setState(() {
- _volume = value;
- });
- widget.onVolumeChanged.call(_volume);
- },
- onVolumeChanging: (value) {
- if (value == _lastChangedVolume) return;
- _lastChangedVolume = value;
- },
- ),
- ),
- ),
- );
- }
- /// 创建新的 OverlayEntry
- void _createNewEntry() {
- final OverlayState overlayState = Overlay.of(
- context,
- debugRequiredFor: widget,
- );
- final RenderBox target = context.findRenderObject()! as RenderBox;
- final RenderBox targetConatiner =
- overlayState.context.findRenderObject() as RenderBox;
- final Offset targetOffset = target.localToGlobal(
- target.size.center(Offset.zero),
- ancestor: overlayState.context.findRenderObject(),
- );
- final Offset overlayOffset = Offset(
- targetOffset.dx - target.size.width + widget.extraOffsetX,
- targetConatiner.size.height - targetOffset.dy + target.size.height / 2,
- );
- final Widget overlay = _buildOverlay(overlayOffset);
- _entry = OverlayEntry(builder: (BuildContext context) => overlay);
- overlayState.insert(_entry!);
- }
- /// 隐藏 Tooltip
- void _hideTooltip() {
- _isEnterTarget = false;
- _hideTooltipLater();
- }
- /// 立即隐藏
- void _hideTooltipImmediately() {
- if (_entry == null) return;
- if (_entry!.mounted) {
- _entry?.remove();
- _entry = null;
- }
- }
- /// 稍后隐藏
- void _hideTooltipLater() {
- Future.delayed(const Duration(milliseconds: 500), () {
- if (_isEnterTarget) {
- _hideTooltipLater();
- return;
- }
- _preHandleWhenRemove();
- _hideTooltipImmediately();
- });
- }
- /// 提前移除的情况下预处理
- void _preHandleWhenRemove() {
- if (_lastChangedVolume != _volume) {
- setState(() {
- _volume = _lastChangedVolume;
- });
- widget.onVolumeChanged.call(_volume);
- }
- }
- /// 显示 Tooltip
- void _showTooltip() {
- _isEnterTarget = true;
- if (_entry != null) return;
- _createNewEntry();
- }
- void onTap() {
- if (_isEnterTarget) {
- _hideTooltip();
- } else {
- _showTooltip();
- }
- }
- }
- class StatefulSlider extends StatefulWidget {
- final ValueCallback<int> onVolumeChanged;
- final ValueCallback<int> onVolumeChanging;
- final int initVolume;
- const StatefulSlider(
- {super.key,
- required this.onVolumeChanged,
- required this.onVolumeChanging,
- required this.initVolume});
- @override
- _StatefulSliderState createState() => _StatefulSliderState();
- }
- class _StatefulSliderState extends State<StatefulSlider> {
- int _volume = 100;
- @override
- Widget build(BuildContext context) {
- /// 音量调节文字高度
- double height = 30.s;
- double width = 30.s;
- return Column(
- children: [
- Expanded(
- child: RotatedBox(
- quarterTurns: 3,
- child: SliderTheme(
- data: SliderThemeData(
- trackHeight: 5.s,
- thumbShape: RoundSliderThumbShape(
- enabledThumbRadius: 10.s,
- ),
- overlayShape: RoundSliderOverlayShape(overlayRadius: 24.s)),
- child: Slider(
- min: 0,
- max: 100,
- divisions: 100,
- value: _volume.toDouble(),
- onChanged: (value) {
- widget.onVolumeChanging(value.toInt());
- setState(() {
- _volume = value.toInt();
- });
- },
- onChangeEnd: (value) {
- widget.onVolumeChanged.call(value.toInt());
- },
- )),
- )),
- SizedBox(
- width: width,
- height: height,
- child: Center(
- child: FISText(
- "$_volume",
- style: TextStyle(color: Colors.white, fontSize: 14.s),
- )),
- ),
- ],
- );
- }
- @override
- void initState() {
- super.initState();
- _volume = widget.initVolume;
- }
- @override
- void didUpdateWidget(StatefulSlider oldWidget) {
- super.didUpdateWidget(oldWidget);
- _volume = widget.initVolume;
- }
- }
|