J'ai créé une ellipse dans l'application Silverlight pour Windows Phone 8.1 et UWP à la fois et je voulais la remplir d'ondes animées, à cet effet, je suis en train de suivre solution
mais c'est pour WPF, donc je ne peux pas utiliser de contrôle comme "Visual Brush".
Je voulais remplir une ellipse avec une onde similaire à celle-ci (ignorer 50% dans l'image) -
Et voici mon eliipse
<Ellipse Name="WaveEllipse" Grid.Column="1" Grid.Row="0" VerticalAlignment="Top"
Stroke="{StaticResource PhoneAccentBrush}"
StrokeThickness="4"
Width="225"
Height="225">
</Ellipse>
une alternative sur le pinceau visuel? Je voulais principalement l'implémenter dans Windows Phone 8.1 Silverlight, mais je passerai à UWP s'il n'est pas disponible sur la plateforme WP
Avant de vous donner le code, jetez un œil à ce - gif animé ci-dessous pour essayer de comprendre comment cette animation pourrait être créée.
C'est logique, non? Tout ce que nous devons faire est de créer une forme comme celle-ci, d'animer son décalage X(endlessly) et Y (niveau d'eau), et enfin de le couper avec une ellipse.
Vous devez donc d'abord utiliser Adobe Illustrator ou des outils similaires pour créer cette forme. Dans [~ # ~] ai [~ # ~], il y a un effet Zig Zag (voir capture d'écran ci-dessous) qui convient parfaitement à cela. Vous avez juste besoin de vous assurer que le point de départ est à la même position que celui de fin, donc lorsque vous répétez l'animation, vous aurez l'impression qu'il ne se termine jamais.
Ce qui manque actuellement dans UWP, c'est la possibilité de couper un UIElement
avec une forme non rectangulaire, donc ici nous devons l'exporter en tant que png (sinon nous l'exporterions sous la forme a svg et utilisez Path
pour l'afficher).
Toujours pour la même raison, la partie de découpage nécessite beaucoup de travail. Comme dans la réponse de Jet Chopper, c'est des tonnes de code pour obtenir juste un surfaceBrush
! Sans oublier que vous devrez également gérer manuellement la perte d'appareil et le cycle de vie de l'application.
Heureusement, dans Creators Update (c'est-à-dire 15063), il y a une nouvelle API appelée LoadedImageSurface
qui crée un CompositionSurfaceBrush
par une image uri avec quelques lignes de code . Dans mon exemple de code ci-dessous, vous verrez que j'utilise cela, ce qui signifie que si vous souhaitez prendre en charge les anciennes versions de Windows 10, vous devrez le remplacer par ce qui est dans la réponse de Jet.
L'idée est de créer un UserControl appelé WaveProgressControl
qui encapsule toute la logique d'animation et expose une propriété de dépendance appelée Percent
qui contrôle le niveau d'eau.
Le contrôle 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>
Le contrôle WaveProgressControl
- Code-behind
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);
}
}
Le 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>
J'ai tout mis dans ce exemple de projet et ci-dessous est une démo en direct. Prendre plaisir! :)
Voici l'exemple UWP. Vous pouvez l'ajuster comme vous le souhaitez:
<Canvas>
<Ellipse x:Name="Ellipse" Width="256" Height="256" Fill="DarkViolet" Stroke="DeepSkyBlue" StrokeThickness="8"/>
<Border x:Name="VisualBorder" Opacity="0.5"/>
</Canvas>
Et code derrière:
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);
}
}
Voici à quoi cela ressemble:
J'ai réalisé cela en utilisant une solution simple:
Wave2.png est une extension (copie collée l'image et ajoutée à la fin de la première image) pour la rendre plus longue.
La solution fonctionne sur 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>
Et voici le code d'animation:
<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>