diff options
Diffstat (limited to 'Xamarin.Forms.Platform.Tizen')
-rw-r--r-- | Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs | 277 |
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; } - } } |