summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.Tizen/Renderers
diff options
context:
space:
mode:
authorSeungkeun Lee <sngn.lee@samsung.com>2017-07-11 15:25:25 +0900
committerKangho Hur <kangho.hur@samsung.com>2017-10-23 13:34:39 +0900
commit4dcc3c1f7f9fd6f39c3829711b61c8f0305e1c34 (patch)
tree1aab17710fac7ea3659f7a16eabdc4193c971572 /Xamarin.Forms.Platform.Tizen/Renderers
parentae5956e20d1ea484f260c3245bd93e94129060e3 (diff)
downloadxamarin-forms-4dcc3c1f7f9fd6f39c3829711b61c8f0305e1c34.tar.gz
xamarin-forms-4dcc3c1f7f9fd6f39c3829711b61c8f0305e1c34.tar.bz2
xamarin-forms-4dcc3c1f7f9fd6f39c3829711b61c8f0305e1c34.zip
Use Scroller as a base for CarouselPageRenderer
Modifies the CarouselPageRenderer so that it utilizes a Scroller as its base widget. This allows for smooth pages scrolling (i.e. the pages do not switch instantly), following the material design principles. Change-Id: I9967576a9d51515320975bf19bf5703fb8f8b440 Signed-off-by: Adam Szczerbiak <a.szczerbiak@samsung.com>
Diffstat (limited to 'Xamarin.Forms.Platform.Tizen/Renderers')
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs277
1 files changed, 110 insertions, 167 deletions
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs
index 469d1a91..4f7a8f65 100644
--- a/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs
@@ -1,7 +1,6 @@
using System;
using ElmSharp;
-using EColor = ElmSharp.Color;
-using ERectangle = ElmSharp.Rectangle;
+using ESize = ElmSharp.Size;
namespace Xamarin.Forms.Platform.Tizen
{
@@ -10,37 +9,14 @@ namespace Xamarin.Forms.Platform.Tizen
/// </summary>
public class CarouselPageRenderer : VisualElementRenderer<CarouselPage>, IVisualElementRenderer
{
- /// <summary>
- /// The minimum length of a swipe to be recognized as a page switching command, in screen pixels unit.
- /// </summary>
- public static readonly double s_minimumSwipeLengthX = 200.0;
+ Box _outterLayout;
+ Box _innerContainer;
+ Scroller _scroller;
- // Different levels of "difficulty" in making a valid swipe gesture, determined by a maximum absolute value
- // of an angle between a line formed by the swipe gesture and the horizontal axis, in arc degrees:
- public static readonly double s_challengeEasyArcDegrees = 25.0;
- public static readonly double s_challengeComfortableArcDegrees = 20.0;
- public static readonly double s_challengeStandardArcDegrees = 15.0;
- public static readonly double s_challengeHardArcDegrees = 10.0;
+ int _pageIndex = 0;
- /// <summary>
- /// The maximum allowed angle between a line formed by the swipe gesture and the horizontal axis, in arc degrees.
- /// The gesture will be recognized as a page switching command if its angle does not exceed this value.
- /// </summary>
- public static readonly double s_thresholdSwipeArcDegrees = s_challengeComfortableArcDegrees;
-
- /// <summary>
- /// The tangent of a maximum allowed angle between the swipe line and the horizontal axis.
- /// </summary>
- public static readonly double s_thresholdSwipeTangent = Math.Tan(s_thresholdSwipeArcDegrees * (Math.PI / 180.0));
-
- // A master container for the entire widget:
- protected Box _box;
-
- // Used for grabbing gestures over the entire screen, even if Page is smaller than it:
- protected ERectangle _filler;
-
- protected GestureLayer _gestureLayer;
- protected EvasObject _page;
+ int _changedByScroll = 0;
+ ESize _layoutBound;
/// <summary>
/// Invoked whenever the CarouselPage element has been changed in Xamarin.
@@ -50,56 +26,52 @@ namespace Xamarin.Forms.Platform.Tizen
{
if (NativeView == null)
{
- // Creates an overlaying box which serves as a container
- // for both page and a gesture handling layer:
- _box = new Box(Forms.Context.MainWindow)
+ //Create box that holds toolbar and selected content
+ _outterLayout = new Box(Forms.Context.MainWindow)
{
+ AlignmentX = -1,
+ AlignmentY = -1,
+ WeightX = 1,
+ WeightY = 1,
IsHorizontal = false,
};
- _box.SetAlignment(-1, -1);
- _box.SetWeight(1, 1);
- _box.Show();
-
- // Disallows the Box to lay out its contents. They will be laid out manually,
- // because the page has to overlay the conformant rectangle. By default
- // Box will lay its contents in a stack. Applying an empty method disables it:
- _box.SetLayoutCallback(() => {
- ResizeContentsToFullScreen();
- });
-
- // Creates a Rectangle used for ensuring that the gestures will get recognized:
- _filler = new ERectangle(Forms.Context.MainWindow)
- {
- Color = EColor.Transparent,
- };
- _filler.SetAlignment(-1, -1);
- _filler.SetWeight(1, 1);
- _filler.Show();
- _box.PackEnd(_filler);
-
- // Creates a GestureLayer used for swipe gestures recognition and attaches it to the Box:
- _gestureLayer = new GestureLayer(_box);
- _gestureLayer.Attach(_box);
- AddLineGestureHandler();
-
- SetNativeControl(_box);
+ _outterLayout.Show();
+
+ _scroller = new Scroller(Forms.Context.MainWindow);
+ _scroller.PageScrolled += OnScrolled;
+
+ // Disables the visibility of the scrollbar in both directions:
+ _scroller.HorizontalScrollBarVisiblePolicy = ElmSharp.ScrollBarVisiblePolicy.Invisible;
+ _scroller.VerticalScrollBarVisiblePolicy = ElmSharp.ScrollBarVisiblePolicy.Invisible;
+ // Sets the limit of scroll to one page maximum:
+ _scroller.HorizontalPageScrollLimit = 1;
+ _scroller.SetPageSize(1.0, 1.0);
+ _scroller.SetAlignment(-1, -1);
+ _scroller.SetWeight(1.0, 1.0);
+ _scroller.Show();
+
+ _innerContainer = new Box(Forms.Context.MainWindow);
+ _innerContainer.SetLayoutCallback(OnInnerLayoutUpdate);
+ _innerContainer.SetAlignment(-1, -1);
+ _innerContainer.SetWeight(1.0, 1.0);
+ _innerContainer.Show();
+ _scroller.SetContent(_innerContainer);
+
+ _outterLayout.PackEnd(_scroller);
+ SetNativeControl(_outterLayout);
}
if (e.OldElement != null)
{
e.OldElement.CurrentPageChanged -= OnCurrentPageChanged;
+ e.OldElement.PagesChanged -= OnPagesChanged;
}
if (e.NewElement != null)
{
Element.CurrentPageChanged += OnCurrentPageChanged;
- }
-
- // If pages have been added to the Xamarin widget and the user has not explicitly
- // marked one of them to be displayed, displays the first one:
- if (_page == null && Element.Children.Count > 0)
- {
- DisplayPage(Element.Children[0]);
+ Element.PagesChanged += OnPagesChanged;
+ UpdateCarouselContent();
}
base.OnElementChanged(e);
@@ -116,20 +88,40 @@ namespace Xamarin.Forms.Platform.Tizen
if (Element != null)
{
Element.CurrentPageChanged -= OnCurrentPageChanged;
+ Element.PagesChanged -= OnPagesChanged;
}
- if (_box != null)
- {
- // Unpacks the page from the box to prevent it from being disposed of prematurely:
- _box.UnPack(_page);
+ _innerContainer = null;
+ _scroller = null;
+ }
+ base.Dispose(disposing);
+ }
- _box.Unrealize();
- _box = null;
- }
+ void OnInnerLayoutUpdate()
+ {
+ if (_layoutBound == _innerContainer.Geometry.Size)
+ return;
+
+ _layoutBound = _innerContainer.Geometry.Size;
+
+ int baseX = _innerContainer.Geometry.X;
+ Rect bound = _scroller.Geometry;
+ int index = 0;
+ foreach (var page in Element.Children)
+ {
+ var nativeView = Platform.GetRenderer(page).NativeView;
+ bound.X = baseX + index * bound.Width;
+ nativeView.Geometry = bound;
+ index++;
}
+ _innerContainer.MinimumWidth = Element.Children.Count * bound.Width;
- base.Dispose(disposing);
+ if (_scroller.HorizontalPageIndex != _pageIndex)
+ {
+ _scroller.ScrollTo(_pageIndex, 0, false);
+ }
}
+
/// <summary>
/// Handles the process of switching between the displayed pages.
/// </summary>
@@ -137,121 +129,72 @@ namespace Xamarin.Forms.Platform.Tizen
/// <param name="ea">Additional arguments to the event handler</param>
void OnCurrentPageChanged(object sender, EventArgs ea)
{
- if (_page != null)
+ if (IsChangedByScroll())
+ return;
+
+ if (Element.CurrentPage != Element.Children[_pageIndex])
{
- _page.Hide();
- _box.UnPack(_page);
+ var previousPageIndex = _pageIndex;
+ _pageIndex = Element.Children.IndexOf(Element.CurrentPage);
+ if (previousPageIndex != _pageIndex)
+ {
+ // notify disappearing/appearing pages and scroll to the requested page
+ (Element.Children[previousPageIndex] as IPageController)?.SendDisappearing();
+ _scroller.ScrollTo(_pageIndex, 0, false);
+ (Element.CurrentPage as IPageController)?.SendAppearing();
+ }
}
-
- DisplayPage(Element.CurrentPage);
- ResizeContentsToFullScreen();
}
/// <summary>
- /// Gets the index of the currently displayed page in Element.Children collection.
+ /// Handles the PageScrolled event of the _scroller.
/// </summary>
- /// <returns>An int value representing the index of the page currently displayed,
- /// or -1 if no page is being displayed currently.</returns>
- int GetCurrentPageIndex()
+ void OnScrolled(object sender, EventArgs e2)
{
- int index = -1;
- for (int k = 0; k < Element.Children.Count; ++k)
+ _changedByScroll++;
+ int previousIndex = _pageIndex;
+ _pageIndex = _scroller.HorizontalPageIndex;
+
+ if (previousIndex != _pageIndex)
{
- if (Element.Children[k] == Element.CurrentPage)
- {
- index = k;
- break;
- }
+ (Element.CurrentPage as IPageController)?.SendDisappearing();
+ Element.CurrentPage = Element.Children[_pageIndex];
+ (Element.CurrentPage as IPageController)?.SendAppearing();
}
- return index;
+ _changedByScroll--;
}
/// <summary>
- /// Resizes the widget's contents to utilize all the available screen space.
+ /// Updates the content of the carousel.
/// </summary>
- void ResizeContentsToFullScreen()
+ void UpdateCarouselContent()
{
- // Box's geometry should match Forms.Context.MainWindow's geometry
- // minus the space occupied by the top toolbar.
- // Applies Box's geometry to both displayed page and conformant rectangle:
- _filler.Geometry = _page.Geometry = _box.Geometry;
+ _innerContainer.UnPackAll();
+ foreach (var page in Element.Children)
+ {
+ EvasObject nativeView = Platform.GetOrCreateRenderer(page).NativeView;
+ _innerContainer.PackEnd(nativeView);
+ // if possible, make the subpage focusable, this ensures that there is something
+ // to focus on all pages and prevents the scroller from auto-scrolling to focused widget
+ (nativeView as ElmSharp.Widget)?.AllowFocus(true);
+ }
+ _pageIndex = Element.Children.IndexOf(Element.CurrentPage);
+ _scroller.ScrollTo(_pageIndex, 0, false);
}
/// <summary>
- /// Adds the feature of recognizing swipes to the GestureLayer.
+ /// Handles the notifications about content sub-page changes.
/// </summary>
- void AddLineGestureHandler()
+ void OnPagesChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
- _gestureLayer.SetLineCallback(GestureLayer.GestureState.End, (line) => {
- double horizontalDistance = line.X2 - line.X1;
- double verticalDistance = line.Y2 - line.Y1;
-
- // Determines whether the movement is long enough to be considered a swipe:
- bool isLongEnough = (Math.Abs(horizontalDistance) >= s_minimumSwipeLengthX);
-
- // Determines whether the movement is horizontal enough to be considered as a swipe:
- // The swipe arc's tangent value (v/h) needs to be lesser than or equal to the threshold value.
- // This approach allows for getting rid of computationally heavier atan2() function.
- double angleTangent = Math.Abs(verticalDistance) / horizontalDistance;
- bool isDirectionForward = (angleTangent < 0);
-
- // Determines whether the movement has been recognized as a valid swipe:
- bool isSwipeMatching = (isLongEnough && (Math.Abs(angleTangent) <= s_thresholdSwipeTangent));
-
- if (isSwipeMatching)
- {
- // TODO: Unsure whether changes made via ItemsSource/ItemTemplate properties will be handled correctly this way.
- // If not, it should be implemented in another method.
- if (isDirectionForward)
- {
- // Tries to switch the page to the next one:
- int currentPageIndex = GetCurrentPageIndex();
- if (currentPageIndex < Element.Children.Count - 1)
- {
- // Sets the current page to the next one:
- Element.CurrentPage = Element.Children[currentPageIndex + 1];
- }
- else
- {
- // Reacts to the case of forward-swiping when the last page is already being displayed:
- Log.Debug("CarouselPage: Displaying the last page already - can not revolve further.");
-
- // Note (TODO): Once we have a more sophisticated renderer able to e.g. display the animation
- // of revolving Pages or at least indicate current overall position, some visual feedback
- // should be provided here for the user who has haplessly tried to access a nonexistent page.
- }
- }
- else
- {
- // Tries to switch the page to the previous one:
- int currentPageIndex = GetCurrentPageIndex();
- if (currentPageIndex > 0)
- {
- // Sets the current page to the previous one:
- Element.CurrentPage = Element.Children[currentPageIndex - 1];
- }
- else
- {
- // Reacts to the case of backward-swiping when the first page is already being displayed:
- Log.Debug("CarouselPage: The first page is already being displayed - can not revolve further.");
-
- // Note (TODO): (The same as in case of scrolling forwards)
- }
- }
- }
- });
+ UpdateCarouselContent();
}
- void DisplayPage(ContentPage p)
+ bool IsChangedByScroll()
{
- _page = Platform.GetOrCreateRenderer(p).NativeView;
- _page.SetAlignment(-1, -1);
- _page.SetWeight(1, 1);
- _page.Show();
- _box.PackEnd(_page);
+ return _changedByScroll > 0;
}
-
}
}