[Android] fixes ListView pull-to-refresh gesture in non-data areas (#5417) fixes...
authorPavel Yakovlev <v-payako@microsoft.com>
Thu, 6 Jun 2019 13:55:17 +0000 (06:55 -0700)
committerRui Marinho <me@ruimarinho.net>
Thu, 6 Jun 2019 13:55:17 +0000 (14:55 +0100)
* [Android] fixes ListView pull-to-refresh gesture in non-data areas

* refactoring

* addressing comments

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5268.xaml [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5268.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs

diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5268.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5268.xaml
new file mode 100644 (file)
index 0000000..1767193
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             x:Class="Xamarin.Forms.Controls.Issues.Issue5268">
+    <ListView x:Name="MyListView"
+              IsPullToRefreshEnabled="True"
+              IsRefreshing="{Binding IsBusy, Mode=OneWay}"
+              ItemsSource="{Binding Sources}"
+              BackgroundColor="Blue"
+              RefreshCommand="{Binding Command}" RowHeight="100">
+        <ListView.ItemTemplate>
+            <DataTemplate>
+                <ViewCell>
+                    <ScrollView>
+                        <StackLayout>
+                            <Label Text="{Binding Val}" MaxLines="100" HorizontalTextAlignment="Center" TextColor="White" FontSize="14" />
+                            <Label Text="{Binding Val}" MaxLines="100" HorizontalTextAlignment="Center" TextColor="White" FontSize="14" />
+                            <Label Text="{Binding Val}" MaxLines="100" HorizontalTextAlignment="Center" TextColor="White" FontSize="14" />
+                            <Label Text="{Binding Val}" MaxLines="100" HorizontalTextAlignment="Center" TextColor="White" FontSize="14" />
+                            <Label Text="{Binding Val}" MaxLines="100" HorizontalTextAlignment="Center" TextColor="White" FontSize="14" />
+                        </StackLayout>
+                    </ScrollView>
+                </ViewCell>
+            </DataTemplate>
+        </ListView.ItemTemplate>
+    </ListView>
+</ContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5268.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5268.xaml.cs
new file mode 100644 (file)
index 0000000..22b1650
--- /dev/null
@@ -0,0 +1,42 @@
+#if APP
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Input;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+       [Preserve(AllMembers = true)]
+       [Issue(IssueTracker.Github, 5268, "ListView with PullToRefresh enabled gestures conflict", PlatformAffected.Android)]
+       public partial class Issue5268 : ContentPage
+       {
+               [Preserve(AllMembers = true)]
+               public class SrcItem
+               {
+                       public string Val { get; set; }
+               }
+
+               string GenerateLongString() => string.Join(" \n", Enumerable.Range(0, 50).Select(i => $"{Sources.Count} item"));
+
+               public ObservableCollection<SrcItem> Sources { get; }
+               public ICommand Command { get; }
+
+               public Issue5268()
+               {
+                       InitializeComponent();
+                       Sources = new ObservableCollection<SrcItem>();
+                       Command = new Command(AddData);
+                       Sources.Add(new SrcItem { Val = GenerateLongString() });
+                       MyListView.BindingContext = this;
+               }
+
+               void AddData()
+               {
+                       IsBusy = true;
+                       Sources.Add(new SrcItem { Val = GenerateLongString() });
+                       IsBusy = false;
+               }
+       }
+}
+#endif
\ No newline at end of file
index d996581..22a7b12 100644 (file)
       <DependentUpon>Issue5003.xaml</DependentUpon>
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="$(MSBuildThisFileDirectory)Issue5268.xaml.cs">
+      <DependentUpon>Issue5268.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
     <Compile Include="$(MSBuildThisFileDirectory)LegacyComponents\NonAppCompatSwitch.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)MapsModalCrash.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)ModalActivityIndicatorTest.cs" />
       <Generator>MSBuild:Compile</Generator>
     </EmbeddedResource>
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="$(MSBuildThisFileDirectory)Issue5268.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </EmbeddedResource>
+  </ItemGroup>
 </Project>
\ No newline at end of file
index 58450bf..b8ed391 100644 (file)
@@ -9,6 +9,7 @@ using Xamarin.Forms.Internals;
 using System;
 using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
 using Android.Widget;
+using Android.Runtime;
 
 namespace Xamarin.Forms.Platform.Android
 {
@@ -102,6 +103,9 @@ namespace Xamarin.Forms.Platform.Android
                        return new Size(40, 40);
                }
 
+               protected virtual SwipeRefreshLayout CreateNativePullToRefresh(Context context)
+                       => new SwipeRefreshLayoutWithFixedNestedScrolling(context);
+
                protected override void OnAttachedToWindow()
                {
                        base.OnAttachedToWindow();
@@ -154,9 +158,9 @@ namespace Xamarin.Forms.Platform.Android
                                        nativeListView = CreateNativeControl();
                                        if (Forms.IsLollipopOrNewer)
                                                nativeListView.NestedScrollingEnabled = true;
-                                       _refresh = new SwipeRefreshLayout(ctx);
+                                       _refresh = CreateNativePullToRefresh(ctx);
                                        _refresh.SetOnRefreshListener(this);
-                                       _refresh.AddView(nativeListView, LayoutParams.MatchParent);
+                                       _refresh.AddView(nativeListView, new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent));
                                        SetNativeControl(nativeListView, _refresh);
 
                                        _headerView = new Container(ctx);
@@ -523,5 +527,54 @@ namespace Xamarin.Forms.Platform.Android
                                SetMeasuredDimension(widthSpec, heightSpec);
                        }
                }
+
+               class SwipeRefreshLayoutWithFixedNestedScrolling : SwipeRefreshLayout
+               {
+                       float _touchSlop;
+                       float _initialDownY;
+                       bool _nestedScrollAccepted;
+                       bool _nestedScrollCalled;
+
+                       public SwipeRefreshLayoutWithFixedNestedScrolling(Context ctx) : base(ctx)
+                       {
+                               _touchSlop = ViewConfiguration.Get(ctx).ScaledTouchSlop;
+                       }
+
+                       public override bool OnInterceptTouchEvent(MotionEvent ev)
+                       {
+                               if (ev.Action == MotionEventActions.Down)
+                                       _initialDownY = ev.GetAxisValue(Axis.Y);
+
+                               var isBeingDragged = base.OnInterceptTouchEvent(ev);
+
+                               if (!isBeingDragged && ev.Action == MotionEventActions.Move && _nestedScrollAccepted && !_nestedScrollCalled)
+                               {
+                                       var y = ev.GetAxisValue(Axis.Y);
+                                       var dy = (y - _initialDownY) / 2;
+                                       isBeingDragged = dy > _touchSlop;
+                               }
+
+                               return isBeingDragged;
+                       }
+
+                       public override void OnNestedScrollAccepted(AView child, AView target, [GeneratedEnum] ScrollAxis axes)
+                       {
+                               base.OnNestedScrollAccepted(child, target, axes);
+                               _nestedScrollAccepted = true;
+                               _nestedScrollCalled = false;
+                       }
+
+                       public override void OnStopNestedScroll(AView child)
+                       {
+                               base.OnStopNestedScroll(child);
+                               _nestedScrollAccepted = false;
+                       }
+
+                       public override void OnNestedScroll(AView target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
+                       {
+                               base.OnNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
+                               _nestedScrollCalled = true;
+                       }
+               }
        }
 }