From 8042ec20677a1a34a26e8a30ffbfddb92c8bf8f2 Mon Sep 17 00:00:00 2001 From: neostom432 <31119276+neostom432@users.noreply.github.com> Date: Thu, 11 Jun 2020 14:28:06 +0900 Subject: [PATCH] [NUI] Add GridRecycleLayotManager (#1685) Grid Layout for RecyclerView. When user set Orientation, GridRecycleLayoutManager will lay out items using only one factor between Row and Column. Horizontal => Row Vertical => Column --- .../RecyclerView/GridRecycleLayoutManager.cs | 254 +++++++++++++++++++++ ...outManager.cs => LinearRecycleLayoutManager.cs} | 13 +- .../{LayoutManager.cs => RecycleLayoutManager.cs} | 27 ++- .../Controls/RecyclerView/RecyclerView.cs | 100 +++++++- .../Controls/ScrollableBase.cs | 2 + .../src/internal/FishEyeLayoutManager.cs | 9 +- src/Tizen.NUI.Wearable/src/public/WearableList.cs | 35 ++- 7 files changed, 418 insertions(+), 22 deletions(-) create mode 100644 src/Tizen.NUI.Components/Controls/RecyclerView/GridRecycleLayoutManager.cs rename src/Tizen.NUI.Components/Controls/RecyclerView/{LinearLayoutManager.cs => LinearRecycleLayoutManager.cs} (93%) rename src/Tizen.NUI.Components/Controls/RecyclerView/{LayoutManager.cs => RecycleLayoutManager.cs} (82%) diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/GridRecycleLayoutManager.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/GridRecycleLayoutManager.cs new file mode 100644 index 0000000..bf995b0 --- /dev/null +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/GridRecycleLayoutManager.cs @@ -0,0 +1,254 @@ +/* Copyright (c) 2020 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +using System; +using Tizen.NUI.BaseComponents; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Tizen.NUI.Components +{ + /// + /// [Draft] This class implements a grid box layout. + /// + /// 8 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public class GridRecycleLayoutManager : RecycleLayoutManager + { + private int rows = 1; + + /// + /// [draft ]Get/Set the number of rows in the grid + /// + /// 8 + // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API) + [EditorBrowsable(EditorBrowsableState.Never)] + public int Rows + { + get + { + return rows; + } + set + { + rows = value; + + if (Container != null) + { + Layout(mPrevScrollPosition); + } + } + } + + private int columns = 1; + + + /// + /// [Draft] Get/Set the number of columns in the grid + /// + /// 8 + // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API) + [EditorBrowsable(EditorBrowsableState.Never)] + public int Columns + { + get + { + return columns; + } + set + { + columns = value; + + if (Container != null) + { + Layout(mPrevScrollPosition); + } + } + } + + private int firstVisibleItemIndex = -1; + private int lastVisibleItemIndex = -1; + + private bool IsItemVisible(float scrollPosition, RecycleItem item) + { + bool result = false; + View list = Container.GetParent() as View; + + Vector2 visibleArea = new Vector2(Math.Abs(scrollPosition), + Math.Abs(scrollPosition) + (LayoutOrientation == Orientation.Horizontal ? + list.Size.Width : list.Size.Height) + ); + + float firstCheckPoint = LayoutOrientation == Orientation.Horizontal ? item.Position.X : item.Position.Y; + float secondCheckPoint = LayoutOrientation == Orientation.Horizontal ? + firstCheckPoint + item.Size.Width : + firstCheckPoint + item.Size.Height; + + result = (firstCheckPoint >= visibleArea.X && firstCheckPoint <= visibleArea.Y) || (secondCheckPoint >= visibleArea.X && secondCheckPoint <= visibleArea.Y); + + return result; + } + + /// + /// This is called to find out how much container size can be. + /// + /// 8 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public override float CalculateLayoutOrientationSize() + { + float orientationFactor = LayoutOrientation == Orientation.Horizontal ? Rows : Columns; + return mStepSize * (int)Math.Ceiling((double)DataCount / (double)orientationFactor); + } + + + /// + /// This is called to find out where items are lain out according to current scroll position. + /// + /// Scroll position which is calculated by ScrollableBase + /// 8 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + public override void Layout(float scrollPosition) + { + int itemInGroup = LayoutOrientation == Orientation.Horizontal ? Rows : Columns; + firstVisibleItemIndex = -1; + lastVisibleItemIndex = -1; + + RecycleItem previousItem = null; + + for (int i = 0; i < Container.Children.Count; i++) + { + RecycleItem item = Container.Children[i] as RecycleItem; + + if (previousItem != null) + { + item.Position = LayoutOrientation == Orientation.Horizontal ? + new Position( + (i % itemInGroup == 0 ? + previousItem.Position.X + (previousItem.CurrentSize.Width != 0 ? + previousItem.CurrentSize.Width : + previousItem.Size.Width) : + previousItem.Position.X), + (i % itemInGroup == 0 ? + 0 : + previousItem.PositionY + (previousItem.CurrentSize.Height != 0 ? + previousItem.CurrentSize.Height : + previousItem.Size.Height)) + ) : + new Position( + (i % itemInGroup == 0 ? + 0 : + previousItem.PositionX + (previousItem.CurrentSize.Width != 0 ? + previousItem.CurrentSize.Width : + previousItem.Size.Width)), + (i % itemInGroup == 0 ? + previousItem.Position.Y + (previousItem.CurrentSize.Height != 0 ? + previousItem.CurrentSize.Height : + previousItem.Size.Height) : + previousItem.Position.Y) + ); + } + + bool isVisible = IsItemVisible(scrollPosition, item); + + if (isVisible) + { + firstVisibleItemIndex = firstVisibleItemIndex == -1 ? i : firstVisibleItemIndex; + lastVisibleItemIndex = i; + } + + previousItem = item; + } + + if (mStepSize == 0) + { + mStepSize = LayoutOrientation == Orientation.Horizontal ? ItemSize.Width : ItemSize.Height; + } + } + + + /// + /// This is called to find out which items should be recycled according to current scroll position. + /// + /// Scroll position which is calculated by ScrollableBase + /// List of RecycleItems which should be recycled. + /// 8 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + public override List Recycle(float scrollPosition) + { + List result = new List(); + bool checkFront = (mPrevScrollPosition - scrollPosition) > 0; + + int itemInGroup = LayoutOrientation == Orientation.Horizontal ? Rows : Columns; + + if (checkFront) + { + int currentGroupNum = (int)(firstVisibleItemIndex / itemInGroup) + 1; + + if (currentGroupNum > 2) + { + // Too many item is in front!!! move first item to back!!!! + for (int i = 0; i < itemInGroup; i++) + { + RecycleItem target = Container.Children[0] as RecycleItem; + target.DataIndex = target.DataIndex + Container.Children.Count; + target.SiblingOrder = Container.Children.Count - 1; + + result.Add(target); + } + } + } + else + { + int currentGroupNum = (int)(lastVisibleItemIndex / itemInGroup) + 1; + + if (currentGroupNum < (int)(Container.Children.Count / itemInGroup) - 3) + { + for (int i = 0; i < itemInGroup; i++) + { + RecycleItem prevFirstItem = Container.Children[itemInGroup] as RecycleItem; + + RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem; + target.Position = new Position( + LayoutOrientation == Orientation.Horizontal ? (prevFirstItem.Position.X - target.Size.Width) : prevFirstItem.Position.X, + LayoutOrientation == Orientation.Horizontal ? prevFirstItem.Position.Y : (prevFirstItem.Position.Y - target.Size.Height) + ); + target.DataIndex = target.DataIndex - Container.Children.Count; + target.SiblingOrder = 0; + + result.Add(target); + } + } + } + + + mPrevScrollPosition = scrollPosition; + + return result; + } + + /// + /// Adjust scrolling position by own scrolling rules. + /// + /// Scroll position which is calculated by ScrollableBase + /// 8 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + public override float CalculateCandidateScrollPosition(float scrollPosition) + { + return scrollPosition; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/LinearLayoutManager.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/LinearRecycleLayoutManager.cs similarity index 93% rename from src/Tizen.NUI.Components/Controls/RecyclerView/LinearLayoutManager.cs rename to src/Tizen.NUI.Components/Controls/RecyclerView/LinearRecycleLayoutManager.cs index c088156..c40da45 100644 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/LinearLayoutManager.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/LinearRecycleLayoutManager.cs @@ -26,7 +26,7 @@ namespace Tizen.NUI.Components /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API [EditorBrowsable(EditorBrowsableState.Never)] - public class LinearListLayoutManager : LayoutManager + public class LinearRecycleLayoutManager : RecycleLayoutManager { private int firstVisibleItemIndex = -1; private int lastVisibleItemIndex = -1; @@ -108,6 +108,17 @@ namespace Tizen.NUI.Components } /// + /// This is called to find out how much container size can be. + /// + /// 8 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public override float CalculateLayoutOrientationSize() + { + return mStepSize * DataCount; + } + + /// /// This is called to find out which items should be recycled according to current scroll position. /// /// Scroll position which is calculated by ScrollableBase diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/LayoutManager.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/RecycleLayoutManager.cs similarity index 82% rename from src/Tizen.NUI.Components/Controls/RecyclerView/LayoutManager.cs rename to src/Tizen.NUI.Components/Controls/RecyclerView/RecycleLayoutManager.cs index 2ca6295..af4c4db 100644 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/LayoutManager.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/RecycleLayoutManager.cs @@ -26,7 +26,7 @@ namespace Tizen.NUI.Components /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API [EditorBrowsable(EditorBrowsableState.Never)] - public class LayoutManager + public class RecycleLayoutManager { protected float mPrevScrollPosition = 0.0f; protected int mPrevFirstDataIndex = 0; @@ -69,6 +69,7 @@ namespace Tizen.NUI.Components /// /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] public Size ItemSize{get;set;} = new Size(); /// @@ -76,6 +77,7 @@ namespace Tizen.NUI.Components /// /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] public Orientation LayoutOrientation{get;set;} = Orientation.Vertical; /// @@ -83,6 +85,7 @@ namespace Tizen.NUI.Components /// /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] public float StepSize{ get { @@ -91,23 +94,44 @@ namespace Tizen.NUI.Components } /// + /// How far can you reach the next item. + /// + /// 8 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public int DataCount{get; set;} + + /// /// This is called to find out where items are lain out according to current scroll position. /// /// Scroll position which is calculated by ScrollableBase /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] public virtual void Layout(float scrollPosition) { } /// + /// This is called to find out how much container size can be. + /// + /// 8 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual float CalculateLayoutOrientationSize() + { + return 0.0f; + } + + /// /// This is called to find out which items should be recycled according to current scroll position. /// /// Scroll position which is calculated by ScrollableBase /// List of RecycleItems which should be recycled. /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] public virtual List Recycle(float scrollPosition) { return new List(); @@ -119,6 +143,7 @@ namespace Tizen.NUI.Components /// Scroll position which is calculated by ScrollableBase /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] public virtual float CalculateCandidateScrollPosition(float scrollPosition) { return scrollPosition; diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs index f6d20e3..5faf016 100644 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs @@ -29,10 +29,15 @@ namespace Tizen.NUI.Components { protected RecycleAdapter mAdapter; protected View mContainer; - protected LayoutManager mLayoutManager; + protected RecycleLayoutManager mLayoutManager; protected int mTotalItemCount = 15; private List notifications = new List(); + public RecyclerView() + { + Initialize(new RecycleAdapter(), new RecycleLayoutManager()); + } + /// /// Default constructor. /// @@ -41,7 +46,12 @@ namespace Tizen.NUI.Components /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API [EditorBrowsable(EditorBrowsableState.Never)] - public RecyclerView(RecycleAdapter adapter, LayoutManager layoutManager) + public RecyclerView(RecycleAdapter adapter, RecycleLayoutManager layoutManager) + { + Initialize(adapter, layoutManager); + } + + private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager) { Name = "[List]"; mContainer = new View() @@ -64,6 +74,37 @@ namespace Tizen.NUI.Components mLayoutManager = layoutManager; mLayoutManager.Container = mContainer; mLayoutManager.ItemSize = mAdapter.CreateRecycleItem().Size; + mLayoutManager.DataCount = mAdapter.Data.Count; + + InitializeItems(); + } + + private void OnItemSizeChanged(object source, PropertyNotification.NotifyEventArgs args) + { + mLayoutManager.Layout(ScrollingDirection == Direction.Horizontal ? mContainer.CurrentPosition.X : mContainer.CurrentPosition.Y); + } + + public int TotalItemCount + { + get + { + return mTotalItemCount; + } + set + { + mTotalItemCount = value; + InitializeItems(); + } + } + + private void InitializeItems() + { + for(int i = mContainer.Children.Count -1 ; i > -1 ; i--) + { + mContainer.Children[i].Unparent(); + notifications[i].Notified -= OnItemSizeChanged; + notifications.RemoveAt(i); + } for (int i = 0; i < mTotalItemCount; i++) { @@ -78,10 +119,7 @@ namespace Tizen.NUI.Components mContainer.Add(item); PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f)); - noti.Notified += (object source, PropertyNotification.NotifyEventArgs args) => - { - mLayoutManager.Layout(ScrollingDirection == Direction.Horizontal ? mContainer.CurrentPosition.X : mContainer.CurrentPosition.Y); - }; + noti.Notified += OnItemSizeChanged; notifications.Add(noti); } @@ -89,11 +127,33 @@ namespace Tizen.NUI.Components if (ScrollingDirection == Direction.Horizontal) { - mContainer.SizeWidth = mLayoutManager.StepSize * mAdapter.Data.Count; + mContainer.SizeWidth = mLayoutManager.CalculateLayoutOrientationSize(); } else { - mContainer.SizeHeight = mLayoutManager.StepSize * mAdapter.Data.Count; + mContainer.SizeHeight = mLayoutManager.CalculateLayoutOrientationSize(); + } + } + + + public new Direction ScrollingDirection + { + get + { + return base.ScrollingDirection; + } + set + { + base.ScrollingDirection = value; + + if (ScrollingDirection == Direction.Horizontal) + { + mContainer.SizeWidth = mLayoutManager.CalculateLayoutOrientationSize(); + } + else + { + mContainer.SizeHeight = mLayoutManager.CalculateLayoutOrientationSize(); + } } } @@ -109,6 +169,19 @@ namespace Tizen.NUI.Components { return mAdapter; } + set + { + if(mAdapter != null) + { + mAdapter.OnDataChanged -= OnAdapterDataChanged; + } + + mAdapter = value; + mAdapter.OnDataChanged += OnAdapterDataChanged; + mLayoutManager.ItemSize = mAdapter.CreateRecycleItem().Size; + mLayoutManager.DataCount = mAdapter.Data.Count; + InitializeItems(); + } } /// @@ -117,12 +190,20 @@ namespace Tizen.NUI.Components /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API [EditorBrowsable(EditorBrowsableState.Never)] - public LayoutManager ListLayoutManager + public RecycleLayoutManager LayoutManager { get { return mLayoutManager; } + set + { + mLayoutManager = value; + mLayoutManager.Container = mContainer; + mLayoutManager.ItemSize = mAdapter.CreateRecycleItem().Size; + mLayoutManager.DataCount = mAdapter.Data.Count; + InitializeItems(); + } } private void OnScroll(object source, ScrollableBase.ScrollEventArgs args) @@ -151,6 +232,7 @@ namespace Tizen.NUI.Components if (item.DataIndex > -1 && item.DataIndex < mAdapter.Data.Count) { item.Show(); + item.Name = "["+item.DataIndex+"]"; mAdapter.BindData(item); } else diff --git a/src/Tizen.NUI.Components/Controls/ScrollableBase.cs b/src/Tizen.NUI.Components/Controls/ScrollableBase.cs index 1f7d0a0..2583528 100755 --- a/src/Tizen.NUI.Components/Controls/ScrollableBase.cs +++ b/src/Tizen.NUI.Components/Controls/ScrollableBase.cs @@ -425,6 +425,7 @@ namespace Tizen.NUI.Components } mScrollingChild = view; + mScrollingChild.Layout.SetPositionByLayout = false; propertyNotification = mScrollingChild?.AddPropertyNotification("position", PropertyCondition.Step(1.0f)); propertyNotification.Notified += OnPropertyChanged; mScrollingChild.Relayout += OnScrollingChildRelayout; @@ -446,6 +447,7 @@ namespace Tizen.NUI.Components mScrollingChild.RemovePropertyNotification(propertyNotification); mScrollingChild.Relayout -= OnScrollingChildRelayout; + mScrollingChild.Layout.SetPositionByLayout = true; mScrollingChild = new View(); } } diff --git a/src/Tizen.NUI.Wearable/src/internal/FishEyeLayoutManager.cs b/src/Tizen.NUI.Wearable/src/internal/FishEyeLayoutManager.cs index ab7e1a4..bb20c92 100644 --- a/src/Tizen.NUI.Wearable/src/internal/FishEyeLayoutManager.cs +++ b/src/Tizen.NUI.Wearable/src/internal/FishEyeLayoutManager.cs @@ -22,7 +22,7 @@ namespace Tizen.NUI.Wearable /// /// [Draft] This class implements a fish eye layout /// - internal class FishEyeLayoutManager : LayoutManager + internal class FishEyeLayoutManager : RecycleLayoutManager { public int CurrentFocusedIndex { get; set; } = 0; public int FocusedIndex { get; set; } = 0; @@ -66,6 +66,12 @@ namespace Tizen.NUI.Wearable return (float)(center + result); } + + public override float CalculateLayoutOrientationSize() + { + return mStepSize * (DataCount-1); + } + public override void Layout(float scrollPosition) { RecycleItem centerItem = Container.Children[FocusedIndex] as RecycleItem; @@ -173,6 +179,7 @@ namespace Tizen.NUI.Wearable mStepSize = Container.Children[0].Size.Height / 2.0f + Container.Children[1].Size.Height * Container.Children[1].Scale.X / 2.0f; } + mStepSize = float.IsNaN(mStepSize)?0:mStepSize; } } diff --git a/src/Tizen.NUI.Wearable/src/public/WearableList.cs b/src/Tizen.NUI.Wearable/src/public/WearableList.cs index 0b44afe..e7784a8 100644 --- a/src/Tizen.NUI.Wearable/src/public/WearableList.cs +++ b/src/Tizen.NUI.Wearable/src/public/WearableList.cs @@ -37,29 +37,44 @@ namespace Tizen.NUI.Wearable /// 8 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API [EditorBrowsable(EditorBrowsableState.Never)] - public WearableList(RecycleAdapter adapter) : base(adapter, new FishEyeLayoutManager()) + public WearableList() : base(new RecycleAdapter(), new FishEyeLayoutManager()) { ScrollingDirection = ScrollableBase.Direction.Vertical; ScrollDragStartEvent += OnScrollDragStart; ScrollAnimationEndEvent += OnAnimationEnd; - foreach (View child in mContainer.Children) - { - child.PositionUsesPivotPoint = true; - child.ParentOrigin = Tizen.NUI.ParentOrigin.TopCenter; - } - mContainer.PositionUsesPivotPoint = true; mContainer.ParentOrigin = Tizen.NUI.ParentOrigin.Center; - mContainer.PivotPoint = ScrollingDirection == Direction.Vertical ? Tizen.NUI.PivotPoint.TopCenter : Tizen.NUI.PivotPoint.CenterLeft; - ScrollAvailableArea = new Vector2( 0,ListLayoutManager.StepSize * (mAdapter.Data.Count - 1) ); - + mContainer.PivotPoint = Tizen.NUI.PivotPoint.TopCenter; noticeAnimationEndBeforePosition = 50; + ScrollAvailableArea = new Vector2( 0, mContainer.SizeHeight); + SetFocus(0, false); } + public new RecycleAdapter Adapter + { + get + { + return base.Adapter; + } + + set + { + base.Adapter = value; + + foreach (View child in mContainer.Children) + { + child.PositionUsesPivotPoint = true; + child.ParentOrigin = Tizen.NUI.ParentOrigin.TopCenter; + } + + ScrollAvailableArea = new Vector2( 0, mContainer.SizeHeight ); + } + } + /// /// Set focus to item which has specific data index. /// -- 2.7.4