web-dev-qa-db-fra.com

Remplissez Ellipse avec une animation de vague

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

enter image description here

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

24
Shubham Sahu

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.

enter image description here

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.

enter image description here

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.

Code

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! :)

enter image description here

80
Justin XL

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:

enter image description here

12
Jet Chopper

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

enter image description here

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