lib/ui/aliplayer_setting_menu_panel.dart (189 lines of code) (raw):

// Copyright © 2025 Alibaba Cloud. All rights reserved. // // Author: keria // Date: 2025/2/12 // Brief: 播放器设置面板控件 import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'aliplayer_custom_selector_widget.dart'; import 'aliplayer_custom_slider_widget.dart'; import 'aliplayer_custom_switch_widget.dart'; /// 设置项类型枚举 enum SettingItemType { slider, // 滑块类型 selector, // 选择器类型 switcher, // 开关类型 } /// 设置项数据模型 class SettingItem<T> { /// 设置项类型 final SettingItemType type; /// 设置项名称 final String? text; /// 左侧图标 final IconData? startIcon; /// 右侧图标 final IconData? endIcon; /// 可选项(仅用于选择器) final List<T>? options; /// 初始值 final T initialValue; /// 值变化时的回调 final ValueChanged<T>? onChanged; /// 格式化显示内容(仅用于选择器) final String Function(T)? displayFormatter; const SettingItem({ required this.type, required this.text, required this.initialValue, required this.onChanged, this.startIcon, this.endIcon, this.options, this.displayFormatter, }); /// 比较两个 SettingItem 是否相等 bool isEqualTo(SettingItem<T> other) { return this.type == other.type && this.text == other.text && this.initialValue == other.initialValue && this.startIcon == other.startIcon && this.endIcon == other.endIcon && listEquals(this.options, other.options); } } /// 播放器设置面板控件 class AliPlayerSettingMenuPanel extends StatefulWidget { /// 面板是否可见 final bool isVisible; /// 面板可见性变化时的回调 final ValueChanged<bool>? onVisibilityChanged; /// 设置项列表 final List<SettingItem> settingItems; const AliPlayerSettingMenuPanel({ super.key, required this.isVisible, required this.onVisibilityChanged, required this.settingItems, }); @override State<AliPlayerSettingMenuPanel> createState() => _AliPlayerSettingMenuPanelState(); } class _AliPlayerSettingMenuPanelState extends State<AliPlayerSettingMenuPanel> with SingleTickerProviderStateMixin { /// 控制面板的动画 late AnimationController _controller; late Animation<Offset> _animation; /// 设置面板是否可见 bool _isSettingPanelVisible = false; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _animation = Tween<Offset>( begin: const Offset(1, 0), end: Offset.zero, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, )); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Stack( children: [ if (_isSettingPanelVisible) GestureDetector( behavior: HitTestBehavior.opaque, onTap: _togglePanelVisibility, child: Container( color: Colors.black.withOpacity(0.5), ), ), SlideTransition( position: _animation, child: Align( alignment: Alignment.centerRight, child: _isSettingPanelVisible ? _buildSettingPanel(context) : null, ), ), ], ); } /// 构建设置面板的内容 /// /// Builds the content of the setting panel. Widget _buildSettingPanel(BuildContext context) { return Container( width: MediaQuery.of(context).size.width * 0.6, height: double.infinity, color: Colors.white.withOpacity(0.3), child: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: widget.settingItems.map(_buildSettingItem).toList(), ), ), ), ); } /// 构建设置项 /// /// Build the setting item. Widget _buildSettingItem(SettingItem item) { switch (item.type) { case SettingItemType.slider: return _buildSliderItem(item); case SettingItemType.selector: return _buildSelectorItem(item); case SettingItemType.switcher: return _buildSwitcherItem(item); } } /// 构建滑块类型的设置项 /// /// Build the slider type setting item. Widget _buildSliderItem(SettingItem item) { return AliPlayerCustomSliderWidget( text: item.text ?? "Unknown", startIcon: item.startIcon ?? Icons.settings_rounded, endIcon: item.endIcon ?? Icons.settings_rounded, initialValue: item.initialValue, onChanged: item.onChanged as ValueChanged<double>?, ); } /// 构建选择器类型的设置项 /// /// Build the selector type setting item. Widget _buildSelectorItem(SettingItem item) { return AliPlayerCustomSelectorWidget( text: item.text ?? "Unknown", startIcon: item.startIcon ?? Icons.settings_rounded, options: item.options, initialValue: item.initialValue, onChanged: item.onChanged, displayFormatter: item.displayFormatter, ); } /// 构建开关类型的设置项 /// /// Build the switch type setting item. Widget _buildSwitcherItem(SettingItem item) { return AliPlayerCustomSwitchWidget( text: item.text ?? "Unknown", startIcon: item.startIcon ?? Icons.settings_rounded, initialValue: item.initialValue, onChanged: item.onChanged, ); } /// 切换面板的显示状态 /// /// Toggles the visibility of the setting panel. void _togglePanelVisibility({bool? isVisible}) { setState(() { // 如果传入了显式的 isVisible 值,则直接使用它;否则切换当前状态 final shouldShow = isVisible ?? !_isSettingPanelVisible; if (shouldShow) { _controller.forward(); } else { _controller.reverse(); } _isSettingPanelVisible = shouldShow; // 通知外部可见性变化 widget.onVisibilityChanged?.call(_isSettingPanelVisible); }); } /// 状态更新回调 /// 当 Widget 的状态被更新时,该方法被调用。 /// /// Called when the state of the widget is updated. @override void didUpdateWidget(covariant AliPlayerSettingMenuPanel oldWidget) { super.didUpdateWidget(oldWidget); // 如果 isVisible 发生变化,更新面板的显示状态 if (oldWidget.isVisible != widget.isVisible) { _togglePanelVisibility(isVisible: widget.isVisible); } // 如果 settingItems 发生变化,触发刷新 if (!_areSettingItemsEqual(oldWidget.settingItems, widget.settingItems)) { setState(() {}); } } /// 检查两个 settingItems 列表是否相等 /// /// Checks if two setting item lists are equal. bool _areSettingItemsEqual( List<SettingItem>? oldItems, List<SettingItem>? newItems, ) { // Handle null values. if (oldItems == null || newItems == null) { return oldItems == newItems; // Both null or one is null. } // Return true if the references are identical. if (identical(oldItems, newItems)) return true; // Return false if lengths differ. if (oldItems.length != newItems.length) return false; // Compare each element using Iterable.every. return oldItems .asMap() .entries .every((entry) => entry.value.isEqualTo(newItems[entry.key])); } }