Commands in Custom Controller

MortMort DKMember
edited August 2017 in Xamarin.Forms

In my current project I've had to implement a custom controller. It works absolutely perfectly but at the moment I can only attach Events to it and I'd like to use Commands so I can move the logic to my viewmodel. However, I'm struggling to implement commands and replace the event so I'm hoping someone can help.

My custom controller (without my failed implementation:

using System;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace FirstAED.CustomControllers
{
    class SlideToAct : AbsoluteLayout
    {
        public static readonly BindableProperty ThumbProperty =
            BindableProperty.Create(
                "Thumb", typeof(View), typeof(SlideToAct),
                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)
        {
            ((SlideToAct)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(SlideToAct),
                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)
        {
            ((SlideToAct)bindable).OnTrackBarChangedImpl((View)oldValue, (View)newValue);
        }

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

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

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

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

            _gestureListener = new ContentView { BackgroundColor = Color.Black, Opacity = 0 };
            _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);
        }
    }
}

My XAML implementation:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:FirstAED.CustomControllers"
             x:Class="FirstAED.Views.AlarmAlertPage">
    <ContentPage.Content>
        <StackLayout Margin="40">
            <local:SlideToAct HeightRequest="50">
                <local:SlideToAct.Thumb>
                    <Frame CornerRadius="10" HasShadow="false" BackgroundColor="White" Padding="0">
                        <Image Source="decline.png" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="40" WidthRequest="40" />
                    </Frame>
                </local:SlideToAct.Thumb>

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

                <local:SlideToAct.FillBar>
                    <Frame CornerRadius="10" HasShadow="false" BackgroundColor="Red" Padding="0" />
                </local:SlideToAct.FillBar>
            </local:SlideToAct>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

I have worked with Commands and generally get the idea but I am new to custom controllers and how to set them up.

Sign In or Register to comment.