1. 前言 VisualTransition是控制項模板中的重要組成部分,無論是自定義控制項或者修改控制項樣式都會接觸到VisualTransition。明明這麼重要,博客園上好像都沒多少關於VisualTransition的主題。 2. 什麼是VisualTransition VisualTransit ...
1. 前言
VisualTransition是控制項模板中的重要組成部分,無論是自定義控制項或者修改控制項樣式都會接觸到VisualTransition。明明這麼重要,博客園上好像都沒多少關於VisualTransition的主題。
2. 什麼是VisualTransition
VisualTransition動畫定義VisualState之前切換時的過渡行為,包括過渡時間和過渡動畫。
VisualTransition的類定義如下:
[ContentProperty(Name = "Storyboard")]
public class VisualTransition : DependencyObject, IVisualTransition
{
public VisualTransition();
// 摘要:
// 獲取或設置要轉換為的 Windows.UI.Xaml.VisualState 的名稱。
public string To { get; set; }
//
// 摘要:
// 獲取或設置在發生轉換時運行的 Windows.UI.Xaml.Media.Animation.Storyboard。
public Storyboard Storyboard { get; set; }
//
// 摘要:
// 獲取或設置應用於生成的動畫的緩動函數。
public EasingFunctionBase GeneratedEasingFunction { get; set; }
//
// 摘要:
// 獲取或設置從一種狀態轉換到另一種狀態所花的時間,以及任何隱式過渡動畫應作為過渡行為的一部分運行的時間
public Duration GeneratedDuration { get; set; }
//
// 摘要:
// 獲取或設置要轉換的 Windows.UI.Xaml.VisualState 的名稱。
public string From { get; set; }
}
3.為什麼使用VisualTransition
雖然自WPF4以來VisualTransition一直都存在,但很多人還是習慣這樣寫VisualState:
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="PointOverElement"
Duration="0"
To="1" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="PressElement"
Duration="0"
To="1" />
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled" />
</VisualStateGroup>
正確的做法應該是這樣:
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition To="PointerOver">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="PointOverElement">
<EasingDoubleKeyFrame KeyTime="0"
Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2"
Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition To="Pressed">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="PressElement">
<EasingDoubleKeyFrame KeyTime="0"
Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2"
Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition To="Disabled">
<Storyboard Completed="Storyboard_Completed"></Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="PointOverElement"
Duration="0"
To="1" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="PressElement"
Duration="0"
To="1" />
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled" />
</VisualStateGroup>
可以看到VisualState中的Storyboard只用於定義VisualState的最終可視狀態,而在VIsualState間轉換時用戶看到的是VisualTransition 中定義的Storyboard。但這樣的話兩處的Storyboard不就重覆了?帶著這個疑問很多年,微軟終於給出了另一種方案VisualState.Setters:
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
...
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="PointOverElement.(UIElement.Opacity)"
Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="PressElement.(UIElement.Opacity)"
Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled" />
</VisualStateGroup>
這樣VisualState的做法就十分清晰明瞭:
- 代碼使用VisualStateManager控制控制項當前的VisualState;
- VisualState.Setters定義這個VisualState最終在UI上如何呈現;
- VisualState間的過渡動畫由VisualTransition定義;
4. 怎麼使用VisualTransition
4.1 隱式轉換
不使用Storyboard的VisualTransition稱為隱式轉換:
<VisualStateGroup.Transitions >
<VisualTransition GeneratedDuration="0:0:3"/>
</VisualStateGroup.Transitions>
如上面這段XAML中的VisualTransition ,它指定VisualStateGroup中所有VisualState之間的過渡時間都是3秒,在這3秒中VisualState中的Double、Point和Color使用預設的線性插值方式進行動畫轉換。而其它值,如Visibility,則不可以使用隱式轉換。
這段XAML在Blend中對應“狀態”面板里VisualStateGroup的“預設過渡”。
隱式轉換可以進一步設置其它屬性,如以下XAML:
<VisualStateGroup.Transitions>
<VisualTransition To="PointerOver"
GeneratedDuration="0:0:3">
<VisualTransition.GeneratedEasingFunction>
<ExponentialEase EasingMode="EaseOut" />
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
<VisualTransition From="PointerOver"
To="Pressed"
GeneratedDuration="0:0:3">
<VisualTransition.GeneratedEasingFunction>
<ExponentialEase EasingMode="EaseOut" />
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
這段XAML中VisualTransition指定了以下三種屬性:
- From和To,轉換的舊狀態和新狀態,可以單獨指定。
- 動畫的緩動函數。
4.2 使用Storyboard
當隱式轉換不能滿足需求,可以使用Storyboard指定轉換的動畫。這時Storyboard不需要設置FillBehavior="HoldEnd"
,因為Storyboard結束後將保持VisualState設置的最終狀態。
<VisualTransition To="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
Storyboard.TargetName="PointOverElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="PointOverElement">
<EasingDoubleKeyFrame KeyTime="0"
Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2"
Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition To="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
Storyboard.TargetName="PressElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="PressElement">
<EasingDoubleKeyFrame KeyTime="0"
Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2"
Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
5. 為什麼有時候VisualTransition沒有生效
ControlTemplate在VisualState之間切換是靠下麵這個函數控制的:
//
// 摘要:
// 通過按名稱請求新的 Windows.UI.Xaml.VisualState 來在兩個狀態之間轉換控制項。
//
// 參數:
// control:
// 要進行狀態過渡的控制項。
//
// stateName:
// 要過渡到的狀態。
//
// useTransitions:
// 如果使用 Windows.UI.Xaml.VisualTransition 在各狀態之間轉換,則為 **true**。 如果跳過使用轉換並直接轉到請求的狀態,則為
// **false**。 預設值為 **false**。
//
// 返回結果:
// 如果控制項成功轉換到新狀態或者已經在使用該狀態,則為 **true**;否則為 **false**。
public static bool GoToState(Control control, string stateName, bool useTransitions);
如果useTransitions這個參數為false,則VisualState之間切換時不會使用VisualTransition。在控制項載入模板時(即調用OnApplyTemplate()函數時)通常會這樣做,因為控制項在呈現時通常都不需要做動畫。
另外,VisualStateManager.GoToState不會使控制項重覆進入某個狀態,即如果控制項已處於PointerOver的VisualState,再次調用VisualStateManager.GoToState(this, PointerOverState, useTransitions)不會觸發任何操作,也不會重覆觸發動畫。
6. 結語
除了VisualState.Setters,這篇文章的內容基本和WPF通用。
上次被批評寫得太複雜了,這次本來寫了很多,為了文章簡單易懂刪了一半,希望對理解VisualTransition有幫助。
7. 參考
VisualTransition Class (Windows)
VisualTransition Class (Windows.UI.Xaml) - UWP app developer Microsoft Docs