0

I've created a custom renderer for Android and implemented it for a custom Slider control. I've got 2 solutions. In both solutions an Android App is build and started on an Android Phone. The test program shows the slider fine and I can drag the thumb up and down without any problems. The second solution is a bigger one where the slider should be implemented. If I run that solution the slider is displayed but I cannot move the thumb up and down. The OnTouchEvent is never fired. Below is all the code needed for the Slider. I know it's a lot but I hope someone sees the my problem.

My guess is that the ExportRenderer attribute at on the namespace somehow isn't found or triggered. The 'OnElementChanged' override is called but further nothing.

This is the custom renderer for the DraggableView (the thumb of the slider) in the Android project

using Android.Content;
using Android.Views;
using TGB.Xamarin.Forms.TestApp.Droid.Renderers.Views;
using TGB.Xamarin.Forms.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using static TGB.Xamarin.Forms.Views.DraggableView;
using xam = global::Xamarin.Forms;

[assembly: ExportRenderer(typeof(DraggableView), typeof(DraggableViewRenderer))]
namespace TGB.Xamarin.Forms.TestApp.Droid.Renderers.Views
{
    public class DraggableViewRenderer : VisualElementRenderer<xam.View>
    {
        float originalX;
        float originalY;
        float dX;
        float dY;
        bool firstTime = true;
        bool touchedDown = false;

        public DraggableViewRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<xam.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;
        }

        public override bool OnTouchEvent(MotionEvent e)
        {
            float x = e.RawX;
            float y = e.RawY;

            var dragView = Element as DraggableView;
            var parent = dragView.Parent as xam.View;

            switch (e.Action)
            {
                case MotionEventActions.Down:
                    if (dragView.DragMode == DragModes.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 (touchedDown)
                    {
                        var density = global::Xamarin.Essentials.DeviceDisplay.MainDisplayInfo.Density;

                        if (dragView.DragDirection == DragDirectionTypes.All || dragView.DragDirection == DragDirectionTypes.Horizontal)
                        {
                            var newX = x - dX;

                            if (parent != null)
                            {
                                if (newX + Width > parent.Width * density) newX = (float)(parent.Width * density - Width);
                                if (newX < 0) newX = 0;
                            }

                            SetX(newX);
                        }

                        if (dragView.DragDirection == DragDirectionTypes.All || dragView.DragDirection == DragDirectionTypes.Vertical)
                        {
                            var newY = y - dY;

                            if (parent != null)
                            {
                                if (newY + Height > parent.Height * density) newY = (float)(parent.Height * density - Height);
                                if (newY < 0) newY = 0;
                            }

                            SetY(newY);
                        }
                    }

                    break;

                case MotionEventActions.Up:
                    touchedDown = false;

                    DraggableViewDragEndedEventArgs args = new DraggableViewDragEndedEventArgs
                    {
                        X = GetX(),
                        Y = GetY()
                    };

                    dragView.DragEnded(args);

                    break;

                case MotionEventActions.Cancel:
                    touchedDown = false;

                    break;
            }
            return base.OnTouchEvent(e);
        }

        public override bool OnInterceptTouchEvent(MotionEvent e)
        {
            BringToFront();
            return true;
        }
    }
}

This is the DraggableView in the .Net standard 2.0 project.

    using System;
using System.Windows.Input;
using Xamarin.Forms;

namespace TGB.Xamarin.Forms.Views
{
    public partial class DraggableView : ContentView
    {
        public event EventHandler DragStart = delegate { };

        public delegate void DragEndEventHandler(object sender, DraggableViewDragEndedEventArgs args);

        public event DragEndEventHandler DragEnd;

        public enum DragDirectionTypes
        {
            All,
            Vertical,
            Horizontal
        }

        public enum DragModes
        {
            Touch,
            LongPress
        }

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

        public DragDirectionTypes DragDirection
        {
            get { return (DragDirectionTypes)GetValue(DragDirectionProperty); }
            set { SetValue(DragDirectionProperty, value); }
        }


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

        public DragModes DragMode
        {
            get { return (DragModes)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);
            IsDragging = true;
        }

        public void DragEnded(DraggableViewDragEndedEventArgs args)
        {

            IsDragging = false;
            DragEnd(this, args);
        }
    }
}

The EventArgs class used at EndDrag

using System;
using System.Collections.Generic;
using System.Text;

namespace TGB.Xamarin.Forms.Views
{
    public class DraggableViewDragEndedEventArgs : EventArgs
    {
        public double X;
        public double Y;
    }
}

This is the Slider class:

using System;
using TGB.Xamarin.Forms.Views;
using Xamarin.Forms;

namespace TGB.Xamarin.Forms.Controls
{
    public class Slider : AbsoluteLayout
    {
        private DraggableView m_Thumb;

        public static readonly BindableProperty MinProperty = BindableProperty.Create(
            "Min", typeof(int), typeof(Slider), 0, propertyChanged: (bindable, oldvalue, newvalue) => { ((Slider)bindable).InvalidateLayout(); });

        /// <summary>
        /// The minimum for the slider
        /// </summary>
        public int Min
        {
            set { SetValue(MinProperty, value); }
            get { return (int)GetValue(MinProperty); }
        }

        public static readonly BindableProperty MaxProperty = BindableProperty.Create(
            "Max", typeof(int), typeof(Slider), 100, propertyChanged: (bindable, oldvalue, newvalue) => { ((Slider)bindable).InvalidateLayout(); });

        /// <summary>
        /// The maximum for the slider
        /// </summary>
        public int Max
        {
            set { SetValue(MaxProperty, value); }
            get { return (int)GetValue(MaxProperty); }
        }

        public static readonly BindableProperty ValueProperty = BindableProperty.Create(
            "Value", typeof(int), typeof(Slider), 100, propertyChanged: (bindable, oldvalue, newvalue) => { ((Slider)bindable).InvalidateLayout(); });

        /// <summary>
        /// The value for the slider
        /// </summary>
        public int Value
        {
            set { SetValue(ValueProperty, value); }
            get { return (int)GetValue(ValueProperty); }
        }

        public Slider()
        {
            BackgroundColor = System.Drawing.Color.Green;

            Init();
        }

        private void Init()
        {
            m_Thumb = new DraggableView();

            m_Thumb.DragEnd += Draggable_DragEnd;

            m_Thumb.HorizontalOptions = new LayoutOptions { Alignment = LayoutAlignment.Fill, Expands = true };

            AbsoluteLayout.SetLayoutBounds(m_Thumb, new Rectangle(0, 0, 1, 0.2));
            AbsoluteLayout.SetLayoutFlags(m_Thumb, AbsoluteLayoutFlags.All);

            m_Thumb.BackgroundColor = System.Drawing.Color.Orange;

            this.Children.Add(m_Thumb);
        }

        private void Draggable_DragEnd(object sender, DraggableViewDragEndedEventArgs e)
        {
            var scope = Max - Min;

            var regionSize = this.Height - m_Thumb.Height;

            var perPixel = scope / regionSize;

            Value = Min + Convert.ToInt32(e.Y * perPixel);
        }
    }
}

Here's the implementation in the MainPage.xaml:

<?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:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:tgbc="clr-namespace:TGB.Xamarin.Forms.Controls;assembly=TGB.Xamarin.Forms"
             mc:Ignorable="d"
             x:Name="Page"
             x:Class="ElectroSpit.MainPage">

    <ContentPage.Resources>
        <StyleSheet Source="Styles\Styles.css" />
    </ContentPage.Resources>

    <StackLayout x:Name="Main" 
                    Orientation="Vertical"
                    BackgroundColor="Transparent"
                    VerticalOptions="FillAndExpand"
                    HorizontalOptions="FillAndExpand">
        <ContentView x:Name="Sidebar"
                    VerticalOptions="Start"
                    HorizontalOptions="Start"
                    StyleClass="Sidebar">
            <ContentView.Content>
                <Grid VerticalOptions="FillAndExpand" StyleClass="SidebarSlider">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>

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

                    <StackLayout Grid.Row="0"
                                 Grid.Column="0"
                                 Orientation="Vertical"
                                 VerticalOptions="FillAndExpand" 
                                 StyleClass="SidebarSlider" >


                         <!-- HERE ARE THE SLIDERS -->


                        <tgbc:Slider x:Name="Pitch"  VerticalOptions="FillAndExpand" StyleClass="SidebarSlider" />
                        <tgbc:Slider x:Name="Tremolo" VerticalOptions="FillAndExpand"  StyleClass="SidebarSlider" />
                    </StackLayout>

                    <StackLayout Grid.Row="0"
                                 Grid.Column="1"
                                 Orientation="Vertical">

                        <Label Text="Trans"  StyleClass="SidebarLabel"/>

                        <StackLayout Orientation="Horizontal">
                            <Button x:Name="TransposeUp" Text="+" StyleClass="SidebarButton"/>
                            <Button x:Name="TransposeDown" Text="{Binding Transposition}" StyleClass="SidebarButton" />
                        </StackLayout>

                        <Label Text="Brightness"  StyleClass="SidebarLabel"/>
                        <Stepper x:Name="Brichtness" Minimum="0" Maximum="100" StyleClass="SidebarStepper"/>
                        <Button x:Name="Settings" Text="Opt" StyleClass="SidebarButton"/>
                        <Label Text="Glide"  StyleClass="SidebarLabel"/>
                        <Stepper x:Name="Glide" Minimum="0" Maximum="100" StyleClass="SidebarStepper"/>
                        <Label Text="Octave" StyleClass="SidebarLabel"/>

                        <StackLayout Orientation="Horizontal">
                            <Button x:Name="OctaveUp" Text="+" StyleClass="SidebarButton"/>

                            <Button x:Name="OctaveDown" Text="{Binding Octave}" StyleClass="SidebarButton"/>
                        </StackLayout>
                    </StackLayout>

                    <StackLayout>
                        <ContentView x:Name="Schema"
                                     BackgroundColor="Transparent"
                                     VerticalOptions="FillAndExpand"
                                     HorizontalOptions="FillAndExpand">
                        </ContentView>
                    </StackLayout>
                </Grid>
            </ContentView.Content>
        </ContentView>
        <ContentView x:Name="Notes"
                        BackgroundColor="Transparent"
                        VerticalOptions="FillAndExpand"
                        HorizontalOptions="FillAndExpand">
        </ContentView>
    </StackLayout>
</ContentPage>

== EDIT ==

There is a difference in messages on the working and non-working app. This is what I get on the working app:

05-22 10:29:42.178 D/Mono    ( 6676): DllImport searching in: '__Internal' ('(null)').
05-22 10:29:42.178 D/Mono    ( 6676): Searching for 'java_interop_jnienv_call_float_method_a'.
05-22 10:29:42.178 D/Mono    ( 6676): Probing 'java_interop_jnienv_call_float_method_a'.
05-22 10:29:42.178 D/Mono    ( 6676): Found as 'java_interop_jnienv_call_float_method_a'.
05-22 10:29:42.201 D/Mono    ( 6676): DllImport searching in: '__Internal' ('(null)').
05-22 10:29:42.201 D/Mono    ( 6676): Searching for 'java_interop_jnienv_call_long_method_a'.
05-22 10:29:42.201 D/Mono    ( 6676): Probing 'java_interop_jnienv_call_long_method_a'.
05-22 10:29:42.201 D/Mono    ( 6676): Found as 'java_interop_jnienv_call_long_method_a'.
05-22 10:29:42.260 D/Mono    ( 6676): Requesting loading reference 6 (of 7) of TGB.Xamarin.Forms.Android.dll
05-22 10:29:42.260 D/Mono    ( 6676): Loading reference 6 of TGB.Xamarin.Forms.Android.dll asmctx DEFAULT, looking for Xamarin.Essentials, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
05-22 10:29:42.260 D/Mono    ( 6676): Assembly Ref addref TGB.Xamarin.Forms.Android[0xd57b8680] -> Xamarin.Essentials[0xebf7fd00]: 3

And this on the non-working App:

05-22 09:46:52.254 D/Mono (14307): DllImport searching in: '__Internal' ('(null)'). 
05-22 09:46:52.254 D/Mono (14307): Searching for 'java_interop_jnienv_call_float_method_a'. 
05-22 09:46:52.254 D/Mono (14307): Probing 'java_interop_jnienv_call_float_method_a'. 
05-22 09:46:52.254 D/Mono (14307): Found as 'java_interop_jnienv_call_float_method_a'.
Paul Sinnema
  • 2,534
  • 2
  • 20
  • 33
  • 1
    I test your code, if I drag the thumb up and down, the OnTouchEvent can be called, have no problem. Please see the screen:https://github.com/CherryBu/gif/blob/master/8.gif – Cherry Bu - MSFT May 22 '20 at 03:01
  • Yeah, I know. It's a bit frustrating that it does work in the one solution but not in the solution it has to be implemented in. Today I'm going to try and go about this step by step. Rule out code until it start working again. – Paul Sinnema May 22 '20 at 07:16
  • Concluded that it's not in the Slider class. The DraggableView(Renderer) classes are not working. And I get this error when I click the DraggableView in the App: 5-22 09:28:39.136 W/ame.electrospi(13883): Checksum mismatch for dex base.apk 05-22 09:28:39.136 W/ame.electrospi(13883): Could not add methods to the existing profiler. Clearing the profile data. – Paul Sinnema May 22 '20 at 07:36
  • Ok, that was misleading. The 2 just coincided by exident. The next comment contains messages when I click the DraggableView: – Paul Sinnema May 22 '20 at 07:48
  • 05-22 09:46:52.254 D/Mono (14307): DllImport searching in: '__Internal' ('(null)'). 05-22 09:46:52.254 D/Mono (14307): Searching for 'java_interop_jnienv_call_float_method_a'. 05-22 09:46:52.254 D/Mono (14307): Probing 'java_interop_jnienv_call_float_method_a'. 05-22 09:46:52.254 D/Mono (14307): Found as 'java_interop_jnienv_call_float_method_a'. – Paul Sinnema May 22 '20 at 07:48
  • Moved all the classes from the library to the main program libraries .net standard 2.0 and Android. No luck. – Paul Sinnema May 22 '20 at 08:04
  • Just Edited my post. Take a look at the bottom of the post. There's definitely a difference between the 2 solutions. – Paul Sinnema May 22 '20 at 08:34
  • Still at a loss here? – Paul Sinnema May 22 '20 at 12:02

1 Answers1

0

Ok, found it. The control was already working and my mistake was elsewhere. If you watch closely at the XAML you will see that the last StackLayout has no Grid.Row and Grid.Column attributes causing it to be on top of both Sliders. So the touch doesn't go to the sliders but to the ContentView that's inside the StackLayout giving me the impression it's not working.

Paul Sinnema
  • 2,534
  • 2
  • 20
  • 33
  • Glad to hear that you have solved your problem now, please remember to close your thread by mark your reply as answer, it is beneficial to others to find this answer, thanks. – Cherry Bu - MSFT May 26 '20 at 07:24