Files

802 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 交互规范
本文档详细说明组件交互设计规范,包括交互状态、动画过渡、反馈机制和拖拽调整。
## 🎯 交互设计原则
- **即时反馈** - 所有操作都应有立即的视觉反馈
- **清晰可预测** - 用户能预期操作的结果
- **流畅自然** - 动画和过渡平滑流畅
- **符合直觉** - 遵循用户的使用习惯
- **宽容错误** - 允许撤销和恢复
## 🖱️ 交互状态
### 标准交互状态
所有可交互元素都应该有以下状态:
| 状态 | 说明 | 视觉表现 |
|-----|------|---------|
| **正常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) - 组件开发
---
**记住**: 即时反馈、流畅动画、清晰提示、直觉交互。