/* Copyright (c) 2021 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
{
///
/// layouter for CollectionView to display items in linear layout.
///
/// 9
public class LinearLayouter : ItemsLayouter
{
private readonly List ItemPosition = new List();
private readonly List ItemSize = new List();
private int ItemSizeChanged = -1;
private CollectionView collectionView;
private bool hasHeader;
private float headerSize;
private Extents headerMargin;
private bool hasFooter;
private float footerSize;
private Extents footerMargin;
private bool isGrouped;
private readonly List groups = new List();
private float groupHeaderSize;
private Extents groupHeaderMargin;
private float groupFooterSize;
private Extents groupFooterMargin;
private GroupInfo Visited;
private Timer requestLayoutTimer = null;
private bool isSourceEmpty;
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected new IGroupableItemSource Source => collectionView?.InternalSource;
///
/// Visible ViewItem.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected override List GroupItems => groups;
///
/// Clean up ItemsLayouter.
///
/// CollectionView of layouter.
/// please note that, view must be type of CollectionView
/// 9
public override void Initialize(RecyclerView view)
{
collectionView = view as CollectionView;
if (collectionView == null)
{
throw new ArgumentException("LinearLayouter only can be applied CollectionView.", nameof(view));
}
// 1. Clean Up
foreach (RecyclerViewItem item in VisibleItems)
{
collectionView.UnrealizeItem(item, false);
}
VisibleItems.Clear();
groups.Clear();
FirstVisible = 0;
LastVisible = 0;
IsHorizontal = (collectionView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
RecyclerViewItem header = collectionView?.Header;
RecyclerViewItem footer = collectionView?.Footer;
float width, height;
int count = Source.Count;
if (header != null)
{
MeasureChild(collectionView, header);
width = header.Layout != null? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
height = header.Layout != null? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
Extents itemMargin = header.Margin;
headerSize = IsHorizontal?
width + itemMargin.Start + itemMargin.End:
height + itemMargin.Top + itemMargin.Bottom;
headerMargin = new Extents(itemMargin);
hasHeader = true;
collectionView.UnrealizeItem(header);
}
else hasHeader = false;
if (footer != null)
{
MeasureChild(collectionView, footer);
width = footer.Layout != null? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
height = footer.Layout != null? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
Extents itemMargin = footer.Margin;
footerSize = IsHorizontal?
width + itemMargin.Start + itemMargin.End:
height + itemMargin.Top + itemMargin.Bottom;
footerMargin = new Extents(itemMargin);
footer.Index = count - 1;
hasFooter = true;
collectionView.UnrealizeItem(footer);
}
else hasFooter = false;
//No Internal Source exist.
if (count == (hasHeader? (hasFooter? 2 : 1) : 0))
{
isSourceEmpty = true;
base.Initialize(view);
return;
}
isSourceEmpty = false;
int firstIndex = hasHeader? 1 : 0;
if (collectionView.IsGrouped)
{
isGrouped = true;
if (collectionView.GroupHeaderTemplate != null)
{
while (!Source.IsGroupHeader(firstIndex)) firstIndex++;
//must be always true
if (Source.IsGroupHeader(firstIndex))
{
RecyclerViewItem groupHeader = collectionView.RealizeItem(firstIndex);
firstIndex++;
if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
// Need to Set proper height or width on scroll direction.
if (groupHeader.Layout == null)
{
width = groupHeader.WidthSpecification;
height = groupHeader.HeightSpecification;
}
else
{
MeasureChild(collectionView, groupHeader);
width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
}
// pick the StepCandidate.
Extents itemMargin = groupHeader.Margin;
groupHeaderSize = IsHorizontal?
width + itemMargin.Start + itemMargin.End:
height + itemMargin.Top + itemMargin.Bottom;
groupHeaderMargin = new Extents(itemMargin);
collectionView.UnrealizeItem(groupHeader);
}
}
else
{
groupHeaderSize = 0F;
}
if (collectionView.GroupFooterTemplate != null)
{
int firstFooter = firstIndex;
while (!Source.IsGroupFooter(firstFooter)) firstFooter++;
//must be always true
if (Source.IsGroupFooter(firstFooter))
{
RecyclerViewItem groupFooter = collectionView.RealizeItem(firstFooter);
if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
// Need to Set proper height or width on scroll direction.
if (groupFooter.Layout == null)
{
width = groupFooter.WidthSpecification;
height = groupFooter.HeightSpecification;
}
else
{
MeasureChild(collectionView, groupFooter);
width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
}
// pick the StepCandidate.
Extents itemMargin = groupFooter.Margin;
groupFooterSize = IsHorizontal?
width + itemMargin.Start + itemMargin.End:
height + itemMargin.Top + itemMargin.Bottom;
groupFooterMargin = new Extents(itemMargin);
collectionView.UnrealizeItem(groupFooter);
}
}
else
{
groupFooterSize = 0F;
}
}
else isGrouped = false;
bool failed = false;
//Final Check of FirstIndex
if ((Source.Count - 1 < firstIndex) ||
(Source.IsFooter(firstIndex) && (Source.Count - 1) == firstIndex))
{
StepCandidate = 0F;
failed = true;
}
while (!failed &&
(Source.IsHeader(firstIndex) ||
Source.IsGroupHeader(firstIndex) ||
Source.IsGroupFooter(firstIndex)))
{
if (Source.IsFooter(firstIndex)
|| ((Source.Count - 1) <= firstIndex))
{
StepCandidate = 0F;
failed = true;
break;
}
firstIndex++;
}
if (!failed)
{
RecyclerViewItem sizeDeligate = collectionView.RealizeItem(firstIndex);
if (sizeDeligate == null)
{
// error !
throw new Exception("Cannot create content from DatTemplate.");
}
sizeDeligate.BindingContext = Source.GetItem(firstIndex);
// Need to Set proper height or width on scroll direction.
if (sizeDeligate.Layout == null)
{
width = sizeDeligate.WidthSpecification;
height = sizeDeligate.HeightSpecification;
}
else
{
MeasureChild(collectionView, sizeDeligate);
width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
}
// pick the StepCandidate.
Extents itemMargin = sizeDeligate.Margin;
StepCandidate = IsHorizontal?
width + itemMargin.Start + itemMargin.End:
height + itemMargin.Top + itemMargin.Bottom;
CandidateMargin = new Extents(itemMargin);
if (StepCandidate == 0) StepCandidate = 1; //????
collectionView.UnrealizeItem(sizeDeligate, false);
}
float Current = IsHorizontal? Padding.Start : Padding.Top;
IGroupableItemSource source = Source;
GroupInfo currentGroup = null;
object currentParent = null;
for (int i = 0; i < count; i++)
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
if (i == 0 && hasHeader)
ItemSize.Add(headerSize);
else if (i == count - 1 && hasFooter)
ItemSize.Add(footerSize);
else if (source.IsGroupHeader(i))
ItemSize.Add(groupHeaderSize);
else if (source.IsGroupFooter(i))
ItemSize.Add(groupFooterSize);
else ItemSize.Add(StepCandidate);
}
if (isGrouped)
{
if (source.IsHeader(i))
{
//ItemPosition.Add(Current);
Current += headerSize;
}
else if (source.IsFooter(i))
{
//ItemPosition.Add(Current);
Current += footerSize;
}
else
{
//GroupHeader must always exist in group usage.
//if (source.IsGroupHeader(i))
if (source.GetGroupParent(i) != currentParent)
{
currentParent = source.GetGroupParent(i);
float currentSize = (source.IsGroupHeader(i)? groupHeaderSize :
(source.IsGroupFooter(i)? groupFooterSize: StepCandidate));
currentGroup = new GroupInfo()
{
GroupParent = currentParent,
//hasHeader = true,
//hasFooter = false,
StartIndex = i,
Count = 1,
GroupSize = currentSize,
GroupPosition = Current
};
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
currentGroup.ItemPosition.Add(0);
groups.Add(currentGroup);
if (source.IsGroupHeader(i)) Current += currentSize;
}
//optional
else if (source.IsGroupFooter(i))
{
//currentGroup.hasFooter = true;
if (currentGroup != null)
{
currentGroup.Count++;
currentGroup.GroupSize += groupFooterSize;
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
Current += groupFooterSize;
}
}
else
{
if (currentGroup != null)
{
currentGroup.Count++;
currentGroup.GroupSize += StepCandidate;
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
Current += StepCandidate;
}
}
}
}
else
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
ItemPosition.Add(Current);
if (i == 0 && hasHeader) Current += headerSize;
else if (i == count - 1 && hasFooter) Current += footerSize;
else Current += StepCandidate;
}
}
ScrollContentSize = Current + (IsHorizontal? Padding.End : Padding.Bottom);
if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
base.Initialize(view);
//Console.WriteLine("[NUI] Init Done, StepCnadidate{0}, Scroll{1}", StepCandidate, ScrollContentSize);
}
///
/// This is called to find out where items are lain out according to current scroll position.
///
/// Scroll position which is calculated by ScrollableBase
/// boolean force flag to layouting forcely.
/// 9
public override void RequestLayout(float scrollPosition, bool force = false)
{
// Layouting is only possible after once it initialized.
if (!IsInitialized) return;
if (requestLayoutTimer != null)
{
requestLayoutTimer.Dispose();
requestLayoutTimer = null;
force = true;
}
int LastIndex = Source.Count - 1;
if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
PrevScrollPosition = Math.Abs(scrollPosition);
if (ItemSizeChanged >= 0)
{
for (int i = ItemSizeChanged; i <= LastIndex; i++)
UpdatePosition(i);
(float updateX, float updateY) = GetItemPosition(LastIndex);
ScrollContentSize = GetItemStepSize(LastIndex) + (IsHorizontal? updateX + Padding.End : updateY + Padding.Bottom);
}
int prevFirstVisible = FirstVisible;
int prevLastVisible = LastVisible;
(float X, float Y) visibleArea = (PrevScrollPosition,
PrevScrollPosition + (IsHorizontal? collectionView.Size.Width : collectionView.Size.Height)
);
// 1. Set First/Last Visible Item Index.
(int start, int end) = FindVisibleItems(visibleArea);
FirstVisible = start;
LastVisible = end;
// 2. Unrealize invisible items.
List unrealizedItems = new List();
foreach (RecyclerViewItem item in VisibleItems)
{
if (item.Index < FirstVisible || item.Index > LastVisible)
{
unrealizedItems.Add(item);
collectionView.UnrealizeItem(item);
}
}
VisibleItems.RemoveAll(unrealizedItems.Contains);
unrealizedItems.Clear();
// 3. Realize and placing visible items.
for (int i = FirstVisible; i <= LastVisible; i++)
{
RecyclerViewItem item = null;
// 4. Get item if visible or realize new.
if (i >= prevFirstVisible && i <= prevLastVisible)
{
item = GetVisibleItem(i);
if (item != null && !force) continue;
}
if (item == null)
{
item = collectionView.RealizeItem(i);
if (item != null) VisibleItems.Add(item);
else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
}
// 5. Placing item.
(float posX, float posY) = GetItemPosition(i);
item.Position = new Position(posX, posY);
var size = (IsHorizontal? item.SizeWidth: item.SizeHeight);
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureFirst)
{
if (item.IsHeader || item.IsFooter || item.IsGroupHeader || item.IsGroupFooter)
{
if (item.IsHeader) size = headerSize;
else if (item.IsFooter) size = footerSize;
else if (item.IsGroupHeader) size = groupHeaderSize;
else if (item.IsGroupFooter) size = groupFooterSize;
}
else size = StepCandidate;
}
if (IsHorizontal && item.HeightSpecification == LayoutParamPolicies.MatchParent)
{
item.Size = new Size(size, Container.Size.Height - Padding.Top - Padding.Bottom - item.Margin.Top - item.Margin.Bottom);
}
else if (!IsHorizontal && item.WidthSpecification == LayoutParamPolicies.MatchParent)
{
item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, size);
}
}
return;
}
///
/// Clear the current screen and all properties.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void Clear()
{
// Clean Up
if (requestLayoutTimer != null)
{
requestLayoutTimer.Dispose();
}
if (groups != null)
{
/*
foreach (GroupInfo group in groups)
{
//group.ItemPosition?.Clear();
// if Disposable?
//group.Dispose();
}
*/
groups.Clear();
}
if (ItemPosition != null)
{
ItemPosition.Clear();
}
if (ItemSize != null)
{
ItemSize.Clear();
}
if (headerMargin != null)
{
headerMargin.Dispose();
headerMargin = null;
}
if (footerMargin != null)
{
footerMargin.Dispose();
footerMargin = null;
}
if (groupHeaderMargin != null)
{
groupHeaderMargin.Dispose();
groupHeaderMargin = null;
}
if (groupFooterMargin != null)
{
groupFooterMargin.Dispose();
groupFooterMargin = null;
}
base.Clear();
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void NotifyItemSizeChanged(RecyclerViewItem item)
{
if (item == null)
throw new ArgumentNullException(nameof(item));
if (collectionView == null) return;
if (!IsInitialized ||
(collectionView.SizingStrategy == ItemSizingStrategy.MeasureFirst &&
item.Index != 0) ||
(item.Index < 0))
return;
float PrevSize, CurrentSize;
if (item.Index == (Source.Count - 1))
{
PrevSize = ScrollContentSize - ItemPosition[item.Index];
}
else
{
PrevSize = ItemPosition[item.Index + 1] - ItemPosition[item.Index];
}
CurrentSize = (IsHorizontal? item.Size.Width : item.Size.Height);
if (CurrentSize != PrevSize)
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
ItemSize[item.Index] = CurrentSize;
else
StepCandidate = CurrentSize;
}
if (ItemSizeChanged == -1) ItemSizeChanged = item.Index;
else ItemSizeChanged = Math.Min(ItemSizeChanged, item.Index);
//ScrollContentSize += Diff; UpdateOnce?
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void NotifyItemInserted(IItemSource source, int startIndex)
{
// Insert Single item.
if (source == null) throw new ArgumentNullException(nameof(source));
if (collectionView == null) return;
if (isSourceEmpty || StepCandidate == 0)
{
Initialize(collectionView);
}
// Will be null if not a group.
float currentSize = StepCandidate;
IGroupableItemSource gSource = source as IGroupableItemSource;
// Get the first Visible Position to adjust.
/*
int topInScreenIndex = 0;
float offset = 0F;
(topInScreenIndex, offset) = FindTopItemInScreen();
*/
// 1. Handle MeasureAll
/*
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
//Need To Implement
}
*/
//2. Handle Group Case.
if (isGrouped && gSource != null)
{
GroupInfo groupInfo = null;
object groupParent = gSource.GetGroupParent(startIndex);
int parentIndex = gSource.GetPosition(groupParent);
if (gSource.HasHeader) parentIndex--;
// Check item is group parent or not
// if group parent, add new gorupinfo
if (gSource.IsHeader(startIndex))
{
// This is childless group.
// create new groupInfo!
groupInfo = new GroupInfo()
{
GroupParent = groupParent,
StartIndex = startIndex,
Count = 1,
GroupSize = groupHeaderSize,
};
if (parentIndex >= groups.Count)
{
groupInfo.GroupPosition = ScrollContentSize;
groups.Add(groupInfo);
}
else
{
groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
groups.Insert(parentIndex, groupInfo);
}
currentSize = groupHeaderSize;
}
else
{
// If not group parent, add item into the groupinfo.
if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
if (groupInfo == null) throw new Exception("Cannot find group information!");
groupInfo.Count++;
if (gSource.IsGroupFooter(startIndex))
{
// It doesn't make sence to adding footer by notify...
// if GroupFooterTemplate is added,
// need to implement on here.
}
else
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
float curPos = groupInfo.ItemPosition[startIndex - groupInfo.StartIndex];
groupInfo.ItemPosition.Insert(startIndex - groupInfo.StartIndex, curPos);
for (int i = startIndex - groupInfo.StartIndex; i < groupInfo.Count; i++)
{
groupInfo.ItemPosition[i] = curPos;
curPos += GetItemStepSize(parentIndex + i);
}
groupInfo.GroupSize = curPos;
}
else
{
groupInfo.GroupSize += currentSize;
}
}
}
if (parentIndex + 1 < groups.Count)
{
for(int i = parentIndex + 1; i < groups.Count; i++)
{
groups[i].GroupPosition += currentSize;
groups[i].StartIndex++;
}
}
}
else
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
// Need to Implements
}
}
// 3. Update Scroll Content Size
ScrollContentSize += currentSize;
if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
// 4. Update Visible Items.
foreach (RecyclerViewItem item in VisibleItems)
{
if (item.Index >= startIndex)
{
item.Index++;
}
}
if (startIndex <= FirstVisible)
{
FirstVisible++;
LastVisible++;
}
else if (startIndex > FirstVisible && startIndex <= LastVisible)
{
LastVisible++;
}
if (FirstVisible > Source.Count - 1) FirstVisible = Source.Count -1;
if (LastVisible > Source.Count - 1) LastVisible = Source.Count -1;
float scrollPosition = PrevScrollPosition;
/*
// Position Adjust
// Insertion above Top Visible!
if (startIndex <= topInScreenIndex)
{
scrollPosition = GetItemPosition(topInScreenIndex);
scrollPosition -= offset;
collectionView.ScrollTo(scrollPosition);
}
*/
// Update Viewport in delay.
// FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
// but currently we do not have any accessor to pre-calculation so instead of this,
// using Timer temporarily.
DelayedRequestLayout(scrollPosition);
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
{
// Insert Group
if (source == null) throw new ArgumentNullException(nameof(source));
if (collectionView == null) return;
if (isSourceEmpty || StepCandidate == 0)
{
Initialize(collectionView);
}
float currentSize = StepCandidate;
// Will be null if not a group.
IGroupableItemSource gSource = source as IGroupableItemSource;
// Get the first Visible Position to adjust.
/*
int topInScreenIndex = 0;
float offset = 0F;
(topInScreenIndex, offset) = FindTopItemInScreen();
*/
// 1. Handle MeasureAll
/*
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
//Need To Implement
}
*/
// 2. Handle Group Case
// Adding ranged items should all same new groups.
if (isGrouped && gSource != null)
{
GroupInfo groupInfo = null;
object groupParent = gSource.GetGroupParent(startIndex);
int parentIndex = gSource.GetPosition(groupParent);
if (gSource.HasHeader) parentIndex--;
// We guess here that range inserted from GroupStartIndex.
int groupStartIndex = startIndex;
for (int current = startIndex; current - startIndex < count; current++)
{
// Check item is group parent or not
// if group parent, add new gorupinfo
if (groupStartIndex == current)
{
currentSize = (gSource.IsGroupHeader(current)? groupHeaderSize :
(gSource.IsGroupFooter(current)? groupFooterSize: currentSize));
//create new groupInfo!
groupInfo = new GroupInfo()
{
GroupParent = groupParent,
StartIndex = current,
Count = 1,
GroupSize = currentSize,
};
}
else
{
//if not group parent, add item into the groupinfo.
//groupInfo = GetGroupInfo(groupStartIndex);
if (groupInfo == null) throw new Exception("Cannot find group information!");
groupInfo.Count++;
if (gSource.IsGroupFooter(current))
{
groupInfo.GroupSize += groupFooterSize;
}
else
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
//Need To Implement
/*
float curPos = groupInfo.ItemPosition[current - groupStartIndex];
groupInfo.ItemPosition.Insert(current - groupStartIndex, curPos);
for (int i = current - groupStartIndex; i < groupInfo.Count; i++)
{
groupInfo.ItemPosition[i] = curPos;
curPos += GetItemSize(parentIndex + i);
}
groupInfo.GroupSize = curPos;
*/
}
else
{
groupInfo.GroupSize += StepCandidate;
}
}
}
}
if (groupInfo != null)
{
if (parentIndex >= groups.Count)
{
groupInfo.GroupPosition = ScrollContentSize;
groups.Add(groupInfo);
}
else
{
groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
groups.Insert(parentIndex, groupInfo);
}
// Update other below group's position
if (parentIndex + 1 < groups.Count)
{
for(int i = parentIndex + 1; i < groups.Count; i++)
{
groups[i].GroupPosition += groupInfo.GroupSize;
groups[i].StartIndex += count;
}
}
ScrollContentSize += groupInfo.GroupSize;
}
else
{
Tizen.Log.Error("NUI", "groupInfo is null! Check count = 0");
}
}
else
{
Tizen.Log.Error("NUI", "Not support insert ungrouped range items currently!");
}
// 3. Update Scroll Content Size
if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
// 4. Update Visible Items.
foreach (RecyclerViewItem item in VisibleItems)
{
if (item.Index >= startIndex)
{
item.Index += count;
}
}
if (startIndex <= FirstVisible)
{
FirstVisible = FirstVisible + count;
LastVisible = LastVisible + count;
}
else if (startIndex > FirstVisible && startIndex <= LastVisible)
{
LastVisible = LastVisible + count;
}
if (FirstVisible > Source.Count - 1) FirstVisible = Source.Count -1;
if (LastVisible > Source.Count - 1) LastVisible = Source.Count -1;
// Position Adjust
float scrollPosition = PrevScrollPosition;
/*
// Insertion above Top Visible!
if (startIndex + count <= topInScreenIndex)
{
scrollPosition = GetItemPosition(topInScreenIndex);
scrollPosition -= offset;
collectionView.ScrollTo(scrollPosition);
}
*/
// Update Viewport in delay.
// FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
// but currently we do not have any accessor to pre-calculation so instead of this,
// using Timer temporarily.
DelayedRequestLayout(scrollPosition);
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void NotifyItemRemoved(IItemSource source, int startIndex)
{
// Remove Single
if (source == null) throw new ArgumentNullException(nameof(source));
if (collectionView == null) return;
// Will be null if not a group.
float currentSize = StepCandidate;
IGroupableItemSource gSource = source as IGroupableItemSource;
// Get the first Visible Position to adjust.
/*
int topInScreenIndex = 0;
float offset = 0F;
(topInScreenIndex, offset) = FindTopItemInScreen();
*/
// 1. Handle MeasureAll
/*
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
//Need To Implement
}
*/
// 2. Handle Group Case
if (isGrouped && gSource != null)
{
int parentIndex = 0;
GroupInfo groupInfo = null;
foreach(GroupInfo cur in groups)
{
if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
{
groupInfo = cur;
break;
}
parentIndex++;
}
if (groupInfo == null) throw new Exception("Cannot find group information!");
// Check item is group parent or not
// if group parent, add new gorupinfo
if (groupInfo.StartIndex == startIndex)
{
// This is empty group!
// check group is empty.
if (groupInfo.Count != 1)
{
throw new Exception("Cannot remove group parent");
}
currentSize = groupInfo.GroupSize;
// Remove Group
// groupInfo.Dispose();
groups.Remove(groupInfo);
parentIndex--;
}
else
{
groupInfo.Count--;
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
//Need to Implement this.
}
else
{
groupInfo.GroupSize -= currentSize;
}
}
for (int i = parentIndex + 1; i < groups.Count; i++)
{
groups[i].GroupPosition -= currentSize;
groups[i].StartIndex--;
}
}
else
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
// Need to Implements
}
// else Nothing to Do
}
ScrollContentSize -= currentSize;
// 3. Update Scroll Content Size
if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
// 4. Update Visible Items.
RecyclerViewItem targetItem = null;
foreach (RecyclerViewItem item in VisibleItems)
{
if (item.Index == startIndex)
{
targetItem = item;
collectionView.UnrealizeItem(item);
}
else if (item.Index > startIndex)
{
item.Index--;
}
}
VisibleItems.Remove(targetItem);
if (startIndex <= FirstVisible)
{
FirstVisible--;
LastVisible--;
}
else if (startIndex > FirstVisible && startIndex <= LastVisible)
{
LastVisible--;
}
if (FirstVisible < 0) FirstVisible = 0;
if (LastVisible < 0) LastVisible = 0;
// Position Adjust
float scrollPosition = PrevScrollPosition;
/*
// Insertion above Top Visible!
if (startIndex <= topInScreenIndex)
{
scrollPosition = GetItemPosition(topInScreenIndex);
scrollPosition -= offset;
collectionView.ScrollTo(scrollPosition);
}
*/
// Update Viewport in delay.
// FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
// but currently we do not have any accessor to pre-calculation so instead of this,
// using Timer temporarily.
DelayedRequestLayout(scrollPosition);
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
{
// Remove Group
if (source == null) throw new ArgumentNullException(nameof(source));
if (collectionView == null) return;
// Will be null if not a group.
float currentSize = StepCandidate;
IGroupableItemSource gSource = source as IGroupableItemSource;
// Get the first Visible Position to adjust.
/*
int topInScreenIndex = 0;
float offset = 0F;
(topInScreenIndex, offset) = FindTopItemInScreen();
*/
// 1. Handle MeasureAll
/*
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
//Need To Implement
}
*/
// 2. Handle Group Case
if (isGrouped && gSource != null)
{
int parentIndex = 0;
GroupInfo groupInfo = null;
foreach(GroupInfo cur in groups)
{
if ((cur.StartIndex == startIndex) && (cur.Count == count))
{
groupInfo = cur;
break;
}
parentIndex++;
}
if (groupInfo == null) throw new Exception("Cannot find group information!");
// Check item is group parent or not
// if group parent, add new gorupinfo
currentSize = groupInfo.GroupSize;
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
// Update ItemSize and ItemPosition
}
// Remove Group
// groupInfo.Dispose();
groups.Remove(groupInfo);
for (int i = parentIndex; i < groups.Count; i++)
{
groups[i].GroupPosition -= currentSize;
groups[i].StartIndex -= count;
}
}
else
{
//It must group case! throw exception!
Tizen.Log.Error("NUI", "Not support remove ungrouped range items currently!");
}
ScrollContentSize -= currentSize;
// 3. Update Scroll Content Size
if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
// 4. Update Visible Items.
List unrealizedItems = new List();
foreach (RecyclerViewItem item in VisibleItems)
{
if ((item.Index >= startIndex)
&& (item.Index < startIndex + count))
{
unrealizedItems.Add(item);
collectionView.UnrealizeItem(item);
}
else if (item.Index >= startIndex + count)
{
item.Index -= count;
}
}
VisibleItems.RemoveAll(unrealizedItems.Contains);
unrealizedItems.Clear();
if (startIndex <= FirstVisible)
{
FirstVisible = FirstVisible - count;
LastVisible = LastVisible - count;
}
else if (startIndex > FirstVisible && startIndex <= LastVisible)
{
LastVisible = LastVisible - count;
}
if (FirstVisible < 0) FirstVisible = 0;
if (LastVisible < 0) LastVisible = 0;
// Position Adjust
float scrollPosition = PrevScrollPosition;
/*
// Insertion above Top Visible!
if (startIndex <= topInScreenIndex)
{
scrollPosition = GetItemPosition(topInScreenIndex);
scrollPosition -= offset;
collectionView.ScrollTo(scrollPosition);
}
*/
// Update Viewport in delay.
// FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
// but currently we do not have any accessor to pre-calculation so instead of this,
// using Timer temporarily.
DelayedRequestLayout(scrollPosition);
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
{
// Reorder Single
if (source == null) throw new ArgumentNullException(nameof(source));
if (collectionView == null) return;
// Will be null if not a group.
float currentSize = StepCandidate;
int diff = toPosition - fromPosition;
// Get the first Visible Position to adjust.
/*
int topInScreenIndex = 0;
float offset = 0F;
(topInScreenIndex, offset) = FindTopItemInScreen();
*/
// 1. Handle MeasureAll
/*
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
//Need To Implement
}
*/
// Move can only happen in it's own groups.
// so there will be no changes in position, startIndex in ohter groups.
// check visible item and update indexs.
int startIndex = ( diff > 0 ? fromPosition: toPosition);
int endIndex = (diff > 0 ? toPosition: fromPosition);
if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
{
foreach (RecyclerViewItem item in VisibleItems)
{
if ((item.Index >= startIndex)
&& (item.Index <= endIndex))
{
if (item.Index == fromPosition) item.Index = toPosition;
else
{
if (diff > 0) item.Index--;
else item.Index++;
}
}
}
}
if (fromPosition > FirstVisible)
{
if (toPosition > LastVisible)
{
FirstVisible--;
LastVisible--;
}
else if (toPosition > FirstVisible && toPosition <= LastVisible)
{
LastVisible--;
}
}
else if (fromPosition >= FirstVisible && fromPosition <= LastVisible)
{
if (toPosition < FirstVisible)
{
FirstVisible++;
}
else if (toPosition > LastVisible)
{
LastVisible--;
}
}
else if (fromPosition > LastVisible)
{
if (toPosition <= FirstVisible)
{
FirstVisible++;
LastVisible++;
}
else if (toPosition > FirstVisible && toPosition <= LastVisible)
{
LastVisible++;
}
}
if (FirstVisible < 0) FirstVisible = 0;
if (LastVisible < 0) LastVisible = 0;
if (FirstVisible > Source.Count - 1) FirstVisible = Source.Count -1;
if (LastVisible > Source.Count - 1) LastVisible = Source.Count -1;
if (IsHorizontal) collectionView.ContentContainer.SizeWidth = ScrollContentSize;
else collectionView.ContentContainer.SizeHeight = ScrollContentSize;
// Position Adjust
float scrollPosition = PrevScrollPosition;
/*
// Insertion above Top Visible!
if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
{
scrollPosition = GetItemPosition(topInScreenIndex);
scrollPosition -= offset;
collectionView.ScrollTo(scrollPosition);
}
*/
// Update Viewport in delay.
// FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
// but currently we do not have any accessor to pre-calculation so instead of this,
// using Timer temporarily.
DelayedRequestLayout(scrollPosition);
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
{
// Reorder Groups
if (source == null) throw new ArgumentNullException(nameof(source));
if (collectionView == null) return;
// Will be null if not a group.
float currentSize = StepCandidate;
int diff = toPosition - fromPosition;
int startIndex = ( diff > 0 ? fromPosition: toPosition);
int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
// 2. Handle Group Case
if (isGrouped)
{
int fromParentIndex = 0;
int toParentIndex = 0;
bool findFrom = false;
bool findTo = false;
GroupInfo fromGroup = null;
GroupInfo toGroup = null;
foreach(GroupInfo cur in groups)
{
if ((cur.StartIndex == fromPosition) && (cur.Count == count))
{
fromGroup = cur;
findFrom = true;
if (findFrom && findTo) break;
}
else if (cur.StartIndex == toPosition)
{
toGroup = cur;
findTo = true;
if (findFrom && findTo) break;
}
if (!findFrom) fromParentIndex++;
if (!findTo) toParentIndex++;
}
if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
fromGroup.StartIndex = toGroup.StartIndex;
fromGroup.GroupPosition = toGroup.GroupPosition;
endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
groups.Remove(fromGroup);
groups.Insert(toParentIndex, fromGroup);
int startGroup = (diff > 0? fromParentIndex: toParentIndex);
int endGroup = (diff > 0? toParentIndex: fromParentIndex);
for (int i = startGroup; i <= endGroup; i++)
{
if (i == toParentIndex) continue;
float prevPos = groups[i].GroupPosition;
int prevIdx = groups[i].StartIndex;
groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
}
}
else
{
//It must group case! throw exception!
Tizen.Log.Error("NUI", "Not support move ungrouped range items currently!");
}
// Move can only happen in it's own groups.
// so there will be no changes in position, startIndex in ohter groups.
// check visible item and update indexs.
if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
{
foreach (RecyclerViewItem item in VisibleItems)
{
if ((item.Index >= startIndex)
&& (item.Index <= endIndex))
{
if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
{
item.Index = fromPosition - item.Index + toPosition;
}
else
{
if (diff > 0) item.Index -= count;
else item.Index += count;
}
}
}
}
// FIXME!! Unraelize All and reset First/Last Visible
foreach (RecyclerViewItem item in VisibleItems)
{
collectionView.UnrealizeItem(item);
}
VisibleItems.Clear();
FirstVisible = 0;
LastVisible = 0;
// Position Adjust
float scrollPosition = PrevScrollPosition;
/*
// Insertion above Top Visible!
if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
{
scrollPosition = GetItemPosition(topInScreenIndex);
scrollPosition -= offset;
collectionView.ScrollTo(scrollPosition);
}
*/
// Update Viewport in delay.
// FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
// but currently we do not have any accessor to pre-calculation so instead of this,
// using Timer temporarily.
DelayedRequestLayout(scrollPosition);
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
{
// Reorder Group
if (source == null) throw new ArgumentNullException(nameof(source));
IGroupableItemSource gSource = source as IGroupableItemSource;
if (gSource == null)throw new Exception("Source is not group!");
if (collectionView == null) return;
// Get the first Visible Position to adjust.
/*
int topInScreenIndex = 0;
float offset = 0F;
(topInScreenIndex, offset) = FindTopItemInScreen();
*/
// Unrealize, initialized all items in the Range
// and receate all.
// Update Viewport in delay.
// FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
// but currently we do not have any accessor to pre-calculation so instead of this,
// using Timer temporarily.
DelayedRequestLayout(PrevScrollPosition);
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override float CalculateLayoutOrientationSize()
{
//Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
return ScrollContentSize;
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override float CalculateCandidateScrollPosition(float scrollPosition)
{
//Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
return scrollPosition;
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
{
if (currentFocusedView == null)
throw new ArgumentNullException(nameof(currentFocusedView));
View nextFocusedView = null;
int targetSibling = -1;
switch (direction)
{
case View.FocusDirection.Left:
{
targetSibling = IsHorizontal? currentFocusedView.SiblingOrder - 1 : targetSibling;
break;
}
case View.FocusDirection.Right:
{
targetSibling = IsHorizontal? currentFocusedView.SiblingOrder + 1 : targetSibling;
break;
}
case View.FocusDirection.Up:
{
targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder - 1;
break;
}
case View.FocusDirection.Down:
{
targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder + 1;
break;
}
}
if (targetSibling > -1 && targetSibling < Container.Children.Count)
{
RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
if (candidate != null && candidate.Index >= 0 && candidate.Index < Source.Count)
{
nextFocusedView = candidate;
}
}
return nextFocusedView;
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
{
int MaxIndex = Source.Count - 1 - (hasFooter? 1 : 0);
int adds = 5;
int skipGroup = -2;
(int start, int end) found = (0, 0);
// 1. Find the start index.
// Header is Showing
if (hasHeader && visibleArea.X <= headerSize + (IsHorizontal? Padding.Start: Padding.Top))
{
found.start = 0;
}
else
{
if (isGrouped)
{
bool failed = true;
foreach (GroupInfo gInfo in groups)
{
skipGroup++;
// in the Group
if (gInfo.GroupPosition <= visibleArea.X &&
gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
{
for (int i = 0; i < gInfo.Count; i++)
{
// Reach last index of group.
if (i == (gInfo.Count - 1))
{
found.start = gInfo.StartIndex + i - adds;
failed = false;
break;
}
else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.X &&
GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.X)
{
found.start = gInfo.StartIndex + i - adds;
failed = false;
break;
}
}
}
}
//footer only shows?
if (failed)
{
found.start = MaxIndex;
}
}
else
{
float visibleAreaX = visibleArea.X - (hasHeader? headerSize : 0);
// Prevent zero division.
var itemSize = (StepCandidate != 0)? StepCandidate : 1f;
found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / itemSize)) - adds);
}
if (found.start < 0) found.start = 0;
}
if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
{
found.end = MaxIndex + 1;
}
else
{
if (isGrouped)
{
bool failed = true;
// can it be start from founded group...?
//foreach(GroupInfo gInfo in groups.Skip(skipGroup))
foreach (GroupInfo gInfo in groups)
{
// in the Group
if (gInfo.GroupPosition <= visibleArea.Y &&
gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
{
for (int i = 0; i < gInfo.Count; i++)
{
if (i == (gInfo.Count - 1))
{
//Should be groupFooter!
found.end = gInfo.StartIndex + i + adds;
failed = false;
break;
}
else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.Y &&
GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.Y)
{
found.end = gInfo.StartIndex + i + adds;
failed = false;
break;
}
}
}
}
if (failed) found.end = MaxIndex;
}
else
{
float visibleAreaY = visibleArea.Y - (hasHeader? headerSize : 0);
// Prevent zero division.
var itemSize = (StepCandidate != 0)? StepCandidate : 1f;
found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / itemSize)) + adds);
if (hasHeader) found.end += 1;
}
if (found.end > (MaxIndex)) found.end = MaxIndex;
}
return found;
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected internal override (float X, float Y) GetItemPosition(int index)
{
int spaceStartX = Padding.Start;
int spaceStartY = Padding.Top;
if (Source.IsHeader(index))
{
return (spaceStartX + headerMargin.Start, spaceStartY + headerMargin.Top);
}
else if (Source.IsFooter(index))
{
return ((IsHorizontal? ScrollContentSize - footerSize - Padding.End + footerMargin.Start : spaceStartX + footerMargin.Start),
(IsHorizontal? spaceStartY + footerMargin.Top : ScrollContentSize - footerSize - Padding.Bottom + footerMargin.Top));
}
else if (isGrouped)
{
GroupInfo gInfo = GetGroupInfo(index);
if (gInfo == null)
{
Tizen.Log.Error("NUI", "GroupInfo failed to get in GetItemPosition()!");
return (0, 0);
}
float current = GetGroupPosition(gInfo, index);
Extents itemMargin = CandidateMargin;
if (Source.IsGroupHeader(index))
{
itemMargin = groupHeaderMargin;
}
else if (Source.IsGroupFooter(index))
{
itemMargin = groupFooterMargin;
}
return ((IsHorizontal?
itemMargin.Start + GetGroupPosition(gInfo, index):
spaceStartX + itemMargin.Start),
(IsHorizontal?
spaceStartY + itemMargin.Top:
itemMargin.Top + GetGroupPosition(gInfo, index)));
}
else if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
//FIXME : CandidateMargin need to be actual itemMargin
return ((IsHorizontal? ItemPosition[index] + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
(IsHorizontal? spaceStartY + CandidateMargin.Top : ItemPosition[index] + CandidateMargin.Top));
}
else
{
int adjustIndex = index - (hasHeader ? 1 : 0);
float current = (IsHorizontal ? spaceStartX : spaceStartY) + (hasHeader? headerSize : 0) + adjustIndex * StepCandidate;
//FIXME : CandidateMargin need to be actual itemMargin
return ((IsHorizontal? current + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
(IsHorizontal? spaceStartY + CandidateMargin.Top : current + CandidateMargin.Top));
}
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected internal override (float Width, float Height) GetItemSize(int index)
{
if (Source.IsHeader(index))
{
return ((IsHorizontal? (int)headerSize : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
- headerMargin.Start - headerMargin.End,
(IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)headerSize)
- headerMargin.Top - headerMargin.Bottom);
}
else if (Source.IsFooter(index))
{
return ((IsHorizontal? (int)footerSize : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
- footerMargin.Start - footerMargin.End,
(IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)footerSize)
- footerMargin.Top - footerMargin.Bottom);
}
else if (Source.IsGroupHeader(index))
{
return ((IsHorizontal? (int)groupHeaderSize : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
- groupHeaderMargin.Start - groupHeaderMargin.End,
(IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)groupHeaderSize)
- groupHeaderMargin.Top - groupHeaderMargin.Bottom);
}
else if (Source.IsGroupFooter(index))
{
return ((IsHorizontal? (int)groupFooterSize : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
- groupFooterMargin.Start - groupFooterMargin.End,
(IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)groupFooterSize)
- groupFooterMargin.Top - groupFooterMargin.Bottom);
}
else
{
return ((IsHorizontal? (int)StepCandidate : (int)(collectionView.Size.Width) - Padding.Start - Padding.End)
- CandidateMargin.Start - CandidateMargin.End,
(IsHorizontal? (int)collectionView.Size.Height - Padding.Top - Padding.Bottom: (int)StepCandidate)
- CandidateMargin.Top - CandidateMargin.Bottom);
}
}
private void DelayedRequestLayout(float scrollPosition , bool force = true)
{
if (requestLayoutTimer != null)
{
requestLayoutTimer.Dispose();
}
requestLayoutTimer = new Timer(1);
requestLayoutTimer.Interval = 1;
requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
{
RequestLayout(scrollPosition, force);
return false;
});
requestLayoutTimer.Start();
}
/*
private (int, float) FindTopItemInScreen()
{
int index = -1;
float offset = 0.0F, Pos, Size;
foreach(RecyclerViewItem item in VisibleItems)
{
Pos = IsHorizontal ? item.PositionX : item.PositionY;
Size = IsHorizontal ? item.SizeWidth : item.SizeHeight;
if (PrevScrollPosition >= Pos && PrevScrollPosition < Pos + Size)
{
index = item.Index;
offset = Pos - PrevScrollPosition;
break;
}
}
return (index, offset);
}
*/
private float GetItemStepSize(int index)
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
{
return ItemSize[index];
}
else
{
if (Source.IsHeader(index))
return headerSize;
else if (Source.IsFooter(index))
return footerSize;
else if (Source.IsGroupHeader(index))
return groupHeaderSize;
else if (Source.IsGroupFooter(index))
return groupFooterSize;
else
return StepCandidate;
}
}
private void UpdatePosition(int index)
{
bool IsGroup = (Source is IGroupableItemSource);
if (index <= 0) return;
if (index >= Source.Count)
if (IsGroup)
{
//IsGroupHeader = (Source as IGroupableItemSource).IsGroupHeader(index);
//IsGroupFooter = (Source as IGroupableItemSource).IsGroupFooter(index);
//Do Something
}
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
ItemPosition[index] = ItemPosition[index - 1] + GetItemStepSize(index - 1);
}
private GroupInfo GetGroupInfo(int index)
{
if (Visited != null)
{
if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
return Visited;
}
if (hasHeader && index == 0) return null;
foreach (GroupInfo group in groups)
{
if (group.StartIndex <= index && group.StartIndex + group.Count > index)
{
Visited = group;
return group;
}
}
Visited = null;
return null;
}
private float GetGroupPosition(GroupInfo groupInfo, int index)
{
if (collectionView.SizingStrategy == ItemSizingStrategy.MeasureAll)
return groupInfo.GroupPosition + groupInfo.ItemPosition[index - groupInfo.StartIndex];
else
{
float pos = groupInfo.GroupPosition;
if (groupInfo.StartIndex == index) return pos;
pos = pos + groupHeaderSize + StepCandidate * (index - groupInfo.StartIndex - 1);
return pos;
}
}
/*
private object GetGroupParent(int index)
{
if (Visited != null)
{
if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
return Visited.GroupParent;
}
if (hasHeader && index == 0) return null;
foreach (GroupInfo group in groups)
{
if (group.StartIndex <= index && group.StartIndex + group.Count > index)
{
Visited = group;
return group.GroupParent;
}
}
Visited = null;
return null;
}
*/
}
}