Заполнить эллипс волновой анимацией

Я создал эллипс в приложениях Windows Phone 8.1 Silverlight и UWP и хотел заполнить его волнами анимации. Для этого я следую этому решению

но это для WPF, поэтому я не могу использовать некоторые элементы управления, такие как "Visual Brush".

Я хотел заполнить эллипс волной, подобной этой (игнорировать 50% на изображении) -

А вот и моя эллипса

<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, но я переключусь на UWP, если он не доступен на платформе WP

3 ответа

Решение

Прежде чем дать вам код, посмотрите на этот анимированный GIF ниже, чтобы попытаться понять, как эта анимация может быть создана.

введите описание изображения здесь

Имеет смысл, верно? Все, что нам нужно сделать, это создать такую ​​форму, анимировать ее смещение X(бесконечно) и Y(уровень воды) и, наконец, просто обрезать ее с помощью эллипса.

Поэтому сначала вам нужно будет использовать Adobe Illustrator или аналогичные инструменты для создания этой фигуры. В AI есть эффект Zig Zag (см. Скриншот ниже), который идеально подходит для этого. Вам просто нужно убедиться, что начальная точка находится в той же позиции, что и конечная, поэтому при повторении анимации вы почувствуете, что она никогда не заканчивается.

введите описание изображения здесь

В настоящее время в UWP отсутствует возможность обрезать UIElement с непрямоугольной формой, поэтому здесь мы должны экспортировать это как png (в противном случае мы экспортируем его как svg и используем Path чтобы отобразить это).

Также по той же причине, часть отсечения требует большой работы. Как и в ответе Jet Chopper, это тонны кода, чтобы просто получить surfaceBrush! Не говоря уже о том, что вам также нужно будет вручную обрабатывать потерянные устройства и жизненный цикл приложения.

К счастью, в Creators Update(например, 15063) есть новый API под названием LoadedImageSurface это создает CompositionSurfaceBrush по изображению URI с кодом пары строк. В приведенном ниже примере кода вы увидите, что я использую это, что означает, что если вы хотите поддерживать более старые версии Windows 10, вам нужно будет заменить его тем, что есть в ответе Jet.

Код

Идея состоит в том, чтобы создать UserControl под названием WaveProgressControl который инкапсулирует всю логику анимации и предоставляет свойство зависимости, называемое 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>

Я вложил все в этот пример проекта, и ниже приведена живая демонстрация. Наслаждайтесь!:)

введите описание изображения здесь

Вот пример 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);
    }
}

Вот как это выглядит:

введите описание изображения здесь

Я добился этого с помощью простого решения:

Wave2.png является расширенным (скопируйте, вставьте изображение и добавьте в конец первого изображения), чтобы сделать его длиннее.

Решение работает на WP8/ Магазин приложений /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>
Другие вопросы по тегам