Skip to content

Commit

Permalink
Merge pull request #6796 from unoplatform/dev/dr/manipLV
Browse files Browse the repository at this point in the history
Fix ListView scrolling with manipulation
  • Loading branch information
dr1rrb authored Aug 20, 2021
2 parents 6471ab2 + 245ae57 commit 60d4a34
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ public void Manipulation_WithNestedElement()
Assert.IsTrue(result.Contains("[PARENT] Manip delta[CHILD] Pointer moved[PARENT] Manip delta[CHILD] Pointer moved"));
}

[Test]
[AutoRetry]
[ActivePlatforms(Platform.Android, Platform.iOS)] // Touch only test
public void Manipulation_WhenInListViewAndManipulationTranslateX_ThenAbort()
{
Run("UITests.Windows_UI_Input.GestureRecognizerTests.Manipulation_WhenInListView");

var scroller = _app.WaitForElement("ItemsSupportsTranslateX").Single().Rect;

_app.DragCoordinates(scroller.CenterX, scroller.Bottom - 5, scroller.CenterX, scroller.Y + 10);

var result = TakeScreenshot("after_scroll", ignoreInSnapshotCompare: true);

ImageAssert.DoesNotHaveColorAt(result, scroller.CenterX, scroller.Y + 10, "#FF0000");
}

[Test]
[AutoRetry]
[ActivePlatforms(Platform.Android, Platform.iOS)]
Expand Down
7 changes: 7 additions & 0 deletions src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\Manipulation_WhenInListView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\Manipulation_WhenInScrollViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -4632,6 +4636,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\Manipulation_Inertia.xaml.cs">
<DependentUpon>Manipulation_Inertia.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\Manipulation_WhenInListView.xaml.cs">
<DependentUpon>Manipulation_WhenInListView.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Input\GestureRecognizerTests\Manipulation_WhenInScrollViewer.xaml.cs">
<DependentUpon>Manipulation_WhenInScrollViewer.xaml</DependentUpon>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Page
x:Class="UITests.Windows_UI_Input.GestureRecognizerTests.Manipulation_WhenInListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UITests.Windows_UI_Input.GestureRecognizerTests"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<Border Height="504" Width="204" BorderBrush="Black" BorderThickness="2">
<ListView x:Name="ItemsSupportsTranslateX">
<ListView.ItemTemplate>
<DataTemplate>
<Border Height="200" Width="200" Background="{Binding}" ManipulationMode="TranslateX" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Uno.UI.Samples.Controls;

namespace UITests.Windows_UI_Input.GestureRecognizerTests
{
[Sample(
"Gesture recognizer", "ListView",
Description = "Automated test which validates if vertical scrolling of ListView works properly even if items does handles some manipulations.",
IgnoreInSnapshotTests = true)]
public sealed partial class Manipulation_WhenInListView : Page
{
public Manipulation_WhenInListView()
{
this.InitializeComponent();

ItemsSupportsTranslateX.ItemsSource = new[] { "#FF0000", "#FF8000", "#FFFF00", "#008000", "#0000FF", "#A000C0" };
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ public partial class NativeListViewBase : UnoRecyclerView, ILayoutConstraints
/// </summary>
private bool _isInAnimatedScroll;

bool _shouldRecalibrateFlingVelocity;
float? _previousX, _previousY;
float? _deltaX, _deltaY;
private ScrollBarVisibility? _horizontalScrollBarVisibility;
private ScrollBarVisibility? _verticalScrollBarVisibility;
private bool _shouldRecalibrateFlingVelocity;
private float? _previousX, _previousY;
private float? _deltaX, _deltaY;

internal BufferViewCache ViewCache { get; }

Expand Down Expand Up @@ -125,13 +127,10 @@ public Thickness Padding

public ScrollBarVisibility HorizontalScrollBarVisibility
{
get
{
return HorizontalScrollBarEnabled ? ScrollBarVisibility.Visible : ScrollBarVisibility.Hidden;
}

get => _horizontalScrollBarVisibility ?? (HorizontalScrollBarEnabled ? ScrollBarVisibility.Visible : ScrollBarVisibility.Disabled);
set
{
_horizontalScrollBarVisibility = value;
switch (value)
{
case ScrollBarVisibility.Disabled:
Expand All @@ -149,13 +148,10 @@ public ScrollBarVisibility HorizontalScrollBarVisibility

public ScrollBarVisibility VerticalScrollBarVisibility
{
get
{
return VerticalScrollBarEnabled ? ScrollBarVisibility.Visible : ScrollBarVisibility.Hidden;
}

get => _verticalScrollBarVisibility ?? (VerticalScrollBarEnabled ? ScrollBarVisibility.Visible : ScrollBarVisibility.Disabled);
set
{
_verticalScrollBarVisibility = value;
switch (value)
{
case ScrollBarVisibility.Disabled:
Expand Down
75 changes: 46 additions & 29 deletions src/Uno.UI/UI/Xaml/Controls/ListViewBase/NativeListViewBase.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Windows.UI.Core;
using Uno.UI.Controls;
using System.ComponentModel;
using Windows.UI.Input;
using Uno.Extensions.Specialized;
using Windows.UI.Xaml.Controls.Primitives;
using Uno.Logging;
Expand Down Expand Up @@ -81,34 +82,21 @@ public partial class NativeListViewBase : UICollectionView
#region Properties
new internal ListViewBaseSource Source
{
get { return base.Source as ListViewBaseSource; }
set
{
base.Source = value;
}
get => base.Source as ListViewBaseSource;
set => base.Source = value;
}

public Style ItemContainerStyle => XamlParent?.ItemContainerStyle;

public DataTemplate HeaderTemplate
{
get { return XamlParent?.HeaderTemplate; }
}


public DataTemplate FooterTemplate
{
get { return XamlParent?.FooterTemplate; }
}
public DataTemplate HeaderTemplate => XamlParent?.HeaderTemplate;

public DataTemplate FooterTemplate => XamlParent?.FooterTemplate;

public DataTemplateSelector ItemTemplateSelector => XamlParent?.ItemTemplateSelector;

internal bool NeedsReloadData => _needsReloadData;

internal CGPoint UpperScrollLimit { get { return (CGPoint)(ContentSize - Frame.Size); } }

internal UIElement.TouchesManager TouchesManager { get; /* readonly in int */ private set; }
#endregion

public GroupStyle GroupStyle => XamlParent?.GroupStyle.FirstOrDefault();
Expand All @@ -118,15 +106,10 @@ public DataTemplate FooterTemplate
internal IList<object> SelectedItems => XamlParent?.SelectedItems;

public ListViewSelectionMode SelectionMode => XamlParent?.SelectionMode ?? ListViewSelectionMode.None;
public object Header
{
get { return XamlParent?.ResolveHeaderContext(); }
}

public object Footer
{
get { return XamlParent?.ResolveFooterContext(); }
}
public object Header => XamlParent?.ResolveHeaderContext();

public object Footer => XamlParent?.ResolveFooterContext();

/// <summary>
/// Get all currently visible supplementary views.
Expand Down Expand Up @@ -178,8 +161,7 @@ private void Initialize()
RegisterClassForSupplementaryView(internalContainerType, ListViewFooterElementKindNS, ListViewFooterReuseIdentifier);
RegisterClassForSupplementaryView(internalContainerType, ListViewSectionHeaderElementKindNS, ListViewSectionHeaderReuseIdentifier);

DelaysContentTouches = true;
TouchesManager = UIElement.TouchesManager.GetOrCreate(this);
DelaysContentTouches = true; // cf. TouchesManager which can alter this!

ShowsHorizontalScrollIndicator = true;
ShowsVerticalScrollIndicator = true;
Expand Down Expand Up @@ -498,6 +480,8 @@ public override void LayoutSubviews()
}
}

internal Orientation ScrollOrientation => (CollectionViewLayout as VirtualizingPanelLayout)?.ScrollOrientation ?? Orientation.Vertical;

public ScrollBarVisibility HorizontalScrollBarVisibility
{
get
Expand Down Expand Up @@ -620,7 +604,7 @@ private UICollectionViewScrollPosition ConvertSnapPointsAlignmentToScrollPositio
return UICollectionViewScrollPosition.None;
}

var scrollDirection = (CollectionViewLayout as VirtualizingPanelLayout)?.ScrollOrientation ?? Orientation.Vertical;
var scrollDirection = ScrollOrientation;
var snapPointsAlignment = (CollectionViewLayout as VirtualizingPanelLayout)?.SnapPointsAlignment;

switch (scrollDirection)
Expand Down Expand Up @@ -666,7 +650,7 @@ private UICollectionViewScrollPosition ConvertScrollAlignmentForGroups(ScrollInt
this.Log().Warn("ScrollIntoViewAlignment.Default is not implemented");
}

var scrollDirection = (CollectionViewLayout as VirtualizingPanelLayout)?.ScrollOrientation ?? Orientation.Vertical;
var scrollDirection = ScrollOrientation;
switch (scrollDirection)
{
case Orientation.Horizontal:
Expand Down Expand Up @@ -769,5 +753,38 @@ public Thickness Padding
get { return NativeLayout.Padding; }
set { NativeLayout.Padding = value; }
}

#region Touches
private TouchesManager _touchesManager;

internal TouchesManager TouchesManager => _touchesManager ??= new NativeListViewBaseTouchesManager(this);

private class NativeListViewBaseTouchesManager : TouchesManager
{
private readonly NativeListViewBase _listView;

public NativeListViewBaseTouchesManager(NativeListViewBase listView)
{
_listView = listView;
}

/// <inheritdoc />
protected override bool CanConflict(GestureRecognizer.Manipulation manipulation)
=> manipulation.IsDragManipulation || _listView.ScrollOrientation switch
{
Orientation.Horizontal => manipulation.IsTranslateXEnabled,
Orientation.Vertical => manipulation.IsTranslateYEnabled,
_ => manipulation.IsTranslateXEnabled || manipulation.IsTranslateYEnabled
};

/// <inheritdoc />
protected override void SetCanDelay(bool canDelay)
=> _listView.DelaysContentTouches = canDelay;

/// <inheritdoc />
protected override void SetCanCancel(bool canCancel)
=> _listView.CanCancelContentTouches = canCancel;
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ internal NativeScrollContentPresenter(ScrollViewer scroller) : this()

public NativeScrollContentPresenter()
{
TouchesManager = new ScrollContentPresenterManipulationManager(this);
Scrolled += OnScrolled;
ViewForZoomingInScrollView = _ => Content as UIView;
DidZoom += OnZoom;
Expand Down Expand Up @@ -560,11 +559,12 @@ public override void TouchesCancelled(NSSet touches, UIEvent evt)
* On the UIElement this is defined by the ManipulationMode.
*/

internal UIElement.TouchesManager TouchesManager { get; }
private TouchesManager _touchesManager;
internal TouchesManager TouchesManager => _touchesManager ??= new NativeScrollContentPresenterManipulationManager(this);

private void UpdateDelayedTouches()
{
if (TouchesManager.Listeners == 0)
if ((_touchesManager?.Listeners ?? 0) == 0)
{
// This prevents unnecessary touch delays (which affects the pressed visual states of buttons) when user can't scroll.
var canScrollVertically = VerticalScrollBarVisibility != ScrollBarVisibility.Disabled && ContentSize.Height > Frame.Height;
Expand All @@ -577,20 +577,20 @@ private void UpdateDelayedTouches()
}
}

private class ScrollContentPresenterManipulationManager : UIElement.TouchesManager
private class NativeScrollContentPresenterManipulationManager : TouchesManager
{
private readonly NativeScrollContentPresenter _scrollPresenter;

public ScrollContentPresenterManipulationManager(NativeScrollContentPresenter scrollPresenter)
public NativeScrollContentPresenterManipulationManager(NativeScrollContentPresenter scrollPresenter)
{
_scrollPresenter = scrollPresenter;
}

/// <inheritdoc />
protected override bool CanConflict(GestureRecognizer.Manipulation manipulation)
=> _scrollPresenter.CanHorizontallyScroll && manipulation.IsTranslateXEnabled
|| _scrollPresenter.CanVerticallyScroll && manipulation.IsTranslateYEnabled
|| manipulation.IsDragManipulation; // This will actually always be false when CanConflict is being invoked in current setup.
|| _scrollPresenter.CanVerticallyScroll && manipulation.IsTranslateYEnabled
|| manipulation.IsDragManipulation; // This will actually always be false when CanConflict is being invoked in current setup.

/// <inheritdoc />
protected override void SetCanDelay(bool canDelay)
Expand All @@ -600,6 +600,6 @@ protected override void SetCanDelay(bool canDelay)
protected override void SetCanCancel(bool canCancel)
=> _scrollPresenter.CanCancelContentTouches = canCancel;
}
#endregion
#endregion
}
}
Loading

0 comments on commit 60d4a34

Please sign in to comment.