DraggableView C# Xamarin 的恢复位置命令

Posted

技术标签:

【中文标题】DraggableView C# Xamarin 的恢复位置命令【英文标题】:Restore Position Command of DraggableView C# Xamarin 【发布时间】:2017-11-22 14:32:54 【问题描述】:

问题在于,当 touchevent 结束时,应该调用恢复位置命令并将其恢复到原始位置。我认为问题在于我的属性更改事件

第二个问题是它应该能够锁定最近的数字标签并正确地将自身置于它们的中心,但是我不知道如何做到这一点。我只是想要一些关于如何做到这一点的提示,因为我很无能。

网格

<Grid BackgroundColor="White" ColumnSpacing="10" RowSpacing="10">
            <Label Text="Red" FontSize="Medium" HorizontalOptions="Center" />
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <BoxView Color="Black" Grid.Column="1" Grid.RowSpan="1"/>
            <BoxView Color="Gray" Grid.Column="2" Grid.RowSpan="1"/>
            <Label Text="9" Font ="60" Grid.Row="1" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="8" Font ="60" Grid.Row="2" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="7" Font ="60" Grid.Row="3" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="6" Font ="60" Grid.Row="4" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="5" Font ="60" Grid.Row="5" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="4" Font ="60" Grid.Row="6" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="3" Font ="60" Grid.Row="7" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="2" Font ="60" Grid.Row="8" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="1" Font ="60" Grid.Row="9" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />
            <Label Text="0" Font ="60" Grid.Row="10" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" />

            <local:DraggableView x:Name="dragView" DragMode="LongPress" DragDirection="All" Grid.Row="3" Grid.RowSpan="3" Grid.Column="3" >
                <local:DraggableView.Content>
                    <BoxView x:Name="image" BackgroundColor="Pink" />
                </local:DraggableView.Content>
            </local:DraggableView>
        </Grid>

跨平台代码,Xamarin

  public partial class DraggableView : ContentView
        
            public event EventHandler DragStart = delegate  ;
            public event EventHandler DragEnd = delegate  ;

        public static readonly BindableProperty DragDirectionProperty = BindableProperty.Create(
        propertyName: "DragDirection",
        returnType: typeof(DragDirectionType),
        declaringType: typeof(DraggableView),
        defaultValue: DragDirectionType.All,
        defaultBindingMode: BindingMode.TwoWay);

    public DragDirectionType DragDirection
    
        get  return (DragDirectionType)GetValue(DragDirectionProperty); 
        set  SetValue(DragDirectionProperty, value); 
    


    public static readonly BindableProperty DragModeProperty = BindableProperty.Create(
       propertyName: "DragMode",
       returnType: typeof(DragMode),
       declaringType: typeof(DraggableView),
       defaultValue: DragMode.LongPress,
       defaultBindingMode: BindingMode.TwoWay);

    public DragMode DragMode
    
        get  return (DragMode)GetValue(DragModeProperty); 
        set  SetValue(DragModeProperty, value); 
    

    public static readonly BindableProperty IsDraggingProperty = BindableProperty.Create(
      propertyName: "IsDragging",
      returnType: typeof(bool),
      declaringType: typeof(DraggableView),
      defaultValue: false,
      defaultBindingMode: BindingMode.TwoWay);

    public bool IsDragging
    
        get  return (bool)GetValue(IsDraggingProperty); 
        set  SetValue(IsDraggingProperty, value); 
    

    public static readonly BindableProperty RestorePositionCommandProperty = BindableProperty.Create(nameof(RestorePositionCommand), typeof(ICommand), typeof(DraggableView), default(ICommand), BindingMode.TwoWay, null, OnRestorePositionCommandPropertyChanged);

    static void OnRestorePositionCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    
        var source = bindable as DraggableView;
        if (source == null)
        
            return;
        
        source.OnRestorePositionCommandChanged();
    

    private void OnRestorePositionCommandChanged()
    
        OnPropertyChanged("RestorePositionCommand");
    

    public ICommand RestorePositionCommand
    
        get
        
            return (ICommand)GetValue(RestorePositionCommandProperty);
        
        set
        
            SetValue(RestorePositionCommandProperty, value);
        
    

    public void DragStarted()
    
        DragStart(this, default(EventArgs));
        IsDragging = true;
    

    public void DragEnded()
    
        IsDragging = false;
        DragEnd(this, default(EventArgs));
    

android部分代码

public class DraggableViewRenderer : VisualElementRenderer<Xamarin.Forms.View>
    
        float originalX;
        float originalY;
        float dX;
        float dY;
        bool firstTime = true;
        bool touchedDown = false;
    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
    
        base.OnElementChanged(e);

        if (e.OldElement != null)
        
            LongClick -= HandleLongClick;
        
        if (e.NewElement != null)
        
            LongClick += HandleLongClick;
            var dragView = Element as DraggableView;
            dragView.RestorePositionCommand = new Command(() =>
            
                if (!firstTime)
                
                    SetX(originalX);
                    SetY(originalY);
                

            );
        
    
    private void HandleLongClick(object sender, LongClickEventArgs e)
    
        var dragView = Element as DraggableView;
        if (firstTime)
        
            originalX = GetX();
            originalY = GetY();
            firstTime = false;
        
        dragView.DragStarted();
        touchedDown = true;
    

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    
        var dragView = Element as DraggableView;
        base.OnElementPropertyChanged(sender, e);

    
    protected override void OnVisibilityChanged(AView.View changedView, [GeneratedEnum] ViewStates visibility)
    
        base.OnVisibilityChanged(changedView, visibility);
        if (visibility == ViewStates.Visible)
        



        
    

    // What happens when you toch
    public override bool OnTouchEvent(MotionEvent e)
    
        float x = e.RawX;
        float y = e.RawY;
        var dragView = Element as DraggableView;
        switch (e.Action)
        
            case MotionEventActions.Down:
                if (dragView.DragMode == DragMode.Touch)
                
                    if (!touchedDown)
                    
                        if (firstTime)
                        
                            originalX = GetX();
                            originalY = GetY();
                            firstTime = false;
                        
                        dragView.DragStarted();
                    
                    touchedDown = true;
                
                dX = x - this.GetX();
                dY = y - this.GetY();
                break;
            case MotionEventActions.Move:
                if (dragView.DragDirection == DragDirectionType.All || dragView.DragDirection == DragDirectionType.Horizontal)
                
                    SetX(x - dX);
                

                if (dragView.DragDirection == DragDirectionType.All || dragView.DragDirection == DragDirectionType.Vertical)
                
                    SetY(y - dY);
                
                break;
            case MotionEventActions.Up:
                touchedDown = false;
                dragView.DragEnded();
                break;
            case MotionEventActions.Cancel:
                touchedDown = false;
                break;
        
        return true;
    
    public override bool OnInterceptTouchEvent(MotionEvent e)
    

        BringToFront();
        return true;
    

ios 部分

public class DraggableViewRenderer : VisualElementRenderer<View>

    bool longPress = false;
    bool firstTime = true;
    double lastTimeStamp = 0f;
    UIPanGestureRecognizer panGesture;
    CGPoint lastLocation;
    CGPoint originalPosition;
    UIGestureRecognizer.Token panGestureToken;
    void DetectPan()
    
        var dragView = Element as DraggableView;
        if (longPress || dragView.DragMode == DragMode.Touch)
        
            if (panGesture.State == UIGestureRecognizerState.Began)
            
                dragView.DragStarted();
                if (firstTime)
                
                    originalPosition = Center;
                    firstTime = false;
                
            

            CGPoint translation = panGesture.TranslationInView(Superview);
            var currentCenterX = Center.X;
            var currentCenterY = Center.Y;
            if (dragView.DragDirection == DragDirectionType.All || dragView.DragDirection == DragDirectionType.Horizontal)
            
                currentCenterX = lastLocation.X + translation.X;
            

            if (dragView.DragDirection == DragDirectionType.All || dragView.DragDirection == DragDirectionType.Vertical)
            
                currentCenterY = lastLocation.Y + translation.Y;
            

            Center = new CGPoint(currentCenterX, currentCenterY);

            if (panGesture.State == UIGestureRecognizerState.Ended)
            
                dragView.DragEnded();
                longPress = false;
            
        
    

    protected override void OnElementChanged(ElementChangedEventArgs<View> e)
    
        base.OnElementChanged(e);

        if (e.OldElement != null)
        
            RemoveGestureRecognizer(panGesture);
            panGesture.RemoveTarget(panGestureToken);
        
        if (e.NewElement != null)
        
            var dragView = Element as DraggableView;
            panGesture = new UIPanGestureRecognizer();
            panGestureToken = panGesture.AddTarget(DetectPan);
            AddGestureRecognizer(panGesture);


            dragView.RestorePositionCommand = new Command(() =>
            
                if (!firstTime)
                

                    Center = originalPosition;
                
            );

        

    
    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    
        var dragView = Element as DraggableView;
        base.OnElementPropertyChanged(sender, e);

    

    public override void TouchesBegan(NSSet touches, UIEvent evt)
    
        base.TouchesBegan(touches, evt);
        lastTimeStamp = evt.Timestamp;
        Superview.BringSubviewToFront(this);
        lastLocation = Center;
    
    public override void TouchesMoved(NSSet touches, UIEvent evt)
    
        if (evt.Timestamp - lastTimeStamp >= 0.5)
        
            longPress = true;
        
        base.TouchesMoved(touches, evt);
    


【问题讨论】:

【参考方案1】:

问题在于,当 touchevent 结束时,应该调用恢复位置命令并将其恢复到原始位置。我认为问题在于我的属性更改事件

    如果你想要DraggableView,那么你应该尝试使用ViewRenderer而不是VisualElementRenderer&lt;Xamarin.Forms.View&gt;

    Renderer 不是自定义视图,你不应该尝试在渲染器中覆盖 OnTouchEvent,它不会起作用,你可以在原生项目中创建自定义视图,然后在你的 @ 中使用 SetNativeControl 987654327@:

    //Create this custom view in your Xamarin.Android project.
    public class DragViewNative:View
    
    
       public DragViewNative(Context context) : base(context)
       
    
       
    
       public override bool OnTouchEvent(MotionEvent e)
       
           //implement your OnTouchEvent logic here
           ...
       
     ...
    
    
    public class DraggableViewRenderer : ViewRenderer
    
        ...
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
        
            base.OnElementChanged(e);
            //set native control to be your custom view
            SetNativeControl(new DragViewNative(Xamarin.Forms.Forms.Context));
    
            //other logic here
        
    
    

更新: 关于RestorePositionCommand没有触发的问题:

该命令未触发,因为您没有手动调用它。由于它是您在控件中定义的自定义ICommand,因此您需要手动调用它来触发它。例如,以下代码将让它在您的 DragEnded 函数中执行:

//Inside DraggableView.cs
public void DragEnded()

    IsDragging = false;

    //add this line and your command will be triggered
    this.RestorePositionCommand.Execute(null);
    DragEnd(this, default(EventArgs));

【讨论】:

我不确定您的意思,这如何解决我的 onrestore 命令未正确执行的问题?你能ELI5吗? 这是在xamarin.forms中创建自定义控件的技巧,命令问题请参考我的更新。 我对dragnative有点困惑,我是不是应该把原来的Ontouchevent函数放在新的dragview native里面? 我想说,这是最佳做法,目前您的代码仍然有效。

以上是关于DraggableView C# Xamarin 的恢复位置命令的主要内容,如果未能解决你的问题,请参考以下文章

C# Xamarin IOS:相当于 C# IOS Xamarin 中的 SystemClock.ElapsedRealtime()

C#使用Xamarin开发Android应用程序 -- 系列文章

使用异步等待 C# Xamarin

C# (Xamarin) 如何将 JSON 写入数组?

用 C# (Xamarin) 编写 Android 应用程序 [关闭]

无法使用 C# 从 Xamarin.iOs 和 Xamarin.Android 连接到 MySql