Files

802 lines
20 KiB
Markdown
Raw Permalink Normal View History

# 交互规范
本文档详细说明组件交互设计规范,包括交互状态、动画过渡、反馈机制和拖拽调整。
## 🎯 交互设计原则
- **即时反馈** - 所有操作都应有立即的视觉反馈
- **清晰可预测** - 用户能预期操作的结果
- **流畅自然** - 动画和过渡平滑流畅
- **符合直觉** - 遵循用户的使用习惯
- **宽容错误** - 允许撤销和恢复
## 🖱️ 交互状态
### 标准交互状态
所有可交互元素都应该有以下状态:
| 状态 | 说明 | 视觉表现 |
|-----|------|---------|
| **正常Normal** | 默认状态 | 标准样式 |
| **悬停Hover** | 鼠标悬停 | 背景变化、光标变化 |
| **按下Pressed** | 鼠标按下 | 背景更暗、轻微缩放 |
| **聚焦Focused** | 键盘聚焦 | 显示聚焦环 |
| **禁用Disabled** | 不可用 | 降低透明度、灰色显示 |
| **选中Selected** | 被选中 | 强调色背景 |
### 按钮状态
#### 主要按钮Primary Button
```xml
<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
```xml
<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>
```
#### 图标按钮
```xml
<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>
```
### 输入框状态
```xml
<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>
```
### 光标样式
```xml
<!-- 可点击元素 -->
<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** | 弹性效果 | 有趣的交互 |
### 悬停动画
```xml
<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>
```
### 点击动画
```xml
<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>
```
### 展开/收起动画
```xml
<Expander Header="点击展开" IsExpanded="{Binding IsExpanded}">
<Expander.ContentTransition>
<CrossFade Duration="0:0:0.3"/>
</Expander.ContentTransition>
<Border Padding="16">
<TextBlock Text="展开的内容" TextWrapping="Wrap"/>
</Border>
</Expander>
```
### 淡入/淡出动画
```xml
<!-- 元素淡入 -->
<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>
```
### 旋转动画(加载中)
```xml
<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>
```
### 脉冲动画(加载中)
```xml
<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>
```
## 💬 反馈机制
### 加载状态
#### 加载指示器
```xml
<!-- 旋转加载 -->
<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}"/>
```
#### 骨架屏
```xml
<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>
```
### 错误状态
```xml
<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>
```
### 空状态
```xml
<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>
```
### 成功反馈
```xml
<!-- 简短通知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
```xml
<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>
```
## 🖐️ 拖拽与调整
### 拖拽组件
组件应支持拖拽移动:
```csharp
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();
}
}
```
### 调整大小
组件应支持调整尺寸:
```xml
<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>
```
```csharp
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();
}
```
### 拖拽反馈
```xml
<!-- 拖拽时显示阴影 -->
<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** | 重做 |
### 实现快捷键
```csharp
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);
}
```
### 焦点管理
```xml
<!-- 设置初始焦点 -->
<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 键可取消操作
- [ ] 快捷键正常工作
- [ ] 焦点状态清晰可见
## 📖 相关文档
- [布局规范](03-布局规范.md) - 安全区域和间距
- [视觉规范](02-视觉规范.md) - 颜色、字体、图标
- [主题系统](05-主题系统.md) - 主题切换实现
- [组件系统](../01-插件开发/02-核心概念/02-组件系统.md) - 组件开发
---
**记住**: 即时反馈、流畅动画、清晰提示、直觉交互。