mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
20 KiB
20 KiB
交互规范
本文档详细说明组件交互设计规范,包括交互状态、动画过渡、反馈机制和拖拽调整。
🎯 交互设计原则
- 即时反馈 - 所有操作都应有立即的视觉反馈
- 清晰可预测 - 用户能预期操作的结果
- 流畅自然 - 动画和过渡平滑流畅
- 符合直觉 - 遵循用户的使用习惯
- 宽容错误 - 允许撤销和恢复
🖱️ 交互状态
标准交互状态
所有可交互元素都应该有以下状态:
| 状态 | 说明 | 视觉表现 |
|---|---|---|
| 正常(Normal) | 默认状态 | 标准样式 |
| 悬停(Hover) | 鼠标悬停 | 背景变化、光标变化 |
| 按下(Pressed) | 鼠标按下 | 背景更暗、轻微缩放 |
| 聚焦(Focused) | 键盘聚焦 | 显示聚焦环 |
| 禁用(Disabled) | 不可用 | 降低透明度、灰色显示 |
| 选中(Selected) | 被选中 | 强调色背景 |
按钮状态
主要按钮(Primary Button)
<Button Content="确定"
Padding="12,6"
Background="{DynamicResource AccentBrush}"
Foreground="White">
<Button.Styles>
<!-- 悬停状态 -->
<Style Selector="Button:pointerover">
<Style.Animations>
<Animation Duration="0:0:0.15" Easing="CubicEaseOut">
<KeyFrame Cue="100%">
<Setter Property="Background"
Value="{DynamicResource AccentHoverBrush}"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<!-- 按下状态 -->
<Style Selector="Button:pressed">
<Style.Animations>
<Animation Duration="0:0:0.1" Easing="CubicEaseOut">
<KeyFrame Cue="100%">
<Setter Property="Background"
Value="{DynamicResource AccentPressedBrush}"/>
<Setter Property="RenderTransform">
<ScaleTransform ScaleX="0.98" ScaleY="0.98"/>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<!-- 禁用状态 -->
<Style Selector="Button:disabled">
<Setter Property="Opacity" Value="0.5"/>
</Style>
</Button.Styles>
</Button>
次要按钮(Secondary Button)
<Button Content="取消"
Padding="12,6"
Background="{DynamicResource CardBackgroundSecondaryBrush}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}">
<Button.Styles>
<!-- 悬停状态 -->
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#EBEBEB"/>
</Style>
<!-- 按下状态 -->
<Style Selector="Button:pressed">
<Setter Property="Background" Value="#E0E0E0"/>
</Style>
</Button.Styles>
</Button>
图标按钮
<Button Padding="8"
Background="Transparent"
BorderThickness="0">
<TextBlock Text="🔄" FontSize="16"/>
<Button.Styles>
<!-- 悬停状态 -->
<Style Selector="Button:pointerover">
<Setter Property="Background"
Value="{DynamicResource CardBackgroundSecondaryBrush}"/>
</Style>
<!-- 按下状态 -->
<Style Selector="Button:pressed">
<Setter Property="Background" Value="#E0E0E0"/>
<Setter Property="RenderTransform">
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
</Setter>
</Style>
</Button.Styles>
</Button>
输入框状态
<TextBox Text="{Binding InputText}"
Watermark="请输入内容..."
Padding="8"
BorderBrush="{DynamicResource TextBoxBorderBrush}"
BorderThickness="1">
<TextBox.Styles>
<!-- 聚焦状态 -->
<Style Selector="TextBox:focus">
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
<!-- 错误状态 -->
<Style Selector="TextBox.error">
<Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
<!-- 禁用状态 -->
<Style Selector="TextBox:disabled">
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Background" Value="{DynamicResource CardBackgroundSecondaryBrush}"/>
</Style>
</TextBox.Styles>
</TextBox>
光标样式
<!-- 可点击元素 -->
<Button Cursor="Hand">点击我</Button>
<!-- 文本输入 -->
<TextBox Cursor="IBeam"/>
<!-- 拖拽元素 -->
<Border Cursor="SizeAll">拖动我</Border>
<!-- 调整大小 -->
<Border Cursor="SizeNWSE">调整大小</Border>
<!-- 禁用元素 -->
<Button IsEnabled="False" Cursor="No">禁用</Button>
🎬 动画与过渡
动画时长标准
| 类型 | 时长 | 使用场景 |
|---|---|---|
| 微交互 | 100-150ms | 悬停、点击 |
| 短动画 | 200-300ms | 展开、收起 |
| 中动画 | 300-500ms | 页面切换、弹出 |
| 长动画 | 500-800ms | 复杂过渡 |
缓动函数(Easing)
| 函数 | 效果 | 使用场景 |
|---|---|---|
| Linear | 线性 | 加载动画、循环动画 |
| CubicEaseOut | 快进慢出 | 大部分交互动画 |
| CubicEaseIn | 慢进快出 | 元素退出 |
| CubicEaseInOut | 慢进慢出 | 平滑过渡 |
| BackEaseOut | 回弹效果 | 强调动画 |
| ElasticEaseOut | 弹性效果 | 有趣的交互 |
悬停动画
<Border Background="{DynamicResource CardBackgroundBrush}"
CornerRadius="8"
Padding="16">
<Border.Styles>
<Style Selector="Border:pointerover">
<Style.Animations>
<!-- 背景色过渡 -->
<Animation Duration="0:0:0.15" Easing="CubicEaseOut">
<KeyFrame Cue="100%">
<Setter Property="Background"
Value="{DynamicResource CardBackgroundSecondaryBrush}"/>
</KeyFrame>
</Animation>
<!-- 阴影过渡 -->
<Animation Duration="0:0:0.15" Easing="CubicEaseOut">
<KeyFrame Cue="100%">
<Setter Property="BoxShadow" Value="0 4 16 0 #26000000"/>
</KeyFrame>
</Animation>
<!-- 轻微上移 -->
<Animation Duration="0:0:0.15" Easing="CubicEaseOut">
<KeyFrame Cue="100%">
<Setter Property="RenderTransform">
<TranslateTransform Y="-2"/>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</Border>
点击动画
<Button Content="点击我" Padding="12,6">
<Button.Styles>
<Style Selector="Button:pressed">
<Style.Animations>
<!-- 缩放动画 -->
<Animation Duration="0:0:0.1" Easing="CubicEaseOut">
<KeyFrame Cue="100%">
<Setter Property="RenderTransform">
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Button.Styles>
</Button>
展开/收起动画
<Expander Header="点击展开" IsExpanded="{Binding IsExpanded}">
<Expander.ContentTransition>
<CrossFade Duration="0:0:0.3"/>
</Expander.ContentTransition>
<Border Padding="16">
<TextBlock Text="展开的内容" TextWrapping="Wrap"/>
</Border>
</Expander>
淡入/淡出动画
<!-- 元素淡入 -->
<Border Opacity="0">
<Border.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.3"/>
</Transitions>
</Border.Transitions>
<Border.Loaded>
<EventTrigger>
<ChangePropertyAction TargetName="Self" Property="Opacity" Value="1"/>
</EventTrigger>
</Border.Loaded>
</Border>
旋转动画(加载中)
<TextBlock Text="⏳" FontSize="24">
<TextBlock.RenderTransform>
<RotateTransform/>
</TextBlock.RenderTransform>
<TextBlock.Styles>
<Style Selector="TextBlock">
<Style.Animations>
<Animation Duration="0:0:1" IterationCount="Infinite">
<KeyFrame Cue="0%">
<Setter Property="RenderTransform">
<RotateTransform Angle="0"/>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RenderTransform">
<RotateTransform Angle="360"/>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</TextBlock.Styles>
</TextBlock>
脉冲动画(加载中)
<Border Background="{DynamicResource AccentBrush}"
Width="40" Height="40"
CornerRadius="20">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:1.5"
IterationCount="Infinite"
Easing="CubicEaseInOut">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="1"/>
<Setter Property="RenderTransform">
<ScaleTransform ScaleX="1" ScaleY="1"/>
</Setter>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="RenderTransform">
<ScaleTransform ScaleX="0.8" ScaleY="0.8"/>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="1"/>
<Setter Property="RenderTransform">
<ScaleTransform ScaleX="1" ScaleY="1"/>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</Border>
💬 反馈机制
加载状态
加载指示器
<!-- 旋转加载 -->
<StackPanel Spacing="8"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="⏳" FontSize="32">
<!-- 旋转动画(见上文) -->
</TextBlock>
<TextBlock Text="加载中..."
FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
</StackPanel>
<!-- 进度条 -->
<ProgressBar Value="{Binding Progress}"
Minimum="0"
Maximum="100"
Height="4"
Foreground="{DynamicResource AccentBrush}"/>
<!-- 不确定进度 -->
<ProgressBar IsIndeterminate="True"
Height="4"
Foreground="{DynamicResource AccentBrush}"/>
骨架屏
<StackPanel Spacing="8">
<!-- 标题骨架 -->
<Border Width="120" Height="20"
Background="#F0F0F0"
CornerRadius="4"/>
<!-- 内容骨架 -->
<Border Width="200" Height="16"
Background="#F0F0F0"
CornerRadius="4"/>
<Border Width="180" Height="16"
Background="#F0F0F0"
CornerRadius="4"/>
<!-- 添加脉冲动画 -->
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:1.5"
IterationCount="Infinite"
Easing="CubicEaseInOut">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="Opacity" Value="0.5"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</StackPanel>
错误状态
<Border Background="{DynamicResource CardBackgroundBrush}"
BorderBrush="{DynamicResource ErrorBrush}"
BorderThickness="2"
CornerRadius="8"
Padding="16">
<StackPanel Spacing="12">
<!-- 错误图标 -->
<TextBlock Text="❌"
FontSize="32"
HorizontalAlignment="Center"/>
<!-- 错误信息 -->
<TextBlock Text="加载失败"
FontSize="16"
FontWeight="SemiBold"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock Text="网络连接失败,请检查网络设置"
FontSize="14"
TextWrapping="Wrap"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
<!-- 重试按钮 -->
<Button Content="重试"
Command="{Binding RetryCommand}"
HorizontalAlignment="Center"
Padding="16,6"
Background="{DynamicResource AccentBrush}"
Foreground="White"/>
</StackPanel>
</Border>
空状态
<StackPanel Spacing="16"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<!-- 空状态图标 -->
<TextBlock Text="📭"
FontSize="48"
HorizontalAlignment="Center"/>
<!-- 空状态文字 -->
<StackPanel Spacing="8">
<TextBlock Text="暂无数据"
FontSize="16"
FontWeight="SemiBold"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock Text="添加第一个项目开始使用"
FontSize="14"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
</StackPanel>
<!-- 操作按钮 -->
<Button Content="添加项目"
Command="{Binding AddCommand}"
Padding="16,6"
Background="{DynamicResource AccentBrush}"
Foreground="White"/>
</StackPanel>
成功反馈
<!-- 简短通知(Toast) -->
<Border Background="{DynamicResource SuccessBrush}"
CornerRadius="8"
Padding="12,8"
BoxShadow="0 4 16 0 #26000000">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="✅" FontSize="16"/>
<TextBlock Text="操作成功"
FontSize="14"
Foreground="White"/>
</StackPanel>
<!-- 自动淡出动画 -->
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:0.3" Delay="0:0:2" FillMode="Forward">
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</Border>
提示信息(Tooltip)
<Button Content="🔄"
Padding="8"
ToolTip.Tip="刷新数据"
ToolTip.ShowDelay="500">
<!-- 按钮内容 -->
</Button>
<!-- 自定义 Tooltip -->
<Button Content="⚙️" Padding="8">
<ToolTip.Tip>
<Border Background="{DynamicResource CardBackgroundBrush}"
BorderBrush="{DynamicResource CardBorderBrush}"
BorderThickness="1"
CornerRadius="4"
Padding="8"
BoxShadow="0 2 8 0 #1A000000">
<StackPanel Spacing="4">
<TextBlock Text="设置"
FontSize="14"
FontWeight="SemiBold"/>
<TextBlock Text="打开组件设置"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
</StackPanel>
</Border>
</ToolTip.Tip>
</Button>
🖐️ 拖拽与调整
拖拽组件
组件应支持拖拽移动:
public class DraggableComponent : ComponentBase
{
private Point _dragStartPoint;
private bool _isDragging;
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
_dragStartPoint = e.GetPosition(this);
_isDragging = true;
Cursor = new Cursor(StandardCursorType.SizeAll);
}
protected override void OnPointerMoved(PointerEventArgs e)
{
if (_isDragging)
{
var currentPosition = e.GetPosition(this.Parent as Visual);
var offset = currentPosition - _dragStartPoint;
// 更新位置
Canvas.SetLeft(this, Canvas.GetLeft(this) + offset.X);
Canvas.SetTop(this, Canvas.GetTop(this) + offset.Y);
}
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
_isDragging = false;
Cursor = new Cursor(StandardCursorType.Arrow);
// 保存位置
SavePosition();
}
}
调整大小
组件应支持调整尺寸:
<Border Width="{Binding Width}"
Height="{Binding Height}"
Background="{DynamicResource CardBackgroundBrush}">
<!-- 组件内容 -->
<Grid>
<!-- ... -->
</Grid>
<!-- 调整大小手柄 -->
<Grid>
<!-- 右下角手柄 -->
<Border Width="12" Height="12"
Background="{DynamicResource AccentBrush}"
CornerRadius="6"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Cursor="SizeNWSE"
PointerPressed="OnResizeHandlePressed"
PointerMoved="OnResizeHandleMoved"
PointerReleased="OnResizeHandleReleased"/>
</Grid>
</Border>
private void OnResizeHandlePressed(object sender, PointerPressedEventArgs e)
{
_isResizing = true;
_resizeStartPoint = e.GetPosition(this.Parent as Visual);
_initialWidth = Width;
_initialHeight = Height;
}
private void OnResizeHandleMoved(object sender, PointerEventArgs e)
{
if (_isResizing)
{
var currentPoint = e.GetPosition(this.Parent as Visual);
var delta = currentPoint - _resizeStartPoint;
Width = Math.Max(MinWidth, _initialWidth + delta.X);
Height = Math.Max(MinHeight, _initialHeight + delta.Y);
}
}
private void OnResizeHandleReleased(object sender, PointerReleasedEventArgs e)
{
_isResizing = false;
SaveSize();
}
拖拽反馈
<!-- 拖拽时显示阴影 -->
<Border.Styles>
<Style Selector="Border.dragging">
<Setter Property="BoxShadow" Value="0 8 24 0 #33000000"/>
<Setter Property="Opacity" Value="0.8"/>
</Style>
</Border.Styles>
⌨️ 键盘交互
快捷键
常用快捷键:
| 快捷键 | 操作 |
|---|---|
| Enter | 确认、提交 |
| Esc | 取消、关闭 |
| Tab | 焦点切换 |
| Space | 激活按钮 |
| 方向键 | 导航、选择 |
| Ctrl+S | 保存 |
| Ctrl+Z | 撤销 |
| Ctrl+Y | 重做 |
实现快捷键
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
ConfirmAction();
e.Handled = true;
break;
case Key.Escape:
CancelAction();
e.Handled = true;
break;
case Key.S when e.KeyModifiers.HasFlag(KeyModifiers.Control):
SaveAction();
e.Handled = true;
break;
}
base.OnKeyDown(e);
}
焦点管理
<!-- 设置初始焦点 -->
<TextBox Name="UsernameBox"
Text="{Binding Username}"
Loaded="OnLoaded"/>
<!-- 代码设置焦点 -->
private void OnLoaded(object sender, RoutedEventArgs e)
{
UsernameBox.Focus();
}
<!-- Tab 顺序 -->
<StackPanel>
<TextBox TabIndex="1"/>
<TextBox TabIndex="2"/>
<Button TabIndex="3"/>
</StackPanel>
✅ 交互检查清单
发布前请检查:
状态反馈
- 所有按钮有悬停状态
- 所有按钮有按下状态
- 禁用状态清晰可见
- 加载状态有明确提示
- 错误状态有友好说明
动画
- 动画流畅不卡顿
- 动画时长合适(100-500ms)
- 使用合适的缓动函数
- 不影响性能
- 可以禁用动画
反馈
- 操作成功有提示
- 操作失败有说明
- 空状态有引导
- 加载中有指示
- 提示信息清晰
拖拽与调整
- 组件可拖拽移动
- 组件可调整大小
- 拖拽有视觉反馈
- 调整大小有限制
- 位置和尺寸可保存
键盘交互
- Tab 键可切换焦点
- Enter 键可确认操作
- Esc 键可取消操作
- 快捷键正常工作
- 焦点状态清晰可见
📖 相关文档
记住: 即时反馈、流畅动画、清晰提示、直觉交互。