Will
Will

Reputation: 57

Xamarin Forms - Listview Swipe Gestures not working on Android

I want to be able to swipe over the entire listview (not individual items in the listview) to perform a certain action. I have swipe gestures attached to a listview inside a Gridview. It works as expected in iOS, but on android the gestures are never triggered.

I've tried placing the gesture recognizers on the parent gridview for android only. This re-enables the feature, but only if the swipe includes the very edges of the screen (I assume due to the small padding I have that isn't taken up by the listview.) What can I do to make the behavior similar to ios?

<?xml version="1.0" encoding="utf-8" ?>
<ContentView 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.ViewModels"
             mc:Ignorable="d"
             x:Class="Test.Pages.ViewerPage"
             x:Name="View">
    <ContentView.BindingContext>
        <local:ViewerViewModel />
    </ContentView.BindingContext>
    <Grid Padding="10, 0, 10, 0">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="0" Padding="0, 5, 0, 0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="50" />
            </Grid.ColumnDefinitions>
            <Button Grid.Column="0" Text="Back" IsVisible="{Binding ShowBackButton}" Clicked="OnBackTap" VerticalOptions="Center" Padding="10, 0, 0, 0">
            </Button>
            <Button Grid.Column="1" x:Name="Header" HorizontalOptions="Center" BorderColor="Black" TextColor="Black" VerticalOptions="Center" BorderWidth="1" Clicked="OnTitleTap">
            </Button>
        </Grid>
        <ListView Grid.Row="1" x:Name="Viewer" HasUnevenRows="True" SeparatorVisibility="None" BackgroundColor="Green">
            <ListView.GestureRecognizers>
                <SwipeGestureRecognizer Direction="Left" Swiped="OnSwiped" />
                <SwipeGestureRecognizer Direction="Right" Swiped="OnSwiped"/>
            </ListView.GestureRecognizers>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell IsEnabled="False">
                        <StackLayout>
                            <Label>
                                <Label.FormattedText>
                                    <FormattedString>
                                        <Span Text="{Binding verse}" FontSize="18" TextColor="Gray"></Span>
                                        <Span Text="  "></Span>
                                        <Span Text="{Binding text}" FontSize="18" TextColor="Black"></Span>
                                    </FormattedString>
                                </Label.FormattedText>
                            </Label>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentView>

Upvotes: 1

Views: 2123

Answers (2)

Gaurav Ghode
Gaurav Ghode

Reputation: 11

You can use SwipeView. Wrap your ListView inside SwipeView.

<SwipeView SwipeStarted="SwipeView_SwipeStarted" SwipeEnded="SwipeView_SwipeEnded" SwipeChanging="SwipeView_SwipeChanging">
    
    <SwipeView.RightItems>
        <SwipeItems Mode="Reveal | Execute">
            <SwipeItem Text="Your Action" Command="{Binding SwipeRightToLeftCommand}" />
        </SwipeItems>
    </SwipeView.RightItems>
    
    <SwipeView.LeftItems>
        <SwipeItems Mode="Reveal | Execute">
            <SwipeItem Text="Your Action" Command="{Binding SwipeLeftToRightCommand}" />
        </SwipeItems>
    </SwipeView.LeftItems>
    
    <ListView>
    .
    . Your Complete ListView
    .
    </ListView>
    
</SwipeView>
  • To enable swiping Right to Left, use SwipeView.RightItems and for Left to Right, use SwipeView.LeftItems. If you want swiping enabled in both directions use both.

  • SwipeItems.Mode="Execute" will execute the command directly on swiping.

  • SwipeItems.Mode="Reveal" will just reveal the action button and you will have to click on it to execute the Command.

  • SwipeView is currently experimental, so remember to add the following line in your MainActivity class, before calling Forms.Init.

     Forms.SetFlags("SwipeView_Experimental");
    

Upvotes: 0

Leon Lu
Leon Lu

Reputation: 9274

You can create an custom renderer listview.

  public class MyListview:ListView
    {
        public event EventHandler SwipeLeft;
        public event EventHandler SwipeRight;

        public void OnSwipeLeft() =>
            SwipeLeft?.Invoke(this, null);

        public void OnSwipeRight() =>
            SwipeRight?.Invoke(this, null);
    }

In the android platform.

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using App3;
using App3.Droid;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(MyListview), typeof(MyListviewRenderer))]
namespace App3.Droid
{
    class MyListviewRenderer : ListViewRenderer
    {
        readonly CustomGestureListener _listener;
        readonly GestureDetector _detector;
        public MyListviewRenderer(Context context) : base(context)
        {
            _listener = new CustomGestureListener();
            _detector = new GestureDetector(context, _listener);
        }
        public override bool DispatchTouchEvent(MotionEvent e)
        {
            if (_detector != null)
            {
                _detector.OnTouchEvent(e);
                base.DispatchTouchEvent(e);
                return true;
            }

            return base.DispatchTouchEvent(e);
        }

        public override bool OnTouchEvent(MotionEvent ev)
        {
            base.OnTouchEvent(ev);

            if (_detector != null)
                return _detector.OnTouchEvent(ev);

            return false;
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement == null)
            {
                _listener.OnSwipeLeft -= HandleOnSwipeLeft;
                _listener.OnSwipeRight -= HandleOnSwipeRight;
            }

            if (e.OldElement == null)
            {
                _listener.OnSwipeLeft += HandleOnSwipeLeft;
                _listener.OnSwipeRight += HandleOnSwipeRight;
            }
        }
        

        void HandleOnSwipeLeft(object sender, EventArgs e) =>
            ((MyListview)Element).OnSwipeLeft();

        void HandleOnSwipeRight(object sender, EventArgs e) =>
            ((MyListview)Element).OnSwipeRight();
    }


    public class CustomGestureListener : GestureDetector.SimpleOnGestureListener
    {
        static readonly int SWIPE_THRESHOLD = 100;
        static readonly int SWIPE_VELOCITY_THRESHOLD = 100;

        MotionEvent mLastOnDownEvent;

        public event EventHandler OnSwipeLeft;
        public event EventHandler OnSwipeRight;

        public override bool OnDown(MotionEvent e)
        {
            mLastOnDownEvent = e;

            return true;
        }

        public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
        {
            if (e1 == null)
                e1 = mLastOnDownEvent;

            float diffY = e2.GetY() - e1.GetY();
            float diffX = e2.GetX() - e1.GetX();

            if (Math.Abs(diffX) > Math.Abs(diffY))
            {
                if (Math.Abs(diffX) > SWIPE_THRESHOLD && Math.Abs(velocityX) > SWIPE_VELOCITY_THRESHOLD)
                {
                    if (diffX > 0)
                        OnSwipeRight?.Invoke(this, null);
                    else
                        OnSwipeLeft?.Invoke(this, null);
                }
            }

            return base.OnFling(e1, e2, velocityX, velocityY);
        }
    }
 
}

Then you can use it in xaml.

   <app3:MyListview  x:Name="gi">

            <app3:MyListview.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>mono</x:String>
                    <x:String>monodroid</x:String>
                    <x:String>monotouch</x:String>
                    <x:String>monorail</x:String>
                    <x:String>monodevelop</x:String>
                    <x:String>monotone</x:String>
                    <x:String>monopoly</x:String>
                    <x:String>monomodal</x:String>
                    <x:String>mononucleosis</x:String>
                </x:Array>
            </app3:MyListview.ItemsSource>
        </app3:MyListview>

Here is layot backgroundcode,

 public MainPage()
        {
            InitializeComponent();

            gi.SwipeLeft += (s, e) =>
    DisplayAlert("Gesture Info", "Swipe Left Detected", "OK");

            gi.SwipeRight += (s, e) =>
                DisplayAlert("Gesture Info", "Swipe Right Detected", "OK");
        }

Here is running gif.

enter image description here

Upvotes: 2

Related Questions