首页 > 解决方案 > Xamarin.Forms - 捕捉行为

问题描述

几年前,我在 Xamarin.iOS 中创建了一个类,它只是一个捕捉到屏幕边缘的 UIView。

public class MovableUIButton : UIView
{
    private UIPanGestureRecognizer PanGesture;

    private UIButton btnShoppingCart;

    private UIDynamicAnimator animator;
    private PointF snapPoint;
    private bool isInitialized = false;
    private UISnapBehavior snap;

    // offsets used to position image relative to touch point while being dragged
    private float dx = 0;
    private float dy = 0;

    public event EventHandler TouchUpInside;

    public MovableUIButton(CGRect rect) : base(rect)
    {
        Initialize();
    }

    public MovableUIButton(IntPtr handle) : base(handle)
    {
        Initialize();
    }

    private void Initialize()
    {
        PanGesture = new UIPanGestureRecognizer(DidPan);
        this.AddGestureRecognizer(PanGesture);

        // Make it round
        this.Layer.CornerRadius = this.Bounds.Width / 2;
        this.BackgroundColor = UIColor.FromRGB(89f / 255f, 157f / 255f, 255f / 255f);

        // Set the Border
        this.Layer.BorderColor = UIColor.White.CGColor;
        this.Layer.BorderWidth = 3;

        // Set a Shadow
        this.Layer.ShadowColor = UIColor.White.CGColor;
        this.Layer.ShadowOpacity = .5f;
        this.Layer.ShadowRadius = 8.0f;
        this.Layer.ShadowOffset = new System.Drawing.SizeF(0f, 0f);

        btnShoppingCart = new UIButton(Bounds);
        btnShoppingCart.Font = FontAwesome.Font(30);
        btnShoppingCart.SetTitle(BaseFontAwesome.FAShoppingCart, UIControlState.Normal);
        btnShoppingCart.UserInteractionEnabled = true;
        btnShoppingCart.TouchUpInside += BtnShoppingCart_TouchUpInside;
        this.AddSubview(btnShoppingCart);
    }

    private void InitializeAnimator()
    {
        snapPoint = new PointF((float)SetX(Superview.Bounds), (float)SetY(Superview.Bounds));
        animator = new UIDynamicAnimator(Superview);
        isInitialized = true;
    }

    private void DidPan()
    {
        if (isInitialized == false)
        {
            InitializeAnimator();
        }

        if ((PanGesture.State == UIGestureRecognizerState.Began || PanGesture.State == UIGestureRecognizerState.Changed) && (PanGesture.NumberOfTouches == 1))
        {
            // remove any previosuly applied snap behavior to avoid a flicker that will occur if both the gesture and physics are operating on the view simultaneously
            if (snap != null)
                animator.RemoveBehavior(snap);

            var p0 = PanGesture.LocationInView(Superview);

            if (dx == 0)
                dx = (float)(p0.X - this.Center.X);

            if (dy == 0)
                dy = (float)(p0.Y - this.Center.Y);

            // this is where the offsets are applied so that the location of the image follows the point where the image is touched as it is dragged,
            // otherwise the center of the image would snap to the touch point at the start of the pan gesture
            var p1 = new PointF((float)(p0.X - dx), (float)(p0.Y - dy));

            this.Center = p1;
        }
        else if (PanGesture.State == UIGestureRecognizerState.Ended)
        {
            // reset offsets when dragging ends so that they will be recalculated for next touch and drag that occurs
            dx = 0;
            dy = 0;

            snapPoint = new PointF((float)SetX(Superview.Bounds), (float)SetY(Superview.Bounds));

            SnapImageIntoPlace((System.Drawing.PointF)PanGesture.LocationInView(Superview));
        }
    }

    void SnapImageIntoPlace(PointF touchPoint)
    {
        snap = new UISnapBehavior(this, snapPoint);
        animator.AddBehavior(snap);
    }

    private nfloat SetX(CGRect superBounds)
    {
        nfloat x = 0f;

        if (this.Center.X > superBounds.Width / 2)
        {
            x = superBounds.Width - 20;
        }
        else
        {
            x = 20;
        }

        return x;
    }

    private nfloat SetY(CGRect superBounds)
    {
        nfloat y = 0f;

        if (this.Center.Y < 20)
        {
            y = 20;
        }
        else if (this.Center.Y > superBounds.Height - 20)
        {
            y = superBounds.Height - 20;
        }
        else
        {
            return this.Center.Y;
        }

        return y;
    }

    public override void TouchesBegan(NSSet touches, UIEvent evt)
    {
        base.TouchesBegan(touches, evt);
    }

    private void BtnShoppingCart_TouchUpInside(object sender, EventArgs e)
    {
        TouchUpInside?.Invoke(this, null);
    }
}

此类包含Xamarin.Forms中不可用的UISnapBehavior对象。因为我想将此移植到 Xamarin.Forms,是否有简单的解决方法或与UISnapBehavior类似的类?

标签: c#xamarin.formsuikituisnapbehavior

解决方案


不知道两年后你是否还在等待这个答案,但希望这对某人仍然有用。

您应该能够学习该课程并将其放入您的 .iOS 项目中,并为您的 pcl 中的自定义控件创建自定义渲染器。像这样的东西:

iOS 项目中的自定义渲染器:

[assembly: ExportRenderer(typeof(MovableUIButtonView), typeof(MovableButtonRenderer_iOS))]
namespace Test.iOS.Renderers
{
    public class MovableButtonRenderer_iOS : ViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<View> e)
        {
            if(Control == null)
                SetNativeControl(new MovableUIButton(Bounds));

            base.OnElementChanged(e);
        }
    }
}

要放入 pcl 的自定义控件:

namespace Test.Controls
{
    public class MovableUIButtonView : ContentView
    {
    }
}

在您使用它的任何页面上的 XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:local="clr-namespace:Test.Controls"
             mc:Ignorable="d"
             x:Class="Test.TestVW">

    <Grid>
        <local:MovableUIButtonView></local:MovableUIButtonView>
    </Grid>

</ContentPage>

推荐阅读