Skip to content

Commit

Permalink
Nested CardsView (#357)
Browse files Browse the repository at this point in the history
* approach 1

* approach 2

* approach 2

* Fixed pan and sample

* cleaned code

* #356
  • Loading branch information
AndreiMisiukevich authored Oct 3, 2020
1 parent c37eaa0 commit 9d7848a
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 181 deletions.
30 changes: 7 additions & 23 deletions PanCardView.Droid/CardsViewRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,9 @@ namespace PanCardView.Droid
[Preserve(AllMembers = true)]
public class CardsViewRenderer : VisualElementRenderer<CardsView>
{
private static readonly Random _randomGenerator = new Random();
private static Guid? _lastTouchHandlerId;

public static bool IsTouchHandled { get; private set; }

public static int SwipeThreshold { get; set; } = 100;
public static int SwipeVelocityThreshold { get; set; } = 1200;

private int _gestureId;
private Guid _elementId;
private bool _panStarted;
private float? _startX;
private float? _startY;
Expand All @@ -41,20 +34,15 @@ public override bool OnInterceptTouchEvent(MotionEvent ev)
{
DetectEvent(ev);

if (!Element.IsPanInteractionEnabled || Element.ShouldThrottlePanInteraction)
if (Element.IsPanControllingdByChild || !Element.IsPanInteractionEnabled || Element.ShouldThrottlePanInteraction)
{
base.OnInterceptTouchEvent(ev);
return false;
}

if (ev.ActionMasked == MotionEventActions.Move)
{
if (_lastTouchHandlerId.HasValue && _lastTouchHandlerId != _elementId)
{
return false;
}

return SetIsTouchHandled(GetTotalX(ev), GetTotalY(ev));
return CheckTouchHandled(GetTotalX(ev), GetTotalY(ev));
}

HandleDownUpEvents(ev);
Expand All @@ -68,7 +56,7 @@ public override bool OnTouchEvent(MotionEvent e)
{
var xDelta = GetTotalX(e);
var yDelta = GetTotalY(e);
SetIsTouchHandled(xDelta, yDelta);
CheckTouchHandled(xDelta, yDelta);

if ((Abs(yDelta) <= Abs(xDelta) && Element.IsHorizontalOrientation) ||
(Abs(yDelta) >= Abs(xDelta) && !Element.IsHorizontalOrientation))
Expand All @@ -87,7 +75,6 @@ protected override void OnElementChanged(ElementChangedEventArgs<CardsView> e)
if (e.NewElement != null)
{
_panStarted = false;
_elementId = Guid.NewGuid();
}
}

Expand All @@ -112,7 +99,7 @@ private void DetectEvent(MotionEvent ev)
}
}

private bool SetIsTouchHandled(float xDelta, float yDelta)
private bool CheckTouchHandled(float xDelta, float yDelta)
{
var xDeltaAbs = Abs(xDelta);
var yDeltaAbs = Abs(yDelta);
Expand All @@ -130,7 +117,7 @@ private bool SetIsTouchHandled(float xDelta, float yDelta)

Element.IsUserInteractionRunning |= isHandled;
Parent?.RequestDisallowInterceptTouchEvent(isHandled);
return IsTouchHandled = isHandled;
return isHandled;
}

private void HandleDownUpEvents(MotionEvent ev)
Expand All @@ -144,6 +131,7 @@ private void HandleUpCancelEvent(MotionEvent ev)
var action = ev.ActionMasked;
var isUpAction = action == MotionEventActions.Up;
var isCancelAction = action == MotionEventActions.Cancel;

if (!_panStarted || (!isUpAction && !isCancelAction))
{
return;
Expand All @@ -153,10 +141,8 @@ private void HandleUpCancelEvent(MotionEvent ev)
var yDelta = GetTotalY(ev);
UpdatePan(isUpAction ? GestureStatus.Completed : GestureStatus.Canceled, xDelta, yDelta);
_panStarted = false;
_lastTouchHandlerId = null;

Parent?.RequestDisallowInterceptTouchEvent(false);
IsTouchHandled = false;

_startX = null;
_startY = null;
Expand All @@ -168,13 +154,11 @@ private void HandleDownEvent(MotionEvent ev)
{
return;
}
_gestureId = _randomGenerator.Next();
_startX = ev.GetX();
_startY = ev.GetY();

UpdatePan(GestureStatus.Started);
_panStarted = true;
_lastTouchHandlerId = _elementId;
}

private void UpdatePan(GestureStatus status, double totalX = 0, double totalY = 0)
Expand All @@ -183,7 +167,7 @@ private void UpdatePan(GestureStatus status, double totalX = 0, double totalY =
private void OnSwiped(ItemSwipeDirection swipeDirection) => Element.OnSwiped(swipeDirection);

private PanUpdatedEventArgs GetPanUpdatedEventArgs(GestureStatus status, double totalX = 0, double totalY = 0)
=> new PanUpdatedEventArgs(status, _gestureId, totalX, totalY);
=> new PanUpdatedEventArgs(status, 0, totalX, totalY);

private float GetTotalX(MotionEvent ev) => (ev.GetX() - _startX.GetValueOrDefault()) / Context.Resources.DisplayMetrics.Density;

Expand Down
137 changes: 125 additions & 12 deletions PanCardView/CardsView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ public event NotifyCollectionChangedEventHandler ViewsInUseCollectionChanged

private AnimationDirection _currentBackAnimationDirection;

private Optional<CardsView> _parentCardsViewOption;
private Optional<CardsView> _parentCardsViewTouchHandlerOption;

private int _viewsChildrenCount;
private bool _isPanStarted;
private bool _isOppositePanDirectionIssueResolved;
Expand Down Expand Up @@ -233,6 +236,9 @@ internal double RealMoveDistance

protected virtual int DefaultDesiredMaxChildrenCount => 7;

[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsPanControllingdByChild { get; set; }

public View CurrentView { get; private set; }

public IReadOnlyList<View> ViewsInUseCollection => _viewsInUseSet.Views;
Expand Down Expand Up @@ -565,7 +571,9 @@ public void OnPanUpdated(object sender, PanUpdatedEventArgs e)
[EditorBrowsable(EditorBrowsableState.Never)]
public void OnPanUpdated(PanUpdatedEventArgs e)
{
if (ItemsCount <= 0 || !IsPanInteractionEnabled)
var statusType = e.StatusType;

if (IsPanControllingdByChild || ShouldParentHandleTouch(statusType))
{
return;
}
Expand All @@ -574,25 +582,47 @@ public void OnPanUpdated(PanUpdatedEventArgs e)
var oppositeDirectionDiff = e.TotalY;
if (!IsHorizontalOrientation)
{
var tempDiff = diff;
diff = oppositeDirectionDiff;
oppositeDirectionDiff = tempDiff;
diff = e.TotalY;
oppositeDirectionDiff = e.TotalX;
}
switch (e.StatusType)

if (ItemsCount <= 0 || !IsPanInteractionEnabled)
{
switch(statusType)
{
case GestureStatus.Started:
SetParentCardsViewOption();
SetupBackViews();
ResetActiveInactiveBackViews(diff);
SetParentTouchHandlerIfNeeded(statusType);
break;
case GestureStatus.Canceled:
case GestureStatus.Completed:
ClearParentCardsViewOption();
break;
}
return;
}

SetParentCardsViewOption();
switch (statusType)
{
case GestureStatus.Started:
OnTouchStarted();
return;
case GestureStatus.Running:
OnTouchChanged(diff, oppositeDirectionDiff);
SetParentTouchHandlerIfNeeded(statusType);
return;
case GestureStatus.Canceled:
case GestureStatus.Completed:
if (Device.RuntimePlatform == Device.Android)
{
OnTouchChanged(diff, oppositeDirectionDiff, true);
SetParentTouchHandlerIfNeeded(statusType);
}
OnTouchEnded();
ClearParentCardsViewOption();
return;
}
}
Expand Down Expand Up @@ -620,7 +650,17 @@ public async void OnSwiped(ItemSwipeDirection swipeDirection)
var haveItems = (isLeftSwiped && NextViews.Any()) || (!isLeftSwiped && PrevViews.Any());
var isAndroid = Device.RuntimePlatform == Device.Android;

if (IsPanSwipeEnabled && haveItems && isAndroid)
var parentCardsViewOption = new Optional<CardsView>(FindParentElement<CardsView>());
if (!haveItems && parentCardsViewOption?.Value != null)
{
if (!isAndroid || !IsPanSwipeEnabled)
{
parentCardsViewOption.Value.OnSwiped(swipeDirection);
}
return;
}

if (IsPanSwipeEnabled && isAndroid && haveItems)
{
return;
}
Expand Down Expand Up @@ -865,7 +905,7 @@ protected override void OnSizeAllocated(double width, double height)

lock (_sizeChangedLocker)
{
var parent = FindParentPage();
var parent = FindParentElement<Page>();
if (parent == null)
{
return;
Expand Down Expand Up @@ -1281,9 +1321,82 @@ private async void OnTouchEnded()
_interactions.Remove(gestureId);
}

private bool ShouldParentHandleTouch(GestureStatus satusType)
{
var result = _parentCardsViewTouchHandlerOption?.Value != null;
switch(satusType)
{
case GestureStatus.Canceled:
case GestureStatus.Completed:
ClearParentCardsViewOption();
break;
}
return result;
}

private void SetParentTouchHandlerIfNeeded(GestureStatus statusType)
{
if (CurrentDiff == 0 || _parentCardsViewTouchHandlerOption != null)
{
return;
}

_parentCardsViewTouchHandlerOption = CurrentBackViews.Any()
? new Optional<CardsView>(null)
: _parentCardsViewOption;

if (_parentCardsViewTouchHandlerOption.Value == null)
{
return;
}

_parentCardsViewOption.Value.IsPanControllingdByChild = false;

if (statusType == GestureStatus.Running)
{
OnTouchChanged(0, 0, true);
}

switch(statusType)
{
case GestureStatus.Started:
case GestureStatus.Running:
OnTouchEnded();
break;
}
}

private void SetParentCardsViewOption()
{
if (_parentCardsViewOption != null)
{
return;
}

_parentCardsViewOption = new Optional<CardsView>(FindParentElement<CardsView>());

if (_parentCardsViewOption.Value == null)
{
return;
}

_parentCardsViewOption.Value.IsPanControllingdByChild = true;
}

private void ClearParentCardsViewOption()
{
if (_parentCardsViewOption?.Value != null)
{
_parentCardsViewOption.Value.IsPanControllingdByChild = false;
}

IsPanControllingdByChild = false;
_parentCardsViewTouchHandlerOption = null;
_parentCardsViewOption = null;
}

private bool CheckInteractionDelay()
=>
CurrentView != null &&
=> CurrentView != null &&
IsUserInteractionEnabled &&
Abs((DateTime.UtcNow - _lastPanTime).TotalMilliseconds) >= UserInteractionDelay &&
(!IsUserInteractionInCourse || (_animationTask?.IsCompleted ?? true));
Expand Down Expand Up @@ -1853,14 +1966,14 @@ private void PerformUWPFrontViewProcessorHandlePanChanged(double value, Animatio
});
}

private Page FindParentPage()
private TElement FindParentElement<TElement>() where TElement : VisualElement
{
var parent = Parent;
while (parent != null && !(parent is Page))
while (parent != null && !(parent is TElement))
{
parent = parent.Parent;
}
return parent as Page;
return parent as TElement;
}

private bool CheckIsProcessingView(View view)
Expand Down
1 change: 1 addition & 0 deletions PanCardView/PanCardView.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard1.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Forms" Version="4.6.0.847" />
Expand Down
11 changes: 11 additions & 0 deletions PanCardView/Utility/Optional.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

namespace PanCardView.Utility
{
public sealed class Optional<TValue> where TValue : class
{
public Optional(TValue value)
=> Value = value;

public TValue Value { get; }
}
}
14 changes: 7 additions & 7 deletions PanCardViewSample/PanCardViewSample/PanCardViewSample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ public StartPage()
this.Navigation.PushAsync(new CoverFlowSampleXamlView());
};

var toCarouselScrollBtn = new Button { Text = "CarouselView scroll" };
toCarouselScrollBtn.Clicked += (sender, e) =>
var toCarouselNestedBtn = new Button { Text = "CarouselView Nested" };
toCarouselNestedBtn.Clicked += (sender, e) =>
{
this.Navigation.PushAsync(new CarouselSampleSrollView());
this.Navigation.PushAsync(new CarouselSampleNestedXamlView());
};

var toCarouselDoubleBtn = new Button { Text = "CarouselView DoubleView" };
toCarouselDoubleBtn.Clicked += (sender, e) =>
var toCarouselScrollBtn = new Button { Text = "CarouselView scroll" };
toCarouselScrollBtn.Clicked += (sender, e) =>
{
this.Navigation.PushAsync(new CarouselSampleDoubleView());
this.Navigation.PushAsync(new CarouselSampleSrollView());
};

var toCarouselNoTemplateBtn = new Button { Text = "CarouselView No template" };
Expand Down Expand Up @@ -87,8 +87,8 @@ public StartPage()
toCarouselXamlBtn,
toCoverFlowBtn,
toCubeBtn,
toCarouselNestedBtn,
toCarouselScrollBtn,
toCarouselDoubleBtn,
toCarouselNoTemplateBtn,
toCarouselListBtn,
toCarouselEmbBtn
Expand Down
Loading

0 comments on commit 9d7848a

Please sign in to comment.