using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.ComponentModel; namespace BetterGenshinImpact.View.Controls.Drawer; public class CustomDrawer : ContentControl { #region 依赖属性 public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(CustomDrawer), new PropertyMetadata(false, OnIsOpenChanged)); public static readonly DependencyProperty DrawerPositionProperty = DependencyProperty.Register(nameof(DrawerPosition), typeof(DrawerPosition), typeof(CustomDrawer), new PropertyMetadata(DrawerPosition.Right, OnDrawerPositionChanged)); public static readonly DependencyProperty OpenWidthProperty = DependencyProperty.Register(nameof(OpenWidth), typeof(double), typeof(CustomDrawer), new PropertyMetadata(400.0)); public static readonly DependencyProperty OpenHeightProperty = DependencyProperty.Register(nameof(OpenHeight), typeof(double), typeof(CustomDrawer), new PropertyMetadata(300.0)); public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register(nameof(AnimationDuration), typeof(TimeSpan), typeof(CustomDrawer), new PropertyMetadata(TimeSpan.FromMilliseconds(200))); public static readonly DependencyProperty BackgroundOpacityProperty = DependencyProperty.Register(nameof(BackgroundOpacity), typeof(double), typeof(CustomDrawer), new PropertyMetadata(0.6)); public static readonly DependencyProperty DrawerBackgroundProperty = DependencyProperty.Register(nameof(DrawerBackground), typeof(Brush), typeof(CustomDrawer), new PropertyMetadata(Brushes.Black)); #endregion #region 事件 /// /// 抽屉打开后触发的事件 /// public event EventHandler Opened; /// /// 抽屉关闭前触发的事件,可以取消关闭操作 /// public event CancelEventHandler Closing; /// /// 引发 Opened 事件 /// protected virtual void OnOpened() { Opened?.Invoke(this, EventArgs.Empty); } /// /// 引发 Closing 事件 /// /// 如果取消关闭,则返回 true;否则返回 false protected virtual bool OnClosing() { if (Closing != null) { CancelEventArgs args = new CancelEventArgs(); Closing(this, args); return args.Cancel; } return false; } #endregion #region 属性 public bool IsOpen { get => (bool)GetValue(IsOpenProperty); set => SetValue(IsOpenProperty, value); } public DrawerPosition DrawerPosition { get => (DrawerPosition)GetValue(DrawerPositionProperty); set => SetValue(DrawerPositionProperty, value); } public double OpenWidth { get => (double)GetValue(OpenWidthProperty); set => SetValue(OpenWidthProperty, value); } public double OpenHeight { get => (double)GetValue(OpenHeightProperty); set => SetValue(OpenHeightProperty, value); } public TimeSpan AnimationDuration { get => (TimeSpan)GetValue(AnimationDurationProperty); set => SetValue(AnimationDurationProperty, value); } public double BackgroundOpacity { get => (double)GetValue(BackgroundOpacityProperty); set => SetValue(BackgroundOpacityProperty, value); } public Brush DrawerBackground { get => (Brush)GetValue(DrawerBackgroundProperty); set => SetValue(DrawerBackgroundProperty, value); } #endregion private Border _backgroundOverlay; private Border _drawerContainer; private Grid _mainGrid; static CustomDrawer() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDrawer), new FrameworkPropertyMetadata(typeof(CustomDrawer))); } public CustomDrawer() { this.Loaded += CustomDrawer_Loaded; } public override void OnApplyTemplate() { base.OnApplyTemplate(); _mainGrid = GetTemplateChild("PART_MainGrid") as Grid; _backgroundOverlay = GetTemplateChild("PART_BackgroundOverlay") as Border; _drawerContainer = GetTemplateChild("PART_DrawerContainer") as Border; if (_backgroundOverlay != null) { _backgroundOverlay.MouseDown += BackgroundOverlay_MouseDown; } UpdateDrawerPosition(); UpdateOpenState(false); } private void CustomDrawer_Loaded(object sender, RoutedEventArgs e) { UpdateOpenState(false); } private void BackgroundOverlay_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { CloseDrawer(); } private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is CustomDrawer drawer) { bool newValue = (bool)e.NewValue; bool oldValue = (bool)e.OldValue; // 如果是从打开到关闭状态,需要触发关闭前事件 if (oldValue && !newValue) { bool cancel = drawer.OnClosing(); if (cancel) { // 如果取消关闭,则恢复 IsOpen 为 true drawer.IsOpen = true; return; } } // 更新抽屉状态(动画) drawer.UpdateOpenState(true); } } private static void OnDrawerPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is CustomDrawer drawer) { drawer.UpdateDrawerPosition(); drawer.UpdateOpenState(false); } } private void UpdateDrawerPosition() { if (_drawerContainer == null) return; switch (DrawerPosition) { case DrawerPosition.Left: _drawerContainer.HorizontalAlignment = HorizontalAlignment.Left; _drawerContainer.VerticalAlignment = VerticalAlignment.Stretch; _drawerContainer.Width = OpenWidth; _drawerContainer.Height = double.NaN; break; case DrawerPosition.Right: _drawerContainer.HorizontalAlignment = HorizontalAlignment.Right; _drawerContainer.VerticalAlignment = VerticalAlignment.Stretch; _drawerContainer.Width = OpenWidth; _drawerContainer.Height = double.NaN; break; case DrawerPosition.Top: _drawerContainer.HorizontalAlignment = HorizontalAlignment.Stretch; _drawerContainer.VerticalAlignment = VerticalAlignment.Top; _drawerContainer.Width = double.NaN; _drawerContainer.Height = OpenHeight; break; case DrawerPosition.Bottom: _drawerContainer.HorizontalAlignment = HorizontalAlignment.Stretch; _drawerContainer.VerticalAlignment = VerticalAlignment.Bottom; _drawerContainer.Width = double.NaN; _drawerContainer.Height = OpenHeight; break; } } private void UpdateOpenState(bool animate) { if (_drawerContainer == null || _backgroundOverlay == null) return; _backgroundOverlay.IsHitTestVisible = IsOpen; _drawerContainer.Opacity = 1; if (IsOpen) { Visibility = Visibility.Visible; } // 每次更新状态时重新应用宽高 switch (DrawerPosition) { case DrawerPosition.Left: case DrawerPosition.Right: _drawerContainer.Width = OpenWidth; _drawerContainer.Height = double.NaN; break; case DrawerPosition.Top: case DrawerPosition.Bottom: _drawerContainer.Width = double.NaN; _drawerContainer.Height = OpenHeight; break; } if (animate) { // 动画背景遮罩 DoubleAnimation backgroundAnimation = new DoubleAnimation { To = IsOpen ? BackgroundOpacity : 0, Duration = AnimationDuration }; _backgroundOverlay.BeginAnimation(OpacityProperty, backgroundAnimation); // 确保RenderTransform已设置 if (_drawerContainer.RenderTransform == null || !(_drawerContainer.RenderTransform is TranslateTransform)) { _drawerContainer.RenderTransform = new TranslateTransform(); } TranslateTransform transform = (TranslateTransform)_drawerContainer.RenderTransform; // 动画抽屉 DoubleAnimation drawerAnimation = new DoubleAnimation { Duration = AnimationDuration, // 弹性动画效果 // EasingFunction = IsOpen // ? new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.3 } // : new ExponentialEase { EasingMode = EasingMode.EaseIn, Exponent = 6 } }; // 如果是打开操作,在动画完成时触发Opened事件 if (IsOpen) { drawerAnimation.Completed += (s, e) => OnOpened(); } switch (DrawerPosition) { case DrawerPosition.Left: // 打开时,先设置初始位置 if (IsOpen) { transform.X = -OpenWidth; } drawerAnimation.To = IsOpen ? 0 : -OpenWidth; transform.BeginAnimation(TranslateTransform.XProperty, drawerAnimation); break; case DrawerPosition.Right: // 打开时,先设置初始位置 if (IsOpen) { transform.X = OpenWidth; } drawerAnimation.To = IsOpen ? 0 : OpenWidth; transform.BeginAnimation(TranslateTransform.XProperty, drawerAnimation); break; case DrawerPosition.Top: // 打开时,先设置初始位置 if (IsOpen) { transform.Y = -OpenHeight; } drawerAnimation.To = IsOpen ? 0 : -OpenHeight; transform.BeginAnimation(TranslateTransform.YProperty, drawerAnimation); break; case DrawerPosition.Bottom: // 打开时,先设置初始位置 if (IsOpen) { transform.Y = OpenHeight; } drawerAnimation.To = IsOpen ? 0 : OpenHeight; transform.BeginAnimation(TranslateTransform.YProperty, drawerAnimation); break; } if (!IsOpen) { drawerAnimation.Completed += (s, e) => { if (!IsOpen) { Visibility = Visibility.Collapsed; } }; } } else { // 无动画直接设置 _backgroundOverlay.Opacity = IsOpen ? BackgroundOpacity : 0; TranslateTransform transform = new TranslateTransform(); _drawerContainer.RenderTransform = transform; switch (DrawerPosition) { case DrawerPosition.Left: transform.X = IsOpen ? 0 : -OpenWidth; break; case DrawerPosition.Right: transform.X = IsOpen ? 0 : OpenWidth; break; case DrawerPosition.Top: transform.Y = IsOpen ? 0 : -OpenHeight; break; case DrawerPosition.Bottom: transform.Y = IsOpen ? 0 : OpenHeight; break; } Visibility = IsOpen ? Visibility.Visible : Visibility.Collapsed; // 如果是无动画模式且正在打开,立即触发Opened事件 if (IsOpen) { OnOpened(); } } } /// /// 关闭抽屉的方法,会触发 Closing 事件并可能被取消 /// /// 如果成功关闭返回 true,如果被取消返回 false public bool CloseDrawer() { if (!IsOpen) return true; if (OnClosing()) return false; IsOpen = false; return true; } } public enum DrawerPosition { Left, Right, Top, Bottom }