基于WPF實(shí)現(xiàn)跳動(dòng)的字符效果
本文將介紹一個(gè)好玩但實(shí)際作用可能不太大的動(dòng)畫效果:跳動(dòng)的字符。為了提高動(dòng)畫效果的可重用性以及調(diào)用的靈活性,通過Behavior實(shí)現(xiàn)跳動(dòng)的字符動(dòng)畫。先看下效果:

技術(shù)要點(diǎn)與實(shí)現(xiàn)
通過TextEffect的PositionStart和PositionCount屬性控制應(yīng)用動(dòng)畫效果的子字符串的起始位置以及長(zhǎng)度,同時(shí)使用TranslateTransform設(shè)置字符縱坐標(biāo)的移動(dòng)變換,以實(shí)現(xiàn)跳動(dòng)的效果。主要步驟如下:
- 在OnAttached方法中,注冊(cè)
Loaded事件,在Load事件中為TextBlock添加TextEffect效果,其中PositionCount設(shè)置為1,每次只跳動(dòng)一個(gè)字符。 - 添加啟動(dòng)動(dòng)畫效果的
BeginEffect方法,并創(chuàng)建控制子字符縱向移動(dòng)變換的線性動(dòng)畫。然后根據(jù)字符串(剔除空字符)的長(zhǎng)度n,創(chuàng)建n個(gè)關(guān)鍵幀,每個(gè)關(guān)鍵幀中把PositionStart設(shè)置為要跳動(dòng)的字符在字符串中的索引 - 在開啟動(dòng)畫屬性
IsEnabled=true和TextBlock內(nèi)容變化時(shí),啟動(dòng)動(dòng)畫效果
在創(chuàng)建關(guān)鍵幀設(shè)置跳動(dòng)字符位置時(shí)剔除了空字符,是為了是動(dòng)畫效果顯得連貫
public class DanceCharEffectBehavior : Behavior<TextBlock>
{
private TextEffect _textEffect;
private string _textEffectName;
private TranslateTransform _translateTransform = null;
private string _translateTransformName;
private Storyboard _storyboard;
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
this.AssociatedObject.Unloaded += AssociatedObject_Unloaded;
this.AssociatedObject.IsVisibleChanged += AssociatedObject_IsVisibleChanged;
BindingOperations.SetBinding(this, DanceCharEffectBehavior.InternalTextProperty, new Binding("Text") { Source = this.AssociatedObject });
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
this.AssociatedObject.Unloaded -= AssociatedObject_Unloaded;
this.AssociatedObject.IsVisibleChanged -= AssociatedObject_IsVisibleChanged;
this.ClearValue(DanceCharEffectBehavior.InternalTextProperty);
if (_storyboard != null)
{
_storyboard.Remove(this.AssociatedObject);
_storyboard.Children.Clear();
}
if (_textEffect != null)
this.AssociatedObject.TextEffects.Remove(_textEffect);
}
private void AssociatedObject_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == false)
{
if (_storyboard != null)
_storyboard.Stop(this.AssociatedObject);
}
else
{
BeginEffect(this.AssociatedObject.Text);
}
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
if (_textEffect == null)
{
this.AssociatedObject.TextEffects.Add(_textEffect = new TextEffect()
{
PositionCount = 1,
Transform = _translateTransform = new TranslateTransform(),
});
NameScope.SetNameScope(this.AssociatedObject, new NameScope());
this.AssociatedObject.RegisterName(_textEffectName = "n" + Guid.NewGuid().ToString("N"), _textEffect);
this.AssociatedObject.RegisterName(_translateTransformName = "n" + Guid.NewGuid().ToString("N"), _translateTransform);
if (IsEnabled)
BeginEffect(this.AssociatedObject.Text);
}
}
private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
{
StopEffect();
}
private void SetEffect(string text)
{
if (string.IsNullOrEmpty(text) || this.AssociatedObject.IsLoaded == false)
{
StopEffect();
return;
}
BeginEffect(text);
}
private void StopEffect()
{
if (_storyboard != null)
{
_storyboard.Stop(this.AssociatedObject);
}
}
private void BeginEffect(string text)
{
StopEffect();
int textLength = text.Length;
if (textLength < 1 || _translateTransformName == null || IsEnabled == false) return;
if (_storyboard == null)
_storyboard = new Storyboard();
double duration = 0.5d;
DoubleAnimation da = new DoubleAnimation();
Storyboard.SetTargetName(da, _translateTransformName);
Storyboard.SetTargetProperty(da, new PropertyPath(TranslateTransform.YProperty));
da.From = 0d;
da.To = 10d;
da.Duration = TimeSpan.FromSeconds(duration / 2d);
da.RepeatBehavior = RepeatBehavior.Forever;
da.AutoReverse = true;
char emptyChar = ' ';
List<int> lsb = new List<int>();
for (int i = 0; i < textLength; ++i)
{
if (text[i] != emptyChar)
{
lsb.Add(i);
}
}
Int32AnimationUsingKeyFrames frames = new Int32AnimationUsingKeyFrames();
Storyboard.SetTargetName(frames, _textEffectName);
Storyboard.SetTargetProperty(frames, new PropertyPath(TextEffect.PositionStartProperty));
frames.Duration = TimeSpan.FromSeconds((lsb.Count) * duration);
frames.RepeatBehavior = RepeatBehavior.Forever;
frames.AutoReverse = true;
int ii = 0;
foreach (int index in lsb)
{
frames.KeyFrames.Add(new DiscreteInt32KeyFrame()
{
Value = index,
KeyTime = TimeSpan.FromSeconds(ii * duration),
});
++ii;
}
_storyboard.Children.Add(da);
_storyboard.Children.Add(frames);
_storyboard.Begin(this.AssociatedObject, true);
}
private string InternalText
{
get { return (string)GetValue(InternalTextProperty); }
set { SetValue(InternalTextProperty, value); }
}
private static readonly DependencyProperty InternalTextProperty =
DependencyProperty.Register("InternalText", typeof(string), typeof(DanceCharEffectBehavior),
new PropertyMetadata(OnInternalTextChanged));
private static void OnInternalTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = d as DanceCharEffectBehavior;
if (source._storyboard != null)
{
source._storyboard.Stop(source.AssociatedObject);
source._storyboard.Children.Clear();
}
source.SetEffect(e.NewValue == null ? string.Empty : e.NewValue.ToString());
}
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.Register("IsEnabled", typeof(bool), typeof(DanceCharEffectBehavior), new PropertyMetadata(true, (d, e) =>
{
bool b = (bool)e.NewValue;
var source = d as DanceCharEffectBehavior;
source.SetEffect(source.InternalText);
}));
}調(diào)用的時(shí)候只需要在TextBlock添加Behavior即可,代碼如下
<TextBlock FontSize="20" Text="Hello">
<i:Interaction.Behaviors>
<local:DanceCharEffectBehavior x:Name="titleEffect" IsEnabled="True" />
</i:Interaction.Behaviors>
</TextBlock>結(jié)尾
本例中還有許多可以完善的地方,比如字符跳動(dòng)的幅度可以根據(jù)實(shí)際的FontSize來設(shè)置,或者增加依賴屬性來控制;動(dòng)畫是否倒退播放,是否循環(huán)播放,以及動(dòng)畫的速度都可以通過增加依賴屬性在調(diào)用時(shí)靈活設(shè)置。
到此這篇關(guān)于基于WPF實(shí)現(xiàn)跳動(dòng)的字符效果的文章就介紹到這了,更多相關(guān)WPF字符跳動(dòng)效果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)windows form限制文本框輸入的方法
這篇文章主要介紹了C#實(shí)現(xiàn)windows form限制文本框輸入的方法,涉及C#限制文本框輸入的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
在C#中實(shí)現(xiàn)文本與Word文檔互相轉(zhuǎn)換的操作步驟
在現(xiàn)代軟件開發(fā)中,處理文檔內(nèi)容是一個(gè)非常常見的需求,無論是生成報(bào)告、存儲(chǔ)日志,還是處理用戶輸入,開發(fā)者都可能需要在純文本與 Word 文檔之間進(jìn)行轉(zhuǎn)換,所以本文給大家介紹了如何在 C# 中將文本轉(zhuǎn)換為 Word 以及將 Word 轉(zhuǎn)換為文本,需要的朋友可以參考下2025-09-09
WPF中不規(guī)則窗體與WindowsFormsHost控件兼容問題的解決方法
這篇文章主要介紹了WPF中不規(guī)則窗體與WindowsFormsHost控件兼容問題的解決方法,對(duì)比以往的解決方案,給出了一個(gè)具有普遍性的技巧,具有一定的借鑒價(jià)值,需要的朋友可以參考下2014-11-11
C#使用SqlServer作為日志數(shù)據(jù)庫(kù)的設(shè)計(jì)與實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于C#使用SqlServer作為日志數(shù)據(jù)庫(kù)的設(shè)計(jì)與實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
C#.NET ObjectPool 對(duì)象復(fù)用、池化策略與使用邊界
本文主要介紹了C#.NET ObjectPool 對(duì)象復(fù)用、池化策略與使用邊界,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-06-06
使用GetInvalidFileNameCharts生成文件名
這篇文章主要介紹了一個(gè)很實(shí)用的函數(shù)Path.GetInvalidFileNameCharts(),他可以很方便的生成一個(gè)有效的文件名稱2014-01-01

