Xamarin.forms Xamarin窗体滑动按钮

Xamarin.forms Xamarin窗体滑动按钮,xamarin.forms,slide,Xamarin.forms,Slide,我希望在我的应用程序中添加一个刷卡功能,该功能与(旧的?)iPhone上的解锁机制几乎相同(见图) 我正在努力研究如何在跨平台解决方案上实现这一点。我的直接想法是使用一个滑块和一个自定义的渲染器,但不确定如果用户在完成幻灯片之前放手,如何创建捕捉启动的功能。如果有人可以帮助实现该功能,或者对如何实现该功能有更好的建议,我们将不胜感激。使用xamarin表单的自定义渲染,以便您可以定义滑块在每个平台中的外观,在android中,Seekbar通常用于滑块和iOS用户界面中 另外,如果您决定

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


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

使用xamarin表单的自定义渲染,以便您可以定义滑块在每个平台中的外观,在android中,Seekbar通常用于滑块和iOS用户界面中

另外,如果您决定使用自定义渲染,则可以使用从android seek bar派生的滑块进行动画

也是一个用于iOS的自定义UIslider


您可以在可移植类中保留您的泛型方法,正如您所解释的那样,这种行为只有两种状态,这也可以通过使用自定义开关小部件来实现,除非和直到-您确实需要每个平台都有一个特别的本机外观;您几乎可以使用和()编写自己的自定义滑块控件,而不需要自定义渲染器。对于该捕捉效果,可以使用with effect

例如,您可以如下定义控件:;此示例控件扩展了
AbsoluteLayout
,同时允许您定义自己的表示thumb和track bar的控件。它还创建了一个几乎看不见的最上面的层,用作平移手势侦听器。手势完成后,它会检查幻灯片是否完成(即轨迹栏的整个宽度),然后引发
SlideCompleted
事件

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


更新:08/30 正如@morten-j-petersen希望支持类似填充条的实现;增加了对此的支持

更新的控制代码

公共类SlideToActView:绝对布局
{
公共静态只读BindableProperty ThumbProperty=
BindableProperty.Create(
“拇指”、typeof(视图)、typeof(SlideToActView),
defaultValue:默认(视图));
公众视野拇指
{
get{return(View)GetValue(ThumbProperty);}
set{SetValue(ThumbProperty,value);}
}
公共静态只读BindableProperty TrackBarProperty=
BindableProperty.Create(
“轨迹栏”、类型(视图)、类型(SlideToActView),
defaultValue:默认(视图));
公共视图轨迹栏
{
获取{return(View)GetValue(TrackBarProperty);}
set{SetValue(TrackBarProperty,value);}
}
公共静态只读BindableProperty FillBarProperty=
BindableProperty.Create(
“FillBar”、typeof(视图)、typeof(SlideToActView),
defaultValue:默认(视图));
公共视图栏
{
获取{return(View)GetValue(FillBarProperty);}
set{SetValue(FillBarProperty,value);}
}
专用PanGestureRecognizer_panGesture=新的PanGestureRecognizer();
私有视图_-gestureListener;
公共幻灯片ActView()
{
_pangestrue.PanUpdated+=onpangestrueupdated;
SizeChanged+=OnSizeChanged;
_gestureListener=newContentView{BackgroundColor=Color.White,不透明度=0.05};
_gestureListener.GestureRecognitors.Add(_panGesture);
}
公共事件事件处理程序幻灯片已完成;
私人常数双_fadeEffect=0.5;
私人建筑长度=50;
async void OnPanGestureUpdated(对象发送方,PanUpdatedEventArgs e)
{
if(Thumb==null | |轨迹栏==null | |填充栏==null)
返回;
开关(如状态类型)
{
案例手势状态。已启动:
等待TrackBar.FadeTo(_fadeEffect,_animLength);
打破
案例手势状态。正在运行:
//翻译并确保平移不会超出包装的用户界面元素边界。
var x=数学最大值(0,e.TotalX);
如果(x>(宽度-拇指宽度))
x=(宽度-拇指宽度);
//如果只想向前拖动,请取消对此的注释。
//if(e.TotalX=(Width-Thumb.Width-10/*为错误保留一些余量*/)
SlideCompleted?.Invoke(此为EventArgs.Empty);
打破
}
}
无效OnSizeChanged(对象发送方,事件参数e)
{
如果(宽度=0 | |高度=0)
返回;
if(Thumb==null | |轨迹栏==null | |填充栏==null)
返回;
儿童。清除();
SetLayoutFlags(轨迹栏,AbsoluteLayoutFlags.SizePropartional);
SetLayoutBounds(轨迹栏,新矩形(0,0,1,1));
添加(轨迹栏);
SetLayoutFlags(FillBar,AbsoluteLayoutFlags.None);
SetLayoutBounds(FillBar,新矩形(0,0,0,this.Height));
添加(FillBar);
SetLayoutFlags(Thumb,AbsoluteLayoutFlags.None);
SetLayoutBounds(拇指,新矩形(0,0,this.Width/5,this.Height));
添加(拇指);
SetLayoutFlags(_gestureListener,AbsoluteLayoutFlags.SizePropartional);
SetLayoutBounds(_gestureListener,新矩形(0,0,1,1));
<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!!";
}
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);


    }
}
<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>
public class SlideToOpenView : AbsoluteLayout
{

    public static readonly BindableProperty ThumbProperty =
    BindableProperty.Create(
            "Thumb", typeof(View), typeof(SlideToOpenView),
        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(SlideToOpenView),
            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(SlideToOpenView),
            defaultValue: default(View));

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

    private PanGestureRecognizer _panGesture = new PanGestureRecognizer();
    private View _gestureListener;

    private bool _android = false;

    public SlideToOpenView()
    {
        _panGesture.PanUpdated += OnPanGestureUpdated;

        SizeChanged += OnSizeChanged;

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

        if (Device.RuntimePlatform == Device.Android) {
            _android = true;
        }
    }


    public event EventHandler SlideCompleted;

    private const double _fadeEffect = 0.5;
    private const uint _animLength = 50;

    //Variable that stores the last state in axis X
    private double _lastX = -1;
    private bool _panRunning = false;

    async void OnPanGestureUpdated(object sender, PanUpdatedEventArgs e)
    {

        if (Thumb == null || TrackBar == null || FillBar == null)
            return;

        switch (e.StatusType)
        {
            case GestureStatus.Started:
                Debug.WriteLine("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));

                if (_panRunning == false && _android == true)
                {
                    Device.StartTimer(TimeSpan.FromMilliseconds(2000), TimerHandle);
                    _panRunning = true;
                }
                break;

            case GestureStatus.Completed:
                _panRunning = false;
                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),
                });

                //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;
        }
    }

    //Timer handle for Android Xamarin.Forms Gesture Bug
    bool TimerHandle()
    {

        if (_lastX == 0) {
            _lastX = -1;
            return false;
        }

        if (Thumb.TranslationX == _lastX && _lastX != -1) {
            _panRunning = false;
            var posX = Thumb.TranslationX;
            SetLayoutBounds(FillBar, new Rectangle(0, 0, 0, this.Height));

            // Reset translation applied during the pan

            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);
            _lastX = -1;
            return false;
        } 
            _lastX = Thumb.TranslationX;
            return true;

    }

    void OnSizeChanged(object sender, EventArgs e)
    {
        Debug.WriteLine("OnSizeChanged");
        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);


    }
}