Xamarin.Forms Бесконечная прокрутка изображения Фоновый эффект - PullRequest
0 голосов
/ 22 июня 2019

Я пытаюсь сделать то, что, по моему мнению, будет хорошим эффектом для приложения - серия изображений (думаю, обои) будет постоянно прокручиваться на заднем плане во время просмотра. Я начал создавать прототипы в Xamarin.Forms, создавая собственный элемент управления. Запланировано по диагональному переводу, но началось с самого простого подхода и все же довольно быстро столкнулось с некоторыми проблемами, а именно, что оно не совсем гладкое, так как оно становится немного прерывистым здесь и там (даже при использовании кэширования и всего лишь 10-килобайтного изображения) и 2 ) если пользователь выполняет более сложное действие, это может привести к задержке, и изображения будут отображаться ближе друг к другу, чем следовало бы. Есть ли способ исправить этот подход так, чтобы он был как можно более плавным и не мешал (или не мешал) другим элементам пользовательского интерфейса, или есть куда более превосходный подход для чего-то подобного - кто-нибудь когда-нибудь занимался этим? Пожалуйста, дайте мне знать, спасибо.

FlyingImageBackground.cs

public class FlyingImageBackground : ContentView
{
    public static readonly BindableProperty FlyingImageProperty =
      BindableProperty.Create(nameof(FlyingImage), typeof(ImageSource), typeof(FlyingImageBackground), default(ImageSource), BindingMode.TwoWay, propertyChanged: OnFlyingImageChanged);

    public ImageSource FlyingImage
    {
        get => (ImageSource)GetValue(FlyingImageProperty);
        set => SetValue(FlyingImageProperty, value);
    }

    private AbsoluteLayout canvas;

    public FlyingImageBackground()
    {
        this.canvas = new AbsoluteLayout()
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions = LayoutOptions.FillAndExpand
            };

        this.canvas.SizeChanged += Canvas_SizeChanged;

        Content = this.canvas;
    }

    ~FlyingImageBackground() => this.canvas.SizeChanged -= Canvas_SizeChanged;

    private static void OnFlyingImageChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var control = (FlyingImageBackground)bindable;
        control.BringToLife();
    }

    private void BringToLife()
    {
        if (this.canvas.Width <= 0 || this.canvas.Height <= 0)
            return;

        Device.StartTimer(TimeSpan.FromSeconds(1), () =>
        {
            Device.BeginInvokeOnMainThread(async () =>
            {
                await SendImageWave();
            });

            return this.canvas.IsVisible;
        });
    }

    private async Task SendImageWave()
    {
        var startingX = -100;
        var endingX = this.canvas.Width;

        if (endingX <= 0)
            return;

        endingX += 100;

        var yPositions = Enumerable.Range(0, (int)this.canvas.Height).Where(x => x % 90 == 0).ToList();

        var imgList = new List<CachedImage>();

        foreach (var yPos in yPositions)
        {
            var img = new CachedImage
            {
                Source = FlyingImage,
                HeightRequest = 50
            };
            imgList.Add(img);

            this.canvas.Children.Add(img, new Point(startingX, yPos));
        }

        await Task.WhenAll(
            imgList.Select(x => x.TranslateTo(endingX, 0, 10000)));
        //.Concat(imgList.Select(x => x.TranslateTo(startingX, 0, uint.MinValue))));

        imgList.ForEach(x =>
        {
            this.canvas.Children.Remove(x);
            x = null;
        });

        imgList = null;
    }

    private void Canvas_SizeChanged(object sender, EventArgs e)
    {
        BringToLife();
    }
}

Пример использования:

Просто поместите его в таблицу в ContentPage вместе с основным содержимым: e.g.:

<ContentPage.Content>
    <Grid>
        <controls:FlyingImageBackground FlyingImage="fireTruck.png" />

        <StackLayout HorizontalOptions="Center">
            <Button
                Text="I'm a button!" />
            <Label
                FontAttributes="Bold,Italic"
                Text="You're a good man, old sport!!!"
                TextDecorations="Underline" />
        </StackLayout>
    </Grid>

</ContentPage.Content>

1 Ответ

0 голосов
/ 23 июня 2019

Переключился на SkiaSharp и намного лучшие результаты. Анимация выглядит плавной, и если поток прерывается, изображения сохраняют подходящее расстояние. Также реализовано в первом наброске с использованием встроенных анимаций Xamarin, которые я проверил, проверяя, когда его запускать; .IsVisible prop останется верным, даже если страница больше не будет отображаться на экране, поэтому в этой новой версии необходимо было привязать свойство, которое сообщает мне, является ли страница активной или нет (в зависимости от того, когда на нее переходят и отошел от) и если нет то останови анимацию. На данный момент это всего лишь обработка эффекта горизонтальной прокрутки. Надеюсь, что кто-то еще найдет это полезным, и любые другие улучшения будут приветствоваться, пожалуйста, просто прокомментируйте / оставьте ответ!

[DesignTimeVisible(true)]
public class FlyingImageBackgroundSkia : ContentView
{
    public static readonly BindableProperty IsActiveProperty =
        BindableProperty.Create(
            nameof(IsActive),
            typeof(bool),
            typeof(FlyingImageBackground),
            default(bool),
            BindingMode.TwoWay,
            propertyChanged: OnPageActivenessChanged);

    private SKCanvasView canvasView;
    private SKBitmap resourceBitmap;
    private Stopwatch stopwatch = new Stopwatch();

    // consider making these bindable props
    private float percentComplete;
    private float imageSize = 40;
    private float columnSpacing = 100;
    private float rowSpacing = 100;
    private float framesPerSecond = 60;
    private float cycleTime = 1; // in seconds, for a single column

    public FlyingImageBackgroundSkia()
    {
        this.canvasView = new SKCanvasView();
        this.canvasView.PaintSurface += OnCanvasViewPaintSurface;
        this.Content = this.canvasView;

        string resourceID = "XamarinTestProject.Resources.Images.fireTruck.png";
        Assembly assembly = GetType().GetTypeInfo().Assembly;

        using (Stream stream = assembly.GetManifestResourceStream(resourceID))
        {
            this.resourceBitmap = SKBitmap.Decode(stream);
        }
    }

    ~FlyingImageBackgroundSkia() => this.resourceBitmap.Dispose();

    public bool IsActive
    {
        get => (bool)GetValue(IsActiveProperty);
        set => SetValue(IsActiveProperty, value);
    }

    private static async void OnPageActivenessChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var control = (FlyingImageBackgroundSkia)bindable;
        await control.AnimationLoop();
    }

    private async Task AnimationLoop()
    {
        this.stopwatch.Start();

        while (IsActive)
        {
            this.percentComplete = (float)(this.stopwatch.Elapsed.TotalSeconds % this.cycleTime) / this.cycleTime; // always between 0 and 1
            this.canvasView.InvalidateSurface(); // trigger redraw
            await Task.Delay(TimeSpan.FromSeconds(1.0 / this.framesPerSecond)); // non-blocking
        }

        this.stopwatch.Stop();
    }

    private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        var xPositions = Enumerable.Range(0, info.Width + (int)this.columnSpacing).Where(x => x % (int)this.columnSpacing == 0).ToList();
        xPositions.Insert(0, -(int)this.columnSpacing);

        var yPositions = Enumerable.Range(0, info.Height + (int)this.rowSpacing).Where(x => x % (int)this.rowSpacing == 0).ToList();
        yPositions.Insert(0, -(int)this.rowSpacing);

        if (this.resourceBitmap != null)
        {
            foreach (var xPos in xPositions)
            {
                var xPosNow = xPos + (this.rowSpacing * this.percentComplete);
                foreach (var yPos in yPositions)
                {
                    canvas.DrawBitmap(
                        this.resourceBitmap,
                        new SKRect(xPosNow, yPos, xPosNow + this.imageSize, yPos + this.imageSize));
                }
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...