1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 using Tizen.NUI.BaseComponents;
18 using Tizen.NUI.Components;
19 using System.Collections.Generic;
20 using System.ComponentModel;
22 namespace Tizen.NUI.Wearable
25 /// [Draft] This class provides a View that can recycle items to improve performance.
27 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
28 [EditorBrowsable(EditorBrowsableState.Never)]
29 public class RecyclerView : ScrollableBase
31 private RecycleAdapter adapter;
32 private RecycleLayoutManager layoutManager;
33 private int totalItemCount = 15;
34 private List<PropertyNotification> notifications = new List<PropertyNotification>();
37 /// Default constructor.
39 public RecyclerView() : base()
41 Initialize(new RecycleAdapter(), new RecycleLayoutManager());
45 /// A constructor of <see cref="RecyclerView" />.
47 /// <param name="adapter">Recycle adapter of RecyclerView.</param>
48 /// <param name="layoutManager">Recycle layoutManager of RecyclerView.</param>
49 /// <since_tizen> 8 </since_tizen>
50 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
51 [EditorBrowsable(EditorBrowsableState.Never)]
52 public RecyclerView(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
54 Initialize(adapter, layoutManager);
57 private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
60 SetKeyboardNavigationSupport(true);
61 Scrolling += OnScrolling;
63 this.adapter = adapter;
64 this.adapter.OnDataChanged += OnAdapterDataChanged;
66 this.layoutManager = layoutManager;
67 this.layoutManager.Container = ContentContainer;
68 this.layoutManager.ItemSize = this.adapter.CreateRecycleItem().Size;
69 this.layoutManager.DataCount = this.adapter.Data.Count;
74 private void OnItemSizeChanged(object source, PropertyNotification.NotifyEventArgs args)
76 layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
79 public int TotalItemCount
83 return totalItemCount;
87 totalItemCount = value;
92 private void InitializeItems()
94 for (int i = Children.Count - 1; i > -1; i--)
96 Children[i].Unparent();
97 notifications[i].Notified -= OnItemSizeChanged;
98 notifications.RemoveAt(i);
101 for (int i = 0; i < totalItemCount; i++)
103 RecycleItem item = adapter.CreateRecycleItem();
105 item.Name = "[" + i + "] recycle";
107 if (i < adapter.Data.Count)
109 adapter.BindData(item);
113 PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f));
114 noti.Notified += OnItemSizeChanged;
115 notifications.Add(noti);
118 layoutManager.Layout(0.0f);
120 if (ScrollingDirection == Direction.Horizontal)
122 ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
126 ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
131 public new Direction ScrollingDirection
135 return base.ScrollingDirection;
139 base.ScrollingDirection = value;
141 if (ScrollingDirection == Direction.Horizontal)
143 ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
147 ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
153 /// Recycler adpater.
155 /// <since_tizen> 8 </since_tizen>
156 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
157 [EditorBrowsable(EditorBrowsableState.Never)]
158 public RecycleAdapter Adapter
168 adapter.OnDataChanged -= OnAdapterDataChanged;
172 adapter.OnDataChanged += OnAdapterDataChanged;
173 layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
174 layoutManager.DataCount = adapter.Data.Count;
180 /// Recycler layoutManager.
182 /// <since_tizen> 8 </since_tizen>
183 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
184 [EditorBrowsable(EditorBrowsableState.Never)]
185 public RecycleLayoutManager LayoutManager
189 return layoutManager;
193 layoutManager = value;
194 layoutManager.Container = ContentContainer;
195 layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
196 layoutManager.DataCount = adapter.Data.Count;
201 private void OnScrolling(object source, ScrollEventArgs args)
203 layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
204 List<RecycleItem> recycledItemList = layoutManager.Recycle(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
205 BindData(recycledItemList);
208 private void OnAdapterDataChanged(object source, EventArgs args)
210 List<RecycleItem> changedData = new List<RecycleItem>();
212 foreach (RecycleItem item in Children)
214 changedData.Add(item);
217 BindData(changedData);
220 private void BindData(List<RecycleItem> changedData)
222 foreach (RecycleItem item in changedData)
224 if (item.DataIndex > -1 && item.DataIndex < adapter.Data.Count)
227 item.Name = "[" + item.DataIndex + "]";
228 adapter.BindData(item);
238 /// Adjust scrolling position by own scrolling rules.
239 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
241 /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
242 /// <returns>Adjusted scroll destination</returns>
243 /// <since_tizen> 8 </since_tizen>
244 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
245 [EditorBrowsable(EditorBrowsableState.Never)]
246 protected override float AdjustTargetPositionOfScrollAnimation(float position)
248 // Destination is depending on implementation of layout manager.
249 // Get destination from layout manager.
250 return layoutManager.CalculateCandidateScrollPosition(position);
253 private View focusedView;
254 private int prevFocusedDataIndex = 0;
256 public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
258 View nextFocusedView = null;
262 // If focusedView is null, find child which has previous data index
263 if (Children.Count > 0 && Adapter.Data.Count > 0)
265 for (int i = 0; i < Children.Count; i++)
267 if ((Children[i] is RecycleItem item) && (item.DataIndex == prevFocusedDataIndex))
269 nextFocusedView = item;
277 // If this is not first focus, request next focus to LayoutManager
278 if (LayoutManager != null)
280 nextFocusedView = LayoutManager.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
284 if (nextFocusedView != null)
286 // Check next focused view is inside of visible area.
287 // If it is not, move scroll position to make it visible.
288 Position scrollPosition = ContentContainer.CurrentPosition;
289 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
291 float left = nextFocusedView.Position.X;
292 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
293 float top = nextFocusedView.Position.Y;
294 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
296 float visibleRectangleLeft = -scrollPosition.X;
297 float visibleRectangleRight = -scrollPosition.X + Size.Width;
298 float visibleRectangleTop = -scrollPosition.Y;
299 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
301 if (ScrollingDirection == Direction.Horizontal)
303 if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
305 targetPosition = left;
307 else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
309 targetPosition = right - Size.Width;
314 if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
316 targetPosition = top;
318 else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
320 targetPosition = bottom - Size.Height;
324 focusedView = nextFocusedView;
325 if (nextFocusedView is RecycleItem item)
327 prevFocusedDataIndex = item.DataIndex;
330 ScrollTo(targetPosition, true);
334 // If nextView is null, it means that we should move focus to outside of Control.
335 // Return FocusableView depending on direction.
338 case View.FocusDirection.Left:
340 nextFocusedView = LeftFocusableView;
343 case View.FocusDirection.Right:
345 nextFocusedView = RightFocusableView;
348 case View.FocusDirection.Up:
350 nextFocusedView = UpFocusableView;
353 case View.FocusDirection.Down:
355 nextFocusedView = DownFocusableView;
368 //If FocusableView doesn't exist, not move focus.
369 nextFocusedView = focusedView;
373 return nextFocusedView;