Xamarin Forms 滑动按钮

2023-12-11

我希望在我的应用程序中添加一个滑动功能,该功能与(旧?)iPhone 上的解锁机制几乎相同(参见图片)。

enter image description here enter image description here

我正在努力解决如何在跨平台解决方案上实现这一点。我的直接想法是使用滑块和自定义渲染器,但不确定如果用户在完成幻灯片之前放手,如何创建捕捉以启动的功能。如果有人可以协助该功能或者他们对如何实现这一功能有更好的建议,我将不胜感激。


除非并且直到 - 您确实需要为每个平台提供特别原生的外观;您几乎可以使用编写自己的自定义滑块控件PanGestureRecognizer, and 绝对布局 (无需任何自定义渲染器)。对于捕捉效果,您可以使用Translation动画片 with 三次缓动 effect.

例如,您可以定义一个控件如下;该示例控件扩展了AbsoluteLayout同时允许您定义自己的代表拇指和轨迹栏的控件。它还创建了一个几乎不可见的最顶层来充当平移手势侦听器。一旦手势完成,它会检查滑动是否完成(即轨迹栏的整个宽度) - 然后抬起SlideCompleted event.

public class SlideToActView : AbsoluteLayout
{
    public static readonly BindableProperty ThumbProperty =
        BindableProperty.Create(
            "Thumb", typeof(View), typeof(SlideToActView),
            defaultValue: default(View), propertyChanged: OnThumbChanged);

    public View Thumb
    {
        get { return (View)GetValue(ThumbProperty); }
        set { SetValue(ThumbProperty, value); }
    }

    private static void OnThumbChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((SlideToActView)bindable).OnThumbChangedImpl((View)oldValue, (View)newValue);
    }

    protected virtual void OnThumbChangedImpl(View oldValue, View newValue)
    {
        OnSizeChanged(this, EventArgs.Empty);
    }

    public static readonly BindableProperty TrackBarProperty =
        BindableProperty.Create(
            "TrackBar", typeof(View), typeof(SlideToActView),
            defaultValue: default(View), propertyChanged: OnTrackBarChanged);

    public View TrackBar
    {
        get { return (View)GetValue(TrackBarProperty); }
        set { SetValue(TrackBarProperty, value); }
    }

    private static void OnTrackBarChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((SlideToActView)bindable).OnTrackBarChangedImpl((View)oldValue, (View)newValue);
    }

    protected virtual void OnTrackBarChangedImpl(View oldValue, View newValue)
    {
        OnSizeChanged(this, EventArgs.Empty);
    }

    private PanGestureRecognizer _panGesture = new PanGestureRecognizer();
    private View _gestureListener;
    public SlideToActView()
    {
        _panGesture.PanUpdated += OnPanGestureUpdated;
        SizeChanged += OnSizeChanged;

        _gestureListener = new ContentView { BackgroundColor = Color.White, Opacity = 0.05 };
        _gestureListener.GestureRecognizers.Add(_panGesture);
    }

    public event EventHandler SlideCompleted;

    private const double _fadeEffect = 0.5;
    private const uint _animLength = 50;
    async void OnPanGestureUpdated(object sender, PanUpdatedEventArgs e)
    {
        if (Thumb == null | TrackBar == null)
            return;

        switch (e.StatusType)
        {
            case GestureStatus.Started:
                await TrackBar.FadeTo(_fadeEffect, _animLength);
                break;

            case GestureStatus.Running:
                // Translate and ensure we don't pan beyond the wrapped user interface element bounds.
                var x = Math.Max(0, e.TotalX);
                if (x > (Width - Thumb.Width))
                    x = (Width - Thumb.Width);

                if (e.TotalX < Thumb.TranslationX)
                    return;
                Thumb.TranslationX = x;
                break;

            case GestureStatus.Completed:
                var posX = Thumb.TranslationX;

                // Reset translation applied during the pan (snap effect)
                await TrackBar.FadeTo(1, _animLength);
                await Thumb.TranslateTo(0, 0, _animLength * 2, Easing.CubicIn);

                if (posX >= (Width - Thumb.Width - 10/* keep some margin for error*/))
                    SlideCompleted?.Invoke(this, EventArgs.Empty);
                break;
        }
    }

    void OnSizeChanged(object sender, EventArgs e)
    {
        if (Width == 0 || Height == 0)
            return;
        if (Thumb == null || TrackBar == null)
            return;


        Children.Clear();

        SetLayoutFlags(TrackBar, AbsoluteLayoutFlags.SizeProportional);
        SetLayoutBounds(TrackBar, new Rectangle(0, 0, 1, 1));
        Children.Add(TrackBar);

        SetLayoutFlags(Thumb, AbsoluteLayoutFlags.None);
        SetLayoutBounds(Thumb, new Rectangle(0, 0, this.Width/5, this.Height));
        Children.Add(Thumb);

        SetLayoutFlags(_gestureListener, AbsoluteLayoutFlags.SizeProportional);
        SetLayoutBounds(_gestureListener, new Rectangle(0, 0, 1, 1));
        Children.Add(_gestureListener);
    }
}

使用示例:

<StackLayout Margin="40">
    <local:SlideToActView HeightRequest="50" SlideCompleted="Handle_SlideCompleted">
        <local:SlideToActView.Thumb>
            <Frame CornerRadius="10" HasShadow="false" BackgroundColor="Silver" Padding="0">
                <Image Source="icon.png" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="40" WidthRequest="40" />
            </Frame>
        </local:SlideToActView.Thumb>

        <local:SlideToActView.TrackBar>
            <Frame CornerRadius="10" HasShadow="false" BackgroundColor="Gray" Padding="0">
                <Label Text="Slide 'x' to cancel" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" />
            </Frame>
        </local:SlideToActView.TrackBar>
    </local:SlideToActView>
    <Label x:Name="MessageLbl" FontAttributes="Bold" TextColor="Green" />
</StackLayout>

代码隐藏

void Handle_SlideCompleted(object sender, System.EventArgs e)
{
    MessageLbl.Text = "Success!!";
}

ios simulator android emulator


更新:08/30

由于 @morten-j-petersen 希望支持类似填充栏的实现;对此添加了支持。

更新了控制代码

public class SlideToActView : AbsoluteLayout
{
    public static readonly BindableProperty ThumbProperty =
        BindableProperty.Create(
            "Thumb", typeof(View), typeof(SlideToActView),
            defaultValue: default(View));

    public View Thumb
    {
        get { return (View)GetValue(ThumbProperty); }
        set { SetValue(ThumbProperty, value); }
    }

    public static readonly BindableProperty TrackBarProperty =
        BindableProperty.Create(
            "TrackBar", typeof(View), typeof(SlideToActView),
            defaultValue: default(View));

    public View TrackBar
    {
        get { return (View)GetValue(TrackBarProperty); }
        set { SetValue(TrackBarProperty, value); }
    }

    public static readonly BindableProperty FillBarProperty =
        BindableProperty.Create(
            "FillBar", typeof(View), typeof(SlideToActView),
            defaultValue: default(View));

    public View FillBar
    {
        get { return (View)GetValue(FillBarProperty); }
        set { SetValue(FillBarProperty, value); }
    }

    private PanGestureRecognizer _panGesture = new PanGestureRecognizer();
    private View _gestureListener;
    public SlideToActView()
    {
        _panGesture.PanUpdated += OnPanGestureUpdated;
        SizeChanged += OnSizeChanged;

        _gestureListener = new ContentView { BackgroundColor = Color.White, Opacity = 0.05 };
        _gestureListener.GestureRecognizers.Add(_panGesture);
    }

    public event EventHandler SlideCompleted;

    private const double _fadeEffect = 0.5;
    private const uint _animLength = 50;
    async void OnPanGestureUpdated(object sender, PanUpdatedEventArgs e)
    {
        if (Thumb == null || TrackBar == null || FillBar == null)
            return;

        switch (e.StatusType)
        {
            case GestureStatus.Started:
                await TrackBar.FadeTo(_fadeEffect, _animLength);
                break;

            case GestureStatus.Running:
                // Translate and ensure we don't pan beyond the wrapped user interface element bounds.
                var x = Math.Max(0, e.TotalX);
                if (x > (Width - Thumb.Width))
                    x = (Width - Thumb.Width);

                //Uncomment this if you want only forward dragging.
                //if (e.TotalX < Thumb.TranslationX)
                //    return;
                Thumb.TranslationX = x;
                SetLayoutBounds(FillBar, new Rectangle(0, 0, x + Thumb.Width / 2, this.Height));
                break;

            case GestureStatus.Completed:
                var posX = Thumb.TranslationX;
                SetLayoutBounds(FillBar, new Rectangle(0, 0, 0, this.Height));

                // Reset translation applied during the pan
                await Task.WhenAll(new Task[]{
                    TrackBar.FadeTo(1, _animLength),
                    Thumb.TranslateTo(0, 0, _animLength * 2, Easing.CubicIn),
                });

                if (posX >= (Width - Thumb.Width - 10/* keep some margin for error*/))
                    SlideCompleted?.Invoke(this, EventArgs.Empty);
                break;
        }
    }

    void OnSizeChanged(object sender, EventArgs e)
    {
        if (Width == 0 || Height == 0)
            return;
        if (Thumb == null || TrackBar == null || FillBar == null)
            return;


        Children.Clear();

        SetLayoutFlags(TrackBar, AbsoluteLayoutFlags.SizeProportional);
        SetLayoutBounds(TrackBar, new Rectangle(0, 0, 1, 1));
        Children.Add(TrackBar);

        SetLayoutFlags(FillBar, AbsoluteLayoutFlags.None);
        SetLayoutBounds(FillBar, new Rectangle(0, 0, 0, this.Height));
        Children.Add(FillBar);

        SetLayoutFlags(Thumb, AbsoluteLayoutFlags.None);
        SetLayoutBounds(Thumb, new Rectangle(0, 0, this.Width/5, this.Height));
        Children.Add(Thumb);

        SetLayoutFlags(_gestureListener, AbsoluteLayoutFlags.SizeProportional);
        SetLayoutBounds(_gestureListener, new Rectangle(0, 0, 1, 1));
        Children.Add(_gestureListener);


    }
}

XAML 用法

<StackLayout Margin="40">
    <local:SlideToActView HeightRequest="50" SlideCompleted="Handle_SlideCompleted">
        <local:SlideToActView.Thumb>
            <Frame CornerRadius="10" HasShadow="false" BackgroundColor="Silver" Padding="0">
                <Image Source="icon.png" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="40" WidthRequest="40" />
            </Frame>
        </local:SlideToActView.Thumb>

        <local:SlideToActView.TrackBar>
            <Frame CornerRadius="10" HasShadow="false" BackgroundColor="Gray" Padding="0">
                <Label Text="Slide 'x' to cancel" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" />
            </Frame>
        </local:SlideToActView.TrackBar>

        <local:SlideToActView.FillBar>
            <Frame CornerRadius="10" HasShadow="false" BackgroundColor="Red" Padding="0" />
        </local:SlideToActView.FillBar>
    </local:SlideToActView>
    <Label x:Name="MessageLbl" FontAttributes="Bold" TextColor="Green" />
</StackLayout>

enter image description here

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Xamarin Forms 滑动按钮 的相关文章

随机推荐