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 System.Collections.Generic;
19 using System.ComponentModel;
21 namespace Tizen.NUI.Components
24 /// [Draft] This class provides a View that can recycle items to improve performance.
26 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
27 [EditorBrowsable(EditorBrowsableState.Never)]
28 public class RecyclerView : ScrollableBase
30 private RecycleAdapter adapter;
31 private RecycleLayoutManager layoutManager;
32 private int totalItemCount = 15;
33 private List<PropertyNotification> notifications = new List<PropertyNotification>();
35 public RecyclerView() : base()
37 Initialize(new RecycleAdapter(), new RecycleLayoutManager());
41 /// Default constructor.
43 /// <param name="adapter">Recycle adapter of RecyclerView.</param>
44 /// <param name="layoutManager">Recycle layoutManager of RecyclerView.</param>
45 /// <since_tizen> 8 </since_tizen>
46 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
47 [EditorBrowsable(EditorBrowsableState.Never)]
48 public RecyclerView(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
50 Initialize(adapter, layoutManager);
53 private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
56 SetKeyboardNavigationSupport(true);
57 Scrolling += OnScrolling;
59 this.adapter = adapter;
60 this.adapter.OnDataChanged += OnAdapterDataChanged;
62 this.layoutManager = layoutManager;
63 this.layoutManager.Container = ContentContainer;
64 this.layoutManager.ItemSize = this.adapter.CreateRecycleItem().Size;
65 this.layoutManager.DataCount = this.adapter.Data.Count;
70 private void OnItemSizeChanged(object source, PropertyNotification.NotifyEventArgs args)
72 layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
75 public int TotalItemCount
79 return totalItemCount;
83 totalItemCount = value;
88 private void InitializeItems()
90 for (int i = Children.Count - 1; i > -1; i--)
92 Children[i].Unparent();
93 notifications[i].Notified -= OnItemSizeChanged;
94 notifications.RemoveAt(i);
97 for (int i = 0; i < totalItemCount; i++)
99 RecycleItem item = adapter.CreateRecycleItem();
101 item.Name = "[" + i + "] recycle";
103 if (i < adapter.Data.Count)
105 adapter.BindData(item);
109 PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f));
110 noti.Notified += OnItemSizeChanged;
111 notifications.Add(noti);
114 layoutManager.Layout(0.0f);
116 if (ScrollingDirection == Direction.Horizontal)
118 ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
122 ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
127 public new Direction ScrollingDirection
131 return base.ScrollingDirection;
135 base.ScrollingDirection = value;
137 if (ScrollingDirection == Direction.Horizontal)
139 ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
143 ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
149 /// Recycler adpater.
151 /// <since_tizen> 8 </since_tizen>
152 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
153 [EditorBrowsable(EditorBrowsableState.Never)]
154 public RecycleAdapter Adapter
164 adapter.OnDataChanged -= OnAdapterDataChanged;
168 adapter.OnDataChanged += OnAdapterDataChanged;
169 layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
170 layoutManager.DataCount = adapter.Data.Count;
176 /// Recycler layoutManager.
178 /// <since_tizen> 8 </since_tizen>
179 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
180 [EditorBrowsable(EditorBrowsableState.Never)]
181 public RecycleLayoutManager LayoutManager
185 return layoutManager;
189 layoutManager = value;
190 layoutManager.Container = ContentContainer;
191 layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
192 layoutManager.DataCount = adapter.Data.Count;
197 private void OnScrolling(object source, ScrollEventArgs args)
199 layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
200 List<RecycleItem> recycledItemList = layoutManager.Recycle(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
201 BindData(recycledItemList);
204 private void OnAdapterDataChanged(object source, EventArgs args)
206 List<RecycleItem> changedData = new List<RecycleItem>();
208 foreach (RecycleItem item in Children)
210 changedData.Add(item);
213 BindData(changedData);
216 private void BindData(List<RecycleItem> changedData)
218 foreach (RecycleItem item in changedData)
220 if (item.DataIndex > -1 && item.DataIndex < adapter.Data.Count)
223 item.Name = "[" + item.DataIndex + "]";
224 adapter.BindData(item);
234 /// Adjust scrolling position by own scrolling rules.
235 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
237 /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
238 /// <returns>Adjusted scroll destination</returns>
239 /// <since_tizen> 8 </since_tizen>
240 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
241 [EditorBrowsable(EditorBrowsableState.Never)]
242 protected override float AdjustTargetPositionOfScrollAnimation(float position)
244 // Destination is depending on implementation of layout manager.
245 // Get destination from layout manager.
246 return layoutManager.CalculateCandidateScrollPosition(position);
249 private View focusedView;
250 private int prevFocusedDataIndex = 0;
252 public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
254 View nextFocusedView = null;
258 // If focusedView is null, find child which has previous data index
259 if (Children.Count > 0 && Adapter.Data.Count > 0)
261 for (int i = 0; i < Children.Count; i++)
263 RecycleItem item = Children[i] as RecycleItem;
264 if (item.DataIndex == prevFocusedDataIndex)
266 nextFocusedView = item;
274 // If this is not first focus, request next focus to LayoutManager
275 if (LayoutManager != null)
277 nextFocusedView = LayoutManager.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
281 if (nextFocusedView != null)
283 // Check next focused view is inside of visible area.
284 // If it is not, move scroll position to make it visible.
285 Position scrollPosition = ContentContainer.CurrentPosition;
286 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
288 float left = nextFocusedView.Position.X;
289 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
290 float top = nextFocusedView.Position.Y;
291 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
293 float visibleRectangleLeft = -scrollPosition.X;
294 float visibleRectangleRight = -scrollPosition.X + Size.Width;
295 float visibleRectangleTop = -scrollPosition.Y;
296 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
298 if (ScrollingDirection == Direction.Horizontal)
300 if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
302 targetPosition = left;
304 else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
306 targetPosition = right - Size.Width;
311 if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
313 targetPosition = top;
315 else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
317 targetPosition = bottom - Size.Height;
321 focusedView = nextFocusedView;
322 if ((nextFocusedView as RecycleItem) != null)
324 prevFocusedDataIndex = (nextFocusedView as RecycleItem).DataIndex;
327 ScrollTo(targetPosition, true);
331 // If nextView is null, it means that we should move focus to outside of Control.
332 // Return FocusableView depending on direction.
335 case View.FocusDirection.Left:
337 nextFocusedView = LeftFocusableView;
340 case View.FocusDirection.Right:
342 nextFocusedView = RightFocusableView;
345 case View.FocusDirection.Up:
347 nextFocusedView = UpFocusableView;
350 case View.FocusDirection.Down:
352 nextFocusedView = DownFocusableView;
363 //If FocusableView doesn't exist, not move focus.
364 nextFocusedView = focusedView;
368 return nextFocusedView;