【问题标题】:Xamarin.Forms Infinite Scrolling Image Background EffectXamarin.Forms 无限滚动图像背景效果
【发布时间】:2019-06-23 16:11:59
【问题描述】:

我正在尝试制作我认为对应用程序来说会很好的效果 - 一系列图像(想想壁纸)将在视图期间在背景中不断滚动。我开始在 Xamarin.Forms 中对此进行原型设计,创建自定义控件。计划进行对角线平移,但从最基本的方法开始,但很快就遇到了一些问题,即它并不完全平滑,因为它在这里和那里有点不稳定(即使使用缓存和只有 10kb 的图像)和 2 ) 如果用户执行的操作涉及更多,则可能会导致延迟,并且图像会比应有的更紧密地呈现在一起。有没有办法修复这种方法,使其尽可能平滑并且不会干扰(或受到干扰)其他 UI 元素,或者有没有一种更优越的方法来解决这样的问题 - 任何人都解决过这个问题?请告诉我,谢谢。

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 中的 Grid 中即可: 例如:

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

【问题讨论】:

  • 只是一个建议:动画可能看起来不错,但从用户和设备的角度考虑。设备,如果它必须加载大量图像,它会首先将它们加载到内存中(你想要性能,对吧?)。另外,它需要为它们设置动画。这会消耗大量内存,使手机发热/降低性能。请记住,一个伟大的应用程序不是你不能添加更多功能的地方,一个伟大的应用程序是你不能从中取出现有功能的应用程序,因为它们是必需的。您可以做的一件事是,不要为太多图像制作动画,而是像这样创建一个 gif 并使用它。
  • 感谢@Aousafrashid 的建议 - 是的,我完全明白这一点,但在这种情况下 1) 我在给定时间仅使用大约 50 个相同缓存图像的实例,2) 会很方便通过绑定更改图像(这是我最初概念的重点,例如,用户更改某个设置,然后在背景中飞行的图像无缝过渡到另一个,3)大多数设备都能够运行相当不错的 3D 游戏- 这是一个非常基本的动画,资产非常小,我希望它能够工作。

标签: c# performance user-interface animation xamarin.forms


【解决方案1】:

切换到 SkiaSharp 并获得更好的结果。动画看起来很流畅,如果流程中断,图像会保持适当的距离。还在初稿中使用内置的 Xamarin 动画实现了,我搞砸了何时运行它的检查; .IsVisible 属性即使页面不在屏幕上也将保持真实,因此在这个新版本中需要绑定到一个属性,告诉我页面是否实际处于活动状态(基于它何时导航到和导航离开),如果没有,则停止动画。现在这仍然只是处理水平滚动效果。希望其他人发现它有用,并且欢迎任何其他改进,请评论/发布答案!

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-17
    • 1970-01-01
    • 1970-01-01
    • 2017-09-30
    相关资源
    最近更新 更多