用波浪动画填充椭圆
Fill Ellipse with wave animation
我在 Windows Phone 8.1 Silverlight App 和 UWP 中创建了一个椭圆
我想用动画波浪填充它,
为此,我正在关注这个 solution
但是它是针对 WPF 的,所以我无法使用像 "Visual Brush" 这样的控件。
我想用类似于此的波浪填充椭圆(忽略图像中的 50%)-
这是我的 eliipse
<Ellipse Name="WaveEllipse" Grid.Column="1" Grid.Row="0" VerticalAlignment="Top"
Stroke="{StaticResource PhoneAccentBrush}"
StrokeThickness="4"
Width="225"
Height="225">
</Ellipse>
视觉画笔上有替代品吗?
主要是我想在Windows Phone 8.1 Silverlight中实现,但是如果WP平台上不可用我会切换到UWP
这是 UWP 示例。您可以随意调整:
<Canvas>
<Ellipse x:Name="Ellipse" Width="256" Height="256" Fill="DarkViolet" Stroke="DeepSkyBlue" StrokeThickness="8"/>
<Border x:Name="VisualBorder" Opacity="0.5"/>
</Canvas>
后面的代码:
private async void CreateVisuals()
{
var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
var bitmap = await CanvasBitmap.LoadAsync(CanvasDevice.GetSharedDevice(),
new Uri("ms-appx:///Assets/Wave-PNG-Transparent-Picture.png"));
var drawingSurface =
CanvasComposition.CreateCompositionGraphicsDevice(compositor, CanvasDevice.GetSharedDevice())
.CreateDrawingSurface(bitmap.Size,
DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(drawingSurface))
{
ds.Clear(Colors.Transparent);
ds.DrawImage(bitmap);
}
var surfaceBrush = compositor.CreateSurfaceBrush(drawingSurface);
surfaceBrush.Stretch = CompositionStretch.None;
var maskedBrush = compositor.CreateMaskBrush();
maskedBrush.Mask = Ellipse.GetAlphaMask();
maskedBrush.Source = surfaceBrush;
var sprite = compositor.CreateSpriteVisual();
sprite.Size = new Vector2((float)Ellipse.Width, (float)Ellipse.Height);
sprite.Brush = maskedBrush;
sprite.CenterPoint = new Vector3(sprite.Size / 2, 0);
sprite.Scale = new Vector3(0.9f);
ElementCompositionPreview.SetElementChildVisual(VisualBorder, sprite);
var offsetAnimation = compositor.CreateScalarKeyFrameAnimation();
offsetAnimation.InsertKeyFrame(0, 0);
offsetAnimation.InsertKeyFrame(1, 256, compositor.CreateLinearEasingFunction());
offsetAnimation.Duration = TimeSpan.FromMilliseconds(1000);
offsetAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
surfaceBrush.StartAnimation("Offset.X", offsetAnimation);
}
}
这是它的样子:
在给你代码之前,先看看下面这个动画 gif,试着理解这个动画是如何创建的。
有道理,对吧?我们需要做的就是创建一个这样的形状,为其偏移 X(无限)和 Y(水位)设置动画,最后用椭圆剪裁它。
因此,您首先需要使用 Adobe Illustrator 或类似工具来创建此形状。在 AI 中,有一个 Zig Zag 效果(见下面的屏幕截图)非常适合这个。你只需要确保起点和终点在同一个位置,这样当你重复动画时,就会有一种永无止境的感觉。
UWP 目前缺少的是剪辑非矩形 UIElement
的功能,因此我们必须将其导出为 png(否则我们会将其导出为 svg 并使用 Path
显示它)。
同样的道理,裁剪部分也需要大量的工作。就像 Jet Chopper 的回答一样,为了获得 surfaceBrush
需要大量代码!更不用说您还需要手动处理设备丢失和应用程序生命周期。
值得庆幸的是,在创意者更新中(即 15063),有一个新的 API 称为 LoadedImageSurface
,它通过图像创建 CompositionSurfaceBrush
uri 和几行代码。在我下面的代码示例中,你会看到我使用了这个,这意味着,如果你想支持旧版本的 Windows 10,你需要将它替换为 Jet 的答案中的内容。
代码
想法是创建一个名为 WaveProgressControl
的 UserControl,它封装所有动画逻辑并公开一个名为 Percent
的依赖项 属性控制水位。
WaveProgressControl
控件-XAML
<UserControl x:Class="WaveProgressControlRepo.WaveProgressControl"
Height="160"
Width="160">
<Grid x:Name="Root">
<Ellipse x:Name="ClippedImageContainer"
Fill="White"
Margin="6" />
<Ellipse x:Name="CircleBorder"
Stroke="#FF0289CD"
StrokeThickness="3" />
<TextBlock Foreground="#FF0289CD"
FontSize="36"
FontWeight="SemiBold"
TextAlignment="Right"
VerticalAlignment="Center"
Width="83"
Margin="0,0,12,0">
<Run Text="{x:Bind Percent, Mode=OneWay}" />
<Run Text="%"
FontSize="22" />
</TextBlock>
</Grid>
</UserControl>
WaveProgressControl
控件 - 代码隐藏
private readonly Compositor _compositor;
private readonly CompositionPropertySet _percentPropertySet;
public WaveProgressControl()
{
InitializeComponent();
_compositor = Window.Current.Compositor;
_percentPropertySet = _compositor.CreatePropertySet();
_percentPropertySet.InsertScalar("Value", 0.0f);
Loaded += OnLoaded;
}
public double Percent
{
get => (double)GetValue(PercentProperty);
set => SetValue(PercentProperty, value);
}
public static readonly DependencyProperty PercentProperty =
DependencyProperty.Register("Percent", typeof(double), typeof(WaveProgressControl),
new PropertyMetadata(0.0d, (s, e) =>
{
var self = (WaveProgressControl)s;
var propertySet = self._percentPropertySet;
propertySet.InsertScalar("Value", Convert.ToSingle(e.NewValue) / 100);
}));
private void OnLoaded(object sender, RoutedEventArgs e)
{
CompositionSurfaceBrush imageSurfaceBrush;
SetupClippedWaveImage();
SetupEndlessWaveAnimationOnXAxis();
SetupExpressionAnimationOnYAxisBasedOnPercentValue();
void SetupClippedWaveImage()
{
// Note LoadedImageSurface is only available in 15063 onward.
var imageSurface = LoadedImageSurface.StartLoadFromUri(new Uri(BaseUri, "/Assets/wave.png"));
imageSurfaceBrush = _compositor.CreateSurfaceBrush(imageSurface);
imageSurfaceBrush.Stretch = CompositionStretch.None;
imageSurfaceBrush.Offset = new Vector2(120, 248);
var maskBrush = _compositor.CreateMaskBrush();
var maskSurfaceBrush = ClippedImageContainer.GetAlphaMask(); // CompositionSurfaceBrush
maskBrush.Mask = maskSurfaceBrush;
maskBrush.Source = imageSurfaceBrush;
var imageVisual = _compositor.CreateSpriteVisual();
imageVisual.RelativeSizeAdjustment = Vector2.One;
ElementCompositionPreview.SetElementChildVisual(ClippedImageContainer, imageVisual);
imageVisual.Brush = maskBrush;
}
void SetupEndlessWaveAnimationOnXAxis()
{
var waveOffsetXAnimation = _compositor.CreateScalarKeyFrameAnimation();
waveOffsetXAnimation.InsertKeyFrame(1.0f, -80.0f, _compositor.CreateLinearEasingFunction());
waveOffsetXAnimation.Duration = TimeSpan.FromSeconds(1);
waveOffsetXAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
imageSurfaceBrush.StartAnimation("Offset.X", waveOffsetXAnimation);
}
void SetupExpressionAnimationOnYAxisBasedOnPercentValue()
{
var waveOffsetYExpressionAnimation = _compositor.CreateExpressionAnimation("Lerp(248.0f, 120.0f, Percent.Value)");
waveOffsetYExpressionAnimation.SetReferenceParameter("Percent", _percentPropertySet);
imageSurfaceBrush.StartAnimation("Offset.Y", waveOffsetYExpressionAnimation);
}
}
MainPage
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<local:WaveProgressControl x:Name="WaveProgressControl" />
<Slider Grid.Row="1"
Margin="24"
Value="{x:Bind WaveProgressControl.Percent, Mode=TwoWay}" />
</Grid>
我已将所有内容放入此 sample project 下面是现场演示。享受! :)
我使用一个简单的解决方案实现了这一点:
Wave2.png 是一个扩展(复制粘贴图像并添加到第一张图像的末尾)以使其更长。
该解决方案适用于 WP8/Store apps/UWP/Silverlight
<Border
Background="White"
VerticalAlignment="Center"
HorizontalAlignment="Center"
CornerRadius="10000"
BorderBrush="Black"
BorderThickness="5">
<Grid>
<Ellipse
x:Name="ellipse"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Height="200"
Width="200">
<Ellipse.Fill>
<ImageBrush
x:Name="WaveImage"
Stretch="None"
ImageSource="wave2.png">
<ImageBrush.Transform>
<CompositeTransform
TranslateY="200"
TranslateX="299" />
</ImageBrush.Transform>
</ImageBrush>
</Ellipse.Fill>
</Ellipse>
<TextBlock
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="HUJ" />
</Grid>
</Border>
这是动画代码:
<Storyboard
x:Name="AnimateWave">
<DoubleAnimationUsingKeyFrames
RepeatBehavior="Forever"
EnableDependentAnimation="True"
Storyboard.TargetProperty="(Shape.Fill).(Brush.Transform).(CompositeTransform.TranslateX)"
Storyboard.TargetName="ellipse">
<EasingDoubleKeyFrame
KeyTime="0:0:5"
Value="-299" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
我在 Windows Phone 8.1 Silverlight App 和 UWP 中创建了一个椭圆 我想用动画波浪填充它, 为此,我正在关注这个 solution
但是它是针对 WPF 的,所以我无法使用像 "Visual Brush" 这样的控件。
我想用类似于此的波浪填充椭圆(忽略图像中的 50%)-
这是我的 eliipse
<Ellipse Name="WaveEllipse" Grid.Column="1" Grid.Row="0" VerticalAlignment="Top"
Stroke="{StaticResource PhoneAccentBrush}"
StrokeThickness="4"
Width="225"
Height="225">
</Ellipse>
视觉画笔上有替代品吗? 主要是我想在Windows Phone 8.1 Silverlight中实现,但是如果WP平台上不可用我会切换到UWP
这是 UWP 示例。您可以随意调整:
<Canvas>
<Ellipse x:Name="Ellipse" Width="256" Height="256" Fill="DarkViolet" Stroke="DeepSkyBlue" StrokeThickness="8"/>
<Border x:Name="VisualBorder" Opacity="0.5"/>
</Canvas>
后面的代码:
private async void CreateVisuals()
{
var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
var bitmap = await CanvasBitmap.LoadAsync(CanvasDevice.GetSharedDevice(),
new Uri("ms-appx:///Assets/Wave-PNG-Transparent-Picture.png"));
var drawingSurface =
CanvasComposition.CreateCompositionGraphicsDevice(compositor, CanvasDevice.GetSharedDevice())
.CreateDrawingSurface(bitmap.Size,
DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(drawingSurface))
{
ds.Clear(Colors.Transparent);
ds.DrawImage(bitmap);
}
var surfaceBrush = compositor.CreateSurfaceBrush(drawingSurface);
surfaceBrush.Stretch = CompositionStretch.None;
var maskedBrush = compositor.CreateMaskBrush();
maskedBrush.Mask = Ellipse.GetAlphaMask();
maskedBrush.Source = surfaceBrush;
var sprite = compositor.CreateSpriteVisual();
sprite.Size = new Vector2((float)Ellipse.Width, (float)Ellipse.Height);
sprite.Brush = maskedBrush;
sprite.CenterPoint = new Vector3(sprite.Size / 2, 0);
sprite.Scale = new Vector3(0.9f);
ElementCompositionPreview.SetElementChildVisual(VisualBorder, sprite);
var offsetAnimation = compositor.CreateScalarKeyFrameAnimation();
offsetAnimation.InsertKeyFrame(0, 0);
offsetAnimation.InsertKeyFrame(1, 256, compositor.CreateLinearEasingFunction());
offsetAnimation.Duration = TimeSpan.FromMilliseconds(1000);
offsetAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
surfaceBrush.StartAnimation("Offset.X", offsetAnimation);
}
}
这是它的样子:
在给你代码之前,先看看下面这个动画 gif,试着理解这个动画是如何创建的。
有道理,对吧?我们需要做的就是创建一个这样的形状,为其偏移 X(无限)和 Y(水位)设置动画,最后用椭圆剪裁它。
因此,您首先需要使用 Adobe Illustrator 或类似工具来创建此形状。在 AI 中,有一个 Zig Zag 效果(见下面的屏幕截图)非常适合这个。你只需要确保起点和终点在同一个位置,这样当你重复动画时,就会有一种永无止境的感觉。
UWP 目前缺少的是剪辑非矩形 UIElement
的功能,因此我们必须将其导出为 png(否则我们会将其导出为 svg 并使用 Path
显示它)。
同样的道理,裁剪部分也需要大量的工作。就像 Jet Chopper 的回答一样,为了获得 surfaceBrush
需要大量代码!更不用说您还需要手动处理设备丢失和应用程序生命周期。
值得庆幸的是,在创意者更新中(即 15063),有一个新的 API 称为 LoadedImageSurface
,它通过图像创建 CompositionSurfaceBrush
uri 和几行代码。在我下面的代码示例中,你会看到我使用了这个,这意味着,如果你想支持旧版本的 Windows 10,你需要将它替换为 Jet 的答案中的内容。
代码
想法是创建一个名为 WaveProgressControl
的 UserControl,它封装所有动画逻辑并公开一个名为 Percent
的依赖项 属性控制水位。
WaveProgressControl
控件-XAML
<UserControl x:Class="WaveProgressControlRepo.WaveProgressControl"
Height="160"
Width="160">
<Grid x:Name="Root">
<Ellipse x:Name="ClippedImageContainer"
Fill="White"
Margin="6" />
<Ellipse x:Name="CircleBorder"
Stroke="#FF0289CD"
StrokeThickness="3" />
<TextBlock Foreground="#FF0289CD"
FontSize="36"
FontWeight="SemiBold"
TextAlignment="Right"
VerticalAlignment="Center"
Width="83"
Margin="0,0,12,0">
<Run Text="{x:Bind Percent, Mode=OneWay}" />
<Run Text="%"
FontSize="22" />
</TextBlock>
</Grid>
</UserControl>
WaveProgressControl
控件 - 代码隐藏
private readonly Compositor _compositor;
private readonly CompositionPropertySet _percentPropertySet;
public WaveProgressControl()
{
InitializeComponent();
_compositor = Window.Current.Compositor;
_percentPropertySet = _compositor.CreatePropertySet();
_percentPropertySet.InsertScalar("Value", 0.0f);
Loaded += OnLoaded;
}
public double Percent
{
get => (double)GetValue(PercentProperty);
set => SetValue(PercentProperty, value);
}
public static readonly DependencyProperty PercentProperty =
DependencyProperty.Register("Percent", typeof(double), typeof(WaveProgressControl),
new PropertyMetadata(0.0d, (s, e) =>
{
var self = (WaveProgressControl)s;
var propertySet = self._percentPropertySet;
propertySet.InsertScalar("Value", Convert.ToSingle(e.NewValue) / 100);
}));
private void OnLoaded(object sender, RoutedEventArgs e)
{
CompositionSurfaceBrush imageSurfaceBrush;
SetupClippedWaveImage();
SetupEndlessWaveAnimationOnXAxis();
SetupExpressionAnimationOnYAxisBasedOnPercentValue();
void SetupClippedWaveImage()
{
// Note LoadedImageSurface is only available in 15063 onward.
var imageSurface = LoadedImageSurface.StartLoadFromUri(new Uri(BaseUri, "/Assets/wave.png"));
imageSurfaceBrush = _compositor.CreateSurfaceBrush(imageSurface);
imageSurfaceBrush.Stretch = CompositionStretch.None;
imageSurfaceBrush.Offset = new Vector2(120, 248);
var maskBrush = _compositor.CreateMaskBrush();
var maskSurfaceBrush = ClippedImageContainer.GetAlphaMask(); // CompositionSurfaceBrush
maskBrush.Mask = maskSurfaceBrush;
maskBrush.Source = imageSurfaceBrush;
var imageVisual = _compositor.CreateSpriteVisual();
imageVisual.RelativeSizeAdjustment = Vector2.One;
ElementCompositionPreview.SetElementChildVisual(ClippedImageContainer, imageVisual);
imageVisual.Brush = maskBrush;
}
void SetupEndlessWaveAnimationOnXAxis()
{
var waveOffsetXAnimation = _compositor.CreateScalarKeyFrameAnimation();
waveOffsetXAnimation.InsertKeyFrame(1.0f, -80.0f, _compositor.CreateLinearEasingFunction());
waveOffsetXAnimation.Duration = TimeSpan.FromSeconds(1);
waveOffsetXAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
imageSurfaceBrush.StartAnimation("Offset.X", waveOffsetXAnimation);
}
void SetupExpressionAnimationOnYAxisBasedOnPercentValue()
{
var waveOffsetYExpressionAnimation = _compositor.CreateExpressionAnimation("Lerp(248.0f, 120.0f, Percent.Value)");
waveOffsetYExpressionAnimation.SetReferenceParameter("Percent", _percentPropertySet);
imageSurfaceBrush.StartAnimation("Offset.Y", waveOffsetYExpressionAnimation);
}
}
MainPage
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<local:WaveProgressControl x:Name="WaveProgressControl" />
<Slider Grid.Row="1"
Margin="24"
Value="{x:Bind WaveProgressControl.Percent, Mode=TwoWay}" />
</Grid>
我已将所有内容放入此 sample project 下面是现场演示。享受! :)
我使用一个简单的解决方案实现了这一点:
Wave2.png 是一个扩展(复制粘贴图像并添加到第一张图像的末尾)以使其更长。
该解决方案适用于 WP8/Store apps/UWP/Silverlight
<Border
Background="White"
VerticalAlignment="Center"
HorizontalAlignment="Center"
CornerRadius="10000"
BorderBrush="Black"
BorderThickness="5">
<Grid>
<Ellipse
x:Name="ellipse"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Height="200"
Width="200">
<Ellipse.Fill>
<ImageBrush
x:Name="WaveImage"
Stretch="None"
ImageSource="wave2.png">
<ImageBrush.Transform>
<CompositeTransform
TranslateY="200"
TranslateX="299" />
</ImageBrush.Transform>
</ImageBrush>
</Ellipse.Fill>
</Ellipse>
<TextBlock
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="HUJ" />
</Grid>
</Border>
这是动画代码:
<Storyboard
x:Name="AnimateWave">
<DoubleAnimationUsingKeyFrames
RepeatBehavior="Forever"
EnableDependentAnimation="True"
Storyboard.TargetProperty="(Shape.Fill).(Brush.Transform).(CompositeTransform.TranslateX)"
Storyboard.TargetName="ellipse">
<EasingDoubleKeyFrame
KeyTime="0:0:5"
Value="-299" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>