volumeslider.dart 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import 'package:flutter/material.dart';
  2. import 'package:flyinsonolite/controls/text/fistext.dart';
  3. import 'package:flyinsonolite/infrastructure/scale.dart';
  4. typedef void ValueCallback<T>(T value);
  5. /// 音量调节控件 0-100
  6. class VolumeSlider extends StatefulWidget {
  7. final void Function(int) onVolumeChanged;
  8. final Widget child;
  9. final int initVolume;
  10. final double extraOffsetX;
  11. const VolumeSlider(
  12. {super.key,
  13. this.initVolume = 100,
  14. this.extraOffsetX = 0,
  15. required this.child,
  16. required this.onVolumeChanged});
  17. String get pageName => "VolumeSlider";
  18. @override
  19. State<StatefulWidget> createState() {
  20. return _VolumeSliderState();
  21. }
  22. }
  23. class _VolumeSliderState extends State<VolumeSlider> {
  24. /// 音量调节控件高度
  25. double get _height => 180.s;
  26. /// 音量调节控件宽度
  27. double get _width => 30.s;
  28. OverlayEntry? _entry;
  29. int _volume = 100;
  30. /// 滑动时最后一次的音量值
  31. int _lastChangedVolume = 100;
  32. /// 当前是否移入target
  33. bool _isEnterTarget = false;
  34. @override
  35. Widget build(BuildContext context) {
  36. return MouseRegion(
  37. cursor: SystemMouseCursors.click,
  38. child: GestureDetector(
  39. onTap: onTap,
  40. child: widget.child,
  41. ));
  42. }
  43. @override
  44. void dispose() {
  45. super.dispose();
  46. if (_entry != null) {
  47. _entry?.remove();
  48. _entry = null;
  49. }
  50. }
  51. @override
  52. void initState() {
  53. super.initState();
  54. _volume = widget.initVolume;
  55. _lastChangedVolume = widget.initVolume;
  56. }
  57. /// 创建 Overlay 组件
  58. Widget _buildOverlay(Offset offset) {
  59. return Directionality(
  60. textDirection: Directionality.of(context),
  61. child: Positioned(
  62. left: offset.dx,
  63. bottom: offset.dy,
  64. child: Container(
  65. decoration: BoxDecoration(
  66. color: Colors.black.withOpacity(0.5),
  67. borderRadius: BorderRadius.circular(8.s),
  68. ),
  69. margin: EdgeInsets.only(bottom: 5.s),
  70. width: _width * 2,
  71. height: _height,
  72. child: StatefulSlider(
  73. initVolume: _volume,
  74. onVolumeChanged: (value) {
  75. setState(() {
  76. _volume = value;
  77. });
  78. widget.onVolumeChanged.call(_volume);
  79. },
  80. onVolumeChanging: (value) {
  81. if (value == _lastChangedVolume) return;
  82. _lastChangedVolume = value;
  83. },
  84. ),
  85. ),
  86. ),
  87. );
  88. }
  89. /// 创建新的 OverlayEntry
  90. void _createNewEntry() {
  91. final OverlayState overlayState = Overlay.of(
  92. context,
  93. debugRequiredFor: widget,
  94. );
  95. final RenderBox target = context.findRenderObject()! as RenderBox;
  96. final RenderBox targetConatiner =
  97. overlayState.context.findRenderObject() as RenderBox;
  98. final Offset targetOffset = target.localToGlobal(
  99. target.size.center(Offset.zero),
  100. ancestor: overlayState.context.findRenderObject(),
  101. );
  102. final Offset overlayOffset = Offset(
  103. targetOffset.dx - target.size.width + widget.extraOffsetX,
  104. targetConatiner.size.height - targetOffset.dy + target.size.height / 2,
  105. );
  106. final Widget overlay = _buildOverlay(overlayOffset);
  107. _entry = OverlayEntry(builder: (BuildContext context) => overlay);
  108. overlayState.insert(_entry!);
  109. }
  110. /// 隐藏 Tooltip
  111. void _hideTooltip() {
  112. _isEnterTarget = false;
  113. _hideTooltipLater();
  114. }
  115. /// 立即隐藏
  116. void _hideTooltipImmediately() {
  117. if (_entry == null) return;
  118. if (_entry!.mounted) {
  119. _entry?.remove();
  120. _entry = null;
  121. }
  122. }
  123. /// 稍后隐藏
  124. void _hideTooltipLater() {
  125. Future.delayed(const Duration(milliseconds: 500), () {
  126. if (_isEnterTarget) {
  127. _hideTooltipLater();
  128. return;
  129. }
  130. _preHandleWhenRemove();
  131. _hideTooltipImmediately();
  132. });
  133. }
  134. /// 提前移除的情况下预处理
  135. void _preHandleWhenRemove() {
  136. if (_lastChangedVolume != _volume) {
  137. setState(() {
  138. _volume = _lastChangedVolume;
  139. });
  140. widget.onVolumeChanged.call(_volume);
  141. }
  142. }
  143. /// 显示 Tooltip
  144. void _showTooltip() {
  145. _isEnterTarget = true;
  146. if (_entry != null) return;
  147. _createNewEntry();
  148. }
  149. void onTap() {
  150. if (_isEnterTarget) {
  151. _hideTooltip();
  152. } else {
  153. _showTooltip();
  154. }
  155. }
  156. }
  157. class StatefulSlider extends StatefulWidget {
  158. final ValueCallback<int> onVolumeChanged;
  159. final ValueCallback<int> onVolumeChanging;
  160. final int initVolume;
  161. const StatefulSlider(
  162. {super.key,
  163. required this.onVolumeChanged,
  164. required this.onVolumeChanging,
  165. required this.initVolume});
  166. @override
  167. _StatefulSliderState createState() => _StatefulSliderState();
  168. }
  169. class _StatefulSliderState extends State<StatefulSlider> {
  170. int _volume = 100;
  171. @override
  172. Widget build(BuildContext context) {
  173. /// 音量调节文字高度
  174. double height = 30.s;
  175. double width = 30.s;
  176. return Column(
  177. children: [
  178. Expanded(
  179. child: RotatedBox(
  180. quarterTurns: 3,
  181. child: SliderTheme(
  182. data: SliderThemeData(
  183. trackHeight: 5.s,
  184. thumbShape: RoundSliderThumbShape(
  185. enabledThumbRadius: 10.s,
  186. ),
  187. overlayShape: RoundSliderOverlayShape(overlayRadius: 24.s)),
  188. child: Slider(
  189. min: 0,
  190. max: 100,
  191. divisions: 100,
  192. value: _volume.toDouble(),
  193. onChanged: (value) {
  194. widget.onVolumeChanging(value.toInt());
  195. setState(() {
  196. _volume = value.toInt();
  197. });
  198. },
  199. onChangeEnd: (value) {
  200. widget.onVolumeChanged.call(value.toInt());
  201. },
  202. )),
  203. )),
  204. SizedBox(
  205. width: width,
  206. height: height,
  207. child: Center(
  208. child: FISText(
  209. "$_volume",
  210. style: TextStyle(color: Colors.white, fontSize: 14.s),
  211. )),
  212. ),
  213. ],
  214. );
  215. }
  216. @override
  217. void initState() {
  218. super.initState();
  219. _volume = widget.initVolume;
  220. }
  221. @override
  222. void didUpdateWidget(StatefulSlider oldWidget) {
  223. super.didUpdateWidget(oldWidget);
  224. _volume = widget.initVolume;
  225. }
  226. }