c# xaml uwp windows-phone-8.1 windows-phone-silverlight

c# - Rellene Ellipse con animación de onda



xaml uwp (3)

He creado una elipse en Windows Phone 8.1 Silverlight App y UWP, y quería llenarla con ondas de animación. Para este propósito, estoy siguiendo esta solution

pero es para WPF, así que no puedo usar ningún control como "Visual Brush".

Quería llenar la elipse con onda similar a esto (ignorar el 50% en la imagen) -

Y aquí está mi elipse

<Ellipse Name="WaveEllipse" Grid.Column="1" Grid.Row="0" VerticalAlignment="Top" Stroke="{StaticResource PhoneAccentBrush}" StrokeThickness="4" Width="225" Height="225"> </Ellipse>

cualquier alternativa en el pincel visual? principalmente quería implementarlo en Windows Phone 8.1 Silverlight, pero cambiaré a UWP si no está disponible en la plataforma WP


Antes de darle el código, eche un vistazo a este gif animado a continuación para tratar de entender cómo se pudo crear esta animación.

Tiene sentido, ¿verdad? Todo lo que tenemos que hacer es crear una forma como esta, animar su desplazamiento X (interminable) e Y (nivel de agua), y finalmente solo cortarlo con una elipse.

Entonces, primero deberá usar Adobe Illustrator o herramientas similares para crear esta forma. En AI , hay un efecto Zig Zag (ver la captura de pantalla a continuación) que es perfecto para esto. Solo necesita asegurarse de que el punto de partida esté en la misma posición que el punto final, de modo que cuando repita la animación, se sentirá como si nunca terminara.

Lo que falta actualmente en UWP es la capacidad de recortar un UIElement con una forma no rectangular, así que aquí tenemos que exportar esto como un png (de lo contrario, lo exportaríamos como svg y usaríamos Path para mostrarlo).

También por la misma razón, la parte de recorte requiere mucho trabajo. Al igual que en la respuesta de Jet Chopper, ¡eso es un montón de código para obtener una surfaceBrush ! Sin mencionar que también necesitarás manejar manualmente la pérdida de dispositivo y el ciclo de vida de la aplicación.

Afortunadamente, en Creators Update (es decir, 15063 ), hay una nueva API llamada LoadedImageSurface que crea CompositionSurfaceBrush por una imagen uri con un par de líneas de código. En el siguiente ejemplo de código, verá que lo uso, lo que significa que, si desea soportar versiones anteriores de Windows 10, deberá reemplazarlo con la respuesta de Jet.

Código

La idea es crear un UserControl llamado WaveProgressControl que encapsula toda la lógica de la animación y expone una propiedad de dependencia llamada Percent que controla el nivel del agua .

El control 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>

El control WaveProgressControl - Código detrás

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); } }

La 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>

He puesto todo en este proyecto de muestra y debajo hay una demostración en vivo. ¡Disfrutar! :)


Aquí está la muestra de UWP. Puede ajustarlo como lo desee:

<Canvas> <Ellipse x:Name="Ellipse" Width="256" Height="256" Fill="DarkViolet" Stroke="DeepSkyBlue" StrokeThickness="8"/> <Border x:Name="VisualBorder" Opacity="0.5"/> </Canvas>

Y código detrás:

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); } }

Así es como se ve:


Lo he logrado usando una solución simple:

Wave2.png es una copia extendida (copie la imagen y la agrega al final de la primera imagen) para que sea más larga.

La solución funciona en 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>

Y aquí está el código de animación:

<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>