From 193a05a9b599b60842c0ef03ddca5b290369d630 Mon Sep 17 00:00:00 2001 From: MichaelO Date: Tue, 7 Nov 2023 22:29:42 +0800 Subject: [PATCH] updated to v1.5.0 --- .../InfiniteScrollView/Scripts/CHANGELOG.md | 24 ++ .../HorizontalGridInfiniteScrollView.cs | 293 ++++++++++-------- .../Runtime/HorizontalInfiniteScrollView.cs | 284 +++++++++-------- .../Scripts/Runtime/InfiniteScrollView.cs | 281 ++++++++++------- .../Runtime/VerticalGridInfiniteScrollView.cs | 293 ++++++++++-------- .../Runtime/VerticalInfiniteScrollView.cs | 282 +++++++++-------- .../Samples~/05_ChatRoom/TestGUI_05.cs | 6 +- .../InfiniteScrollView/Scripts/package.json | 2 +- 8 files changed, 840 insertions(+), 625 deletions(-) diff --git a/Assets/InfiniteScrollView/Scripts/CHANGELOG.md b/Assets/InfiniteScrollView/Scripts/CHANGELOG.md index a5630ad..2ffb65a 100644 --- a/Assets/InfiniteScrollView/Scripts/CHANGELOG.md +++ b/Assets/InfiniteScrollView/Scripts/CHANGELOG.md @@ -1,5 +1,29 @@ ## CHANGELOG +## [1.5.0] - 2023-11-07 +- Added DataOrder. +```C# + public enum DataOrder + { + Normal, + Reverse + } +``` +- Added lastVisibleRangeSize param in InfiniteScrollView. +```C# + public float lastVisibleRangeSize { get; protected set; } +``` +- Renamed RefreshCellVisibilityWithCheck to RefreshVisibleCells in InfiniteScrollView. +```C# + public void RefreshVisibleCells() +``` +- Renamed RefreshCellVisibility to DoRefreshVisibleCells in InfiniteScrollView. +```C# + protected abstract void DoRefreshVisibleCells(); +``` +- Removed RefreshAndCheckVisibleInfo from InfiniteScrollView. +- Optimized code and refresh efficiency. + ## [1.4.1] - 2023-11-03 - Fixed Refresh method default value bug issue (disabledRefreshCells = false). - Added SnapFirst and SnapMiddle in InfiniteScrollView. diff --git a/Assets/InfiniteScrollView/Scripts/Runtime/HorizontalGridInfiniteScrollView.cs b/Assets/InfiniteScrollView/Scripts/Runtime/HorizontalGridInfiniteScrollView.cs index f98a152..72f4e25 100644 --- a/Assets/InfiniteScrollView/Scripts/Runtime/HorizontalGridInfiniteScrollView.cs +++ b/Assets/InfiniteScrollView/Scripts/Runtime/HorizontalGridInfiniteScrollView.cs @@ -8,77 +8,160 @@ public class HorizontalGridInfiniteScrollView : InfiniteScrollView public Vector2 spacing; public int rowCount = 1; - protected override void RefreshCellVisibility() + #region Override + protected override void DoRefreshVisibleCells() { - if (rowCount <= 0) + if (this.rowCount <= 0) { - rowCount = 1; + this.rowCount = 1; } + // Reset visible count + this.visibleCount = 0; + // Viewport - float viewportInterval = scrollRect.viewport.rect.width; + float viewportInterval = this.scrollRect.viewport.rect.width; // Check content direction pivot - if (this._contentDirCoeff == 0) this._contentDirCoeff = scrollRect.content.pivot.x > 0 ? 1f : -1f; + if (this._contentDirCoeff == 0) this._contentDirCoeff = this.scrollRect.content.pivot.x > 0 ? 1f : -1f; // Set content direction - float minViewport = scrollRect.content.anchoredPosition.x * this._contentDirCoeff; + float minViewport = this.scrollRect.content.anchoredPosition.x * this._contentDirCoeff; Vector2 viewportRange = new Vector2(minViewport - extendVisibleRange, minViewport + viewportInterval + extendVisibleRange); // Hide - float contentWidth = padding.left; - for (int i = 0; i < _dataList.Count; i += rowCount) + float contentWidth = this.padding.left; + switch (this.dataOrder) { - for (int j = 0; j < rowCount; j++) - { - int index = i + j; - if (index >= _dataList.Count) - break; - var visibleRange = new Vector2(contentWidth, contentWidth + _dataList[index].cellSize.x); - if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + case DataOrder.Normal: + for (int i = 0; i < this._dataList.Count; i += this.rowCount) { - RecycleCell(index); + for (int j = 0; j < this.rowCount; j++) + { + int index = i + j; + if (index >= this._dataList.Count) + break; + var visibleRange = new Vector2(contentWidth, contentWidth + this._dataList[index].cellSize.x); + if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + { + this.RecycleCell(index); + } + } + contentWidth += this._dataList[i].cellSize.x + this.spacing.x; } - } - contentWidth += _dataList[i].cellSize.x + spacing.x; + break; + case DataOrder.Reverse: + for (int i = this._dataList.Count - 1; i >= 0; i -= this.rowCount) + { + for (int j = 0; j < this.rowCount; j++) + { + int index = i - j; + if (index < 0 || + index >= this._dataList.Count) + break; + var visibleRange = new Vector2(contentWidth, contentWidth + this._dataList[index].cellSize.x); + if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + { + this.RecycleCell(index); + } + } + contentWidth += this._dataList[i].cellSize.x + this.spacing.x; + } + break; } // Show - contentWidth = padding.left; - for (int i = 0; i < _dataList.Count; i += rowCount) + contentWidth = this.padding.left; + float lastVisibleWidth = 0f; + switch (this.dataOrder) { - for (int j = 0; j < rowCount; j++) - { - int index = i + j; - if (index >= _dataList.Count) - break; - var visibleRange = new Vector2(contentWidth, contentWidth + _dataList[index].cellSize.x); - if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + case DataOrder.Normal: + for (int i = 0; i < this._dataList.Count; i += this.rowCount) { - InfiniteCell cell = null; - if (_cellList[index] == null) + for (int j = 0; j < this.rowCount; j++) { - if (_cellPool.Count > 0) cell = _cellPool.Dequeue(); - else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + int index = i + j; + if (index >= this._dataList.Count) + break; + var visibleRange = new Vector2(contentWidth, contentWidth + this._dataList[index].cellSize.x); + if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + { + // Calcuate visible count + this.visibleCount++; + if (this.visibleCount % this.rowCount == 0) lastVisibleWidth = visibleRange.y; + + InfiniteCell cell = null; + if (this._cellList[index] == null) + { + if (this._cellPool.Count > 0) cell = this._cellPool.Dequeue(); + else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + } + // Check cell direciton pivot + float dirCoeff = 1f; + if (cell != null) dirCoeff = cell.RectTransform.pivot.x > 0 ? -1f : 1f; + this.SetupCell(cell, index, new Vector2(contentWidth * dirCoeff, (this._dataList[index].cellSize.y + this.spacing.y) * -j + -(this.padding.top - this.padding.bottom))); + if (visibleRange.y >= viewportRange.x) + this._cellList[index]?.transform.SetAsLastSibling(); + else + this._cellList[index]?.transform.SetAsFirstSibling(); + } } - // Check cell direciton pivot - float dirCoeff = 1f; - if (cell != null) dirCoeff = cell.RectTransform.pivot.x > 0 ? -1f : 1f; - SetupCell(cell, index, new Vector2(contentWidth * dirCoeff, (_dataList[index].cellSize.y + spacing.y) * -j + -(padding.top - padding.bottom))); - if (visibleRange.y >= viewportRange.x) - _cellList[index]?.transform.SetAsLastSibling(); - else - _cellList[index]?.transform.SetAsFirstSibling(); + contentWidth += this._dataList[i].cellSize.x + this.spacing.x; } - } - contentWidth += _dataList[i].cellSize.x + spacing.x; + break; + case DataOrder.Reverse: + for (int i = this._dataList.Count - 1; i >= 0; i -= this.rowCount) + { + for (int j = 0; j < this.rowCount; j++) + { + int index = i - j; + if (index < 0 || + index >= this._dataList.Count) + break; + var visibleRange = new Vector2(contentWidth, contentWidth + this._dataList[index].cellSize.x); + if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + { + // Calcuate visible count + this.visibleCount++; + if (this.visibleCount % this.rowCount == 0) lastVisibleWidth = visibleRange.y; + + InfiniteCell cell = null; + if (this._cellList[index] == null) + { + if (this._cellPool.Count > 0) cell = this._cellPool.Dequeue(); + else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + } + // Check cell direciton pivot + float dirCoeff = 1f; + if (cell != null) dirCoeff = cell.RectTransform.pivot.x > 0 ? -1f : 1f; + this.SetupCell(cell, index, new Vector2(contentWidth * dirCoeff, (this._dataList[index].cellSize.y + this.spacing.y) * -j + -(this.padding.top - this.padding.bottom))); + if (visibleRange.y >= viewportRange.x) + this._cellList[index]?.transform.SetAsLastSibling(); + else + this._cellList[index]?.transform.SetAsFirstSibling(); + } + } + contentWidth += this._dataList[i].cellSize.x + this.spacing.x; + } + break; + } + + // Calculate fill status + float visibleRangeWidth = viewportRange.y; + float visibleRangeSize = viewportRange.y - viewportRange.x; + this.isVisibleRangeFilled = lastVisibleWidth >= visibleRangeWidth; + if (this.visibleCount > this.lastMaxVisibleCount || + visibleRangeSize != this.lastVisibleRangeSize) + { + this.lastMaxVisibleCount = this.visibleCount; + this.lastVisibleRangeSize = visibleRangeSize; } // Check scroll position - if (scrollRect.content.sizeDelta.x > viewportInterval) + if (this.scrollRect.content.sizeDelta.x > viewportInterval) { - this._isAtLeft = viewportRange.x + extendVisibleRange + _dataList[0].cellSize.x <= _dataList[0].cellSize.x; - this._isAtRight = scrollRect.content.sizeDelta.x - viewportRange.y + extendVisibleRange + _dataList[_dataList.Count - 1].cellSize.x <= _dataList[_dataList.Count - 1].cellSize.x; + this._isAtLeft = viewportRange.x + extendVisibleRange + this._dataList[0].cellSize.x <= this._dataList[0].cellSize.x; + this._isAtRight = this.scrollRect.content.sizeDelta.x - viewportRange.y + extendVisibleRange + this._dataList[this._dataList.Count - 1].cellSize.x <= this._dataList[this._dataList.Count - 1].cellSize.x; } else { @@ -89,122 +172,82 @@ protected override void RefreshCellVisibility() } } + public override void Snap(int index, float duration) + { + if (!this.IsInitialized()) + return; + if (index >= this._dataList.Count || + index < 0) + return; + var columeNumber = index / this.rowCount; + float width = this.padding.left; + for (int i = 0; i < columeNumber; i++) + { + width += this._dataList[i * this.rowCount].cellSize.x + this.spacing.x; + } + + width = this.CalculateSnapPos(ScrollType.Horizontal, this.snapAlign, width, _dataList[index]); + + if (this.scrollRect.content.anchoredPosition.x != width) + { + // Check content direction pivot + this.DoSnapping(new Vector2(width * this._contentDirCoeff, 0), duration); + } + } + #endregion + + #region Sealed Override public sealed override void Refresh(bool disabledRefreshCells = false) { if (!this.IsInitialized()) return; - if (scrollRect.viewport.rect.width == 0) + if (this.scrollRect.viewport.rect.width == 0) { - DelayToRefresh(disabledRefreshCells).Forget(); + this.DoDelayRefresh(disabledRefreshCells).Forget(); } else { - DoRefresh(disabledRefreshCells); + this.DoRefresh(disabledRefreshCells); } } protected sealed override void DoRefresh(bool disabledRefreshCells) { - if (scrollRect == null) return; + if (this.scrollRect == null) return; if (!disabledRefreshCells) { // Refresh content size - float width = padding.left; - for (int i = 0; i < _dataList.Count; i += rowCount) + float width = this.padding.left; + for (int i = 0; i < this._dataList.Count; i += this.rowCount) { - width += _dataList[i].cellSize.x + spacing.x; + width += this._dataList[i].cellSize.x + this.spacing.x; } - width += padding.right; - scrollRect.content.sizeDelta = new Vector2(width, scrollRect.content.sizeDelta.y); + width += this.padding.right; + this.scrollRect.content.sizeDelta = new Vector2(width, this.scrollRect.content.sizeDelta.y); // Recycle all cells first - for (int i = 0; i < _cellList.Count; i++) + for (int i = 0; i < this._cellList.Count; i++) { - RecycleCell(i); + this.RecycleCell(i); } // Refresh cells view - this.RefreshCellVisibility(); + this.DoRefreshVisibleCells(); // Invoke onRefresh callback - onRefreshed?.Invoke(); + this.onRefreshed?.Invoke(); } // Mark flag for refresh at next scrolling else this._disabledRefreshCells = true; } - protected sealed override async UniTask DelayToRefresh(bool disabledRefreshCells) + protected sealed override async UniTask DoDelayRefresh(bool disabledRefreshCells) { await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate); - DoRefresh(disabledRefreshCells); - } - - protected sealed override void RefreshAndCheckVisibleInfo() - { - // Reset visible count - this.visibleCount = 0; - - // Viewport - float viewportInterval = scrollRect.viewport.rect.width; - - // Check content direction pivot - if (this._contentDirCoeff == 0) this._contentDirCoeff = scrollRect.content.pivot.x > 0 ? 1f : -1f; - - // Set content direction - float minViewport = scrollRect.content.anchoredPosition.x * this._contentDirCoeff; - Vector2 viewportRange = new Vector2(minViewport - extendVisibleRange, minViewport + viewportInterval + extendVisibleRange); - - // Show - float contentWidth = padding.left; - for (int i = 0; i < _dataList.Count; i += rowCount) - { - for (int j = 0; j < rowCount; j++) - { - int index = i + j; - if (index >= _dataList.Count) - break; - var visibleRange = new Vector2(contentWidth, contentWidth + _dataList[index].cellSize.x); - if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) - { - // Calcuate visible count - this.visibleCount++; - - // Check filled flag - if (_cellList[index] == null) this.isVisibleRangeFilled = false; - else this.isVisibleRangeFilled = true; - } - } - contentWidth += _dataList[i].cellSize.x + spacing.x; - } - - // Adjust filled flag while cell removing - if (this.visibleCount < this.lastMaxVisibleCount) this.isVisibleRangeFilled = false; - this.lastMaxVisibleCount = this.visibleCount; - } - - public override void Snap(int index, float duration) - { - if (!IsInitialized()) - return; - if (index >= _dataList.Count || - index < 0) - return; - var columeNumber = index / rowCount; - float width = padding.left; - for (int i = 0; i < columeNumber; i++) - { - width += _dataList[i * rowCount].cellSize.x + spacing.x; - } - - width = this.CalculateSnapPos(ScrollType.Horizontal, this.snapAlign, width, _dataList[index]); - - if (scrollRect.content.anchoredPosition.x != width) - { - // Check content direction pivot - DoSnapping(new Vector2(width * this._contentDirCoeff, 0), duration); - } + this.DoRefresh(disabledRefreshCells); } + #endregion } } diff --git a/Assets/InfiniteScrollView/Scripts/Runtime/HorizontalInfiniteScrollView.cs b/Assets/InfiniteScrollView/Scripts/Runtime/HorizontalInfiniteScrollView.cs index fe0f307..8881bb8 100644 --- a/Assets/InfiniteScrollView/Scripts/Runtime/HorizontalInfiniteScrollView.cs +++ b/Assets/InfiniteScrollView/Scripts/Runtime/HorizontalInfiniteScrollView.cs @@ -7,63 +7,129 @@ public class HorizontalInfiniteScrollView : InfiniteScrollView { public float spacing; - protected override void RefreshCellVisibility() + #region Override + protected override void DoRefreshVisibleCells() { - if (_dataList.Count == 0) - return; + // Reset visible count + this.visibleCount = 0; // Viewport - float viewportInterval = scrollRect.viewport.rect.width; + float viewportInterval = this.scrollRect.viewport.rect.width; // Check content direction pivot - if (this._contentDirCoeff == 0) this._contentDirCoeff = scrollRect.content.pivot.x > 0 ? 1f : -1f; + if (this._contentDirCoeff == 0) this._contentDirCoeff = this.scrollRect.content.pivot.x > 0 ? 1f : -1f; // Set content direction - float minViewport = scrollRect.content.anchoredPosition.x * this._contentDirCoeff; + float minViewport = this.scrollRect.content.anchoredPosition.x * this._contentDirCoeff; Vector2 viewportRange = new Vector2(minViewport - extendVisibleRange, minViewport + viewportInterval + extendVisibleRange); // Hide - float contentWidth = padding.left; - for (int i = 0; i < _dataList.Count; i++) + float contentWidth = this.padding.left; + switch (this.dataOrder) { - var visibleRange = new Vector2(contentWidth, contentWidth + _dataList[i].cellSize.x); - if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) - { - RecycleCell(i); - } - contentWidth += _dataList[i].cellSize.x + spacing; + case DataOrder.Normal: + for (int i = 0; i < this._dataList.Count; i++) + { + var visibleRange = new Vector2(contentWidth, contentWidth + this._dataList[i].cellSize.x); + if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + { + this.RecycleCell(i); + } + contentWidth += this._dataList[i].cellSize.x + this.spacing; + } + break; + case DataOrder.Reverse: + for (int i = this._dataList.Count - 1; i >= 0; i--) + { + var visibleRange = new Vector2(contentWidth, contentWidth + this._dataList[i].cellSize.x); + if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + { + this.RecycleCell(i); + } + contentWidth += this._dataList[i].cellSize.x + this.spacing; + } + break; } // Show - contentWidth = padding.left; - for (int i = 0; i < _dataList.Count; i++) + contentWidth = this.padding.left; + float lastVisibleWidth = 0f; + switch (this.dataOrder) { - var visibleRange = new Vector2(contentWidth, contentWidth + _dataList[i].cellSize.x); - if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) - { - InfiniteCell cell = null; - if (_cellList[i] == null) + case DataOrder.Normal: + for (int i = 0; i < this._dataList.Count; i++) { - if (_cellPool.Count > 0) cell = _cellPool.Dequeue(); - else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + var visibleRange = new Vector2(contentWidth, contentWidth + this._dataList[i].cellSize.x); + if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + { + // Calcuate visible count + this.visibleCount++; + lastVisibleWidth = visibleRange.y; + + InfiniteCell cell = null; + if (this._cellList[i] == null) + { + if (this._cellPool.Count > 0) cell = this._cellPool.Dequeue(); + else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + } + // Check cell direciton pivot + float dirCoeff = 1f; + if (cell != null) dirCoeff = cell.RectTransform.pivot.x > 0 ? -1f : 1f; + this.SetupCell(cell, i, new Vector2(contentWidth * dirCoeff, -(this.padding.top - this.padding.bottom))); + if (visibleRange.y >= viewportRange.x) + this._cellList[i]?.transform.SetAsLastSibling(); + else + this._cellList[i]?.transform.SetAsFirstSibling(); + } + contentWidth += this._dataList[i].cellSize.x + this.spacing; } - // Check cell direciton pivot - float dirCoeff = 1f; - if (cell != null) dirCoeff = cell.RectTransform.pivot.x > 0 ? -1f : 1f; - SetupCell(cell, i, new Vector2(contentWidth * dirCoeff, -(padding.top - padding.bottom))); - if (visibleRange.y >= viewportRange.x) - _cellList[i]?.transform.SetAsLastSibling(); - else - _cellList[i]?.transform.SetAsFirstSibling(); - } - contentWidth += _dataList[i].cellSize.x + spacing; + break; + case DataOrder.Reverse: + for (int i = this._dataList.Count - 1; i >= 0; i--) + { + var visibleRange = new Vector2(contentWidth, contentWidth + this._dataList[i].cellSize.x); + if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + { + // Calcuate visible count + this.visibleCount++; + lastVisibleWidth = visibleRange.y; + + InfiniteCell cell = null; + if (this._cellList[i] == null) + { + if (this._cellPool.Count > 0) cell = this._cellPool.Dequeue(); + else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + } + // Check cell direciton pivot + float dirCoeff = 1f; + if (cell != null) dirCoeff = cell.RectTransform.pivot.x > 0 ? -1f : 1f; + this.SetupCell(cell, i, new Vector2(contentWidth * dirCoeff, -(this.padding.top - this.padding.bottom))); + if (visibleRange.y >= viewportRange.x) + this._cellList[i]?.transform.SetAsLastSibling(); + else + this._cellList[i]?.transform.SetAsFirstSibling(); + } + contentWidth += this._dataList[i].cellSize.x + this.spacing; + } + break; + } + + // Calculate fill status + float visibleRangeWidth = viewportRange.y; + float visibleRangeSize = viewportRange.y - viewportRange.x; + this.isVisibleRangeFilled = lastVisibleWidth >= visibleRangeWidth; + if (this.visibleCount > this.lastMaxVisibleCount || + visibleRangeSize != this.lastVisibleRangeSize) + { + this.lastMaxVisibleCount = this.visibleCount; + this.lastVisibleRangeSize = visibleRangeSize; } // Check scroll position - if (scrollRect.content.sizeDelta.x > viewportInterval) + if (this.scrollRect.content.sizeDelta.x > viewportInterval) { - this._isAtLeft = viewportRange.x + extendVisibleRange + _dataList[0].cellSize.x <= _dataList[0].cellSize.x; - this._isAtRight = scrollRect.content.sizeDelta.x - viewportRange.y + extendVisibleRange + _dataList[_dataList.Count - 1].cellSize.x <= _dataList[_dataList.Count - 1].cellSize.x; + this._isAtLeft = viewportRange.x + extendVisibleRange + this._dataList[0].cellSize.x <= this._dataList[0].cellSize.x; + this._isAtRight = this.scrollRect.content.sizeDelta.x - viewportRange.y + extendVisibleRange + this._dataList[this._dataList.Count - 1].cellSize.x <= this._dataList[this._dataList.Count - 1].cellSize.x; } else { @@ -74,130 +140,96 @@ protected override void RefreshCellVisibility() } } + public override void Snap(int index, float duration) + { + if (!this.IsInitialized()) + return; + if (index >= this._dataList.Count || + index < 0) + return; + if (this.scrollRect.content.rect.width < this.scrollRect.viewport.rect.width) + return; + float width = this.padding.left; + for (int i = 0; i < index; i++) + { + width += this._dataList[i].cellSize.x + this.spacing; + } + + width = this.CalculateSnapPos(ScrollType.Horizontal, this.snapAlign, width, this._dataList[index]); + + if (this.scrollRect.content.anchoredPosition.x != width) + { + // Check content direction pivot + this.DoSnapping(new Vector2(width * this._contentDirCoeff, 0), duration); + } + } + + public override bool Remove(int index, bool withRefresh = true) + { + if (!this.IsInitialized()) + return false; + if (index >= this._dataList.Count || + index < 0) + return false; + + var removeCell = this._dataList[index]; + bool result = base.Remove(index, withRefresh); + this.scrollRect.content.anchoredPosition -= new Vector2(removeCell.cellSize.x + this.spacing, 0); + return result; + } + #endregion + + #region Sealed Override public sealed override void Refresh(bool disabledRefreshCells = false) { if (!IsInitialized()) return; - if (scrollRect.viewport.rect.width == 0) + if (this.scrollRect.viewport.rect.width == 0) { - DelayToRefresh(disabledRefreshCells).Forget(); + this.DoDelayRefresh(disabledRefreshCells).Forget(); } else { - DoRefresh(disabledRefreshCells); + this.DoRefresh(disabledRefreshCells); } } protected sealed override void DoRefresh(bool disabledRefreshCells) { - if (scrollRect == null) return; + if (this.scrollRect == null) return; if (!disabledRefreshCells) { // Refresh content size - float width = padding.left; - for (int i = 0; i < _dataList.Count; i++) + float width = this.padding.left; + for (int i = 0; i < this._dataList.Count; i++) { - width += _dataList[i].cellSize.x + spacing; + width += this._dataList[i].cellSize.x + this.spacing; } - width += padding.right; - scrollRect.content.sizeDelta = new Vector2(width, scrollRect.content.sizeDelta.y); + width += this.padding.right; + this.scrollRect.content.sizeDelta = new Vector2(width, this.scrollRect.content.sizeDelta.y); // Recycle all cells first - for (int i = 0; i < _cellList.Count; i++) + for (int i = 0; i < this._cellList.Count; i++) { - RecycleCell(i); + this.RecycleCell(i); } // Refresh cells view - this.RefreshCellVisibility(); + this.DoRefreshVisibleCells(); // Invoke onRefresh callback - onRefreshed?.Invoke(); + this.onRefreshed?.Invoke(); } // Mark flag for refresh at next scrolling else this._disabledRefreshCells = true; } - protected sealed override async UniTask DelayToRefresh(bool disabledRefreshCells) + protected sealed override async UniTask DoDelayRefresh(bool disabledRefreshCells) { await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate); - DoRefresh(disabledRefreshCells); - } - - protected override void RefreshAndCheckVisibleInfo() - { - // Reset visible count - this.visibleCount = 0; - - // Viewport - float viewportInterval = scrollRect.viewport.rect.width; - - // Check content direction pivot - if (this._contentDirCoeff == 0) this._contentDirCoeff = scrollRect.content.pivot.x > 0 ? 1f : -1f; - - // Set content direction - float minViewport = scrollRect.content.anchoredPosition.x * this._contentDirCoeff; - Vector2 viewportRange = new Vector2(minViewport - extendVisibleRange, minViewport + viewportInterval + extendVisibleRange); - - // Show - float contentWidth = padding.left; - for (int i = 0; i < _dataList.Count; i++) - { - var visibleRange = new Vector2(contentWidth, contentWidth + _dataList[i].cellSize.x); - if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) - { - // Calcuate visible count - this.visibleCount++; - - // Check filled flag - if (_cellList[i] == null) this.isVisibleRangeFilled = false; - else this.isVisibleRangeFilled = true; - } - contentWidth += _dataList[i].cellSize.x + spacing; - } - - // Adjust filled flag while cell removing - if (this.visibleCount < this.lastMaxVisibleCount) this.isVisibleRangeFilled = false; - this.lastMaxVisibleCount = this.visibleCount; - } - - public override void Snap(int index, float duration) - { - if (!IsInitialized()) - return; - if (index >= _dataList.Count || - index < 0) - return; - if (scrollRect.content.rect.width < scrollRect.viewport.rect.width) - return; - float width = padding.left; - for (int i = 0; i < index; i++) - { - width += _dataList[i].cellSize.x + spacing; - } - - width = this.CalculateSnapPos(ScrollType.Horizontal, this.snapAlign, width, _dataList[index]); - - if (scrollRect.content.anchoredPosition.x != width) - { - // Check content direction pivot - DoSnapping(new Vector2(width * this._contentDirCoeff, 0), duration); - } - } - - public override bool Remove(int index, bool withRefresh = true) - { - if (!this.IsInitialized()) - return false; - if (index >= _dataList.Count || - index < 0) - return false; - - var removeCell = _dataList[index]; - bool result = base.Remove(index, withRefresh); - scrollRect.content.anchoredPosition -= new Vector2(removeCell.cellSize.x + spacing, 0); - return result; + this.DoRefresh(disabledRefreshCells); } + #endregion } } \ No newline at end of file diff --git a/Assets/InfiniteScrollView/Scripts/Runtime/InfiniteScrollView.cs b/Assets/InfiniteScrollView/Scripts/Runtime/InfiniteScrollView.cs index 7cb3268..f7094bd 100644 --- a/Assets/InfiniteScrollView/Scripts/Runtime/InfiniteScrollView.cs +++ b/Assets/InfiniteScrollView/Scripts/Runtime/InfiniteScrollView.cs @@ -11,6 +11,12 @@ namespace InfiniteScrollViews [RequireComponent(typeof(ScrollRect))] public abstract class InfiniteScrollView : UIBehaviour { + public enum DataOrder + { + Normal, + Reverse + } + public enum SnapAlign { Start = 1, @@ -40,9 +46,15 @@ public class Padding [Space()] + [Header("------ Cell Data Options ------")] + [Tooltip("If reverse is selected, these data indexes will be reversed.")] + public DataOrder dataOrder = DataOrder.Normal; + + [Space()] + [Header("------ Cell View Options ------")] public float extendVisibleRange; - [HideInInspector] public ScrollRect scrollRect; + public ScrollRect scrollRect { get; protected set; } protected List _dataList = new List(); protected List _cellList = new List(); protected Queue _cellPool = new Queue(); @@ -53,6 +65,7 @@ public class Padding protected bool _disabledRefreshCells = false; public int visibleCount { get; protected set; } = 0; public int lastMaxVisibleCount { get; protected set; } = 0; + public float lastVisibleRangeSize { get; protected set; } = 0f; public bool isVisibleRangeFilled { get; protected set; } = false; // Direction pivot @@ -79,6 +92,7 @@ public bool isInitialized protected set; } + #region UIBehaviour protected override async void Awake() { if (this.initializePoolOnAwake) @@ -87,6 +101,14 @@ protected override async void Awake() } } + protected override void OnRectTransformDimensionsChange() + { + base.OnRectTransformDimensionsChange(); + this.onRectTransformDimensionsChanged?.Invoke(); + } + #endregion + + #region Data Info /// /// Get data count (Equals to cell count) /// @@ -95,19 +117,27 @@ public int DataCount() { return this._dataList.Count; } + #endregion + #region Initialization /// /// Init infinite-cell of scrollView /// /// public virtual async UniTask InitializePool(object args = null) { - if (isInitialized) + if (this.isInitialized) + return; + + if (this.cellPrefab == null) + { + Debug.Log("[InfiniteScrollView] Initialization failed. Cell prefab is null!!!"); return; + } - if (scrollRect == null) scrollRect = this.GetComponent(); - scrollRect.onValueChanged.RemoveAllListeners(); - scrollRect.onValueChanged.AddListener(OnValueChanged); + if (this.scrollRect == null) this.scrollRect = this.GetComponent(); + this.scrollRect.onValueChanged.RemoveAllListeners(); + this.scrollRect.onValueChanged.AddListener(OnValueChanged); // Clear children foreach (Transform trans in this.scrollRect.content) @@ -115,20 +145,22 @@ public virtual async UniTask InitializePool(object args = null) Destroy(trans.gameObject); } - _dataList.Clear(); - _cellList.Clear(); - _cellPool.Clear(); + this._dataList.Clear(); + this._cellList.Clear(); + this._cellPool.Clear(); - for (int i = 0; i < cellPoolSize; i++) + for (int i = 0; i < this.cellPoolSize; i++) { - var newCell = Instantiate(cellPrefab, scrollRect.content); + var newCell = Instantiate(this.cellPrefab, this.scrollRect.content); await newCell.OnCreate(args); newCell.gameObject.SetActive(false); - _cellPool.Enqueue(newCell); + this._cellPool.Enqueue(newCell); } - isInitialized = true; + this.isInitialized = true; } + #endregion + #region Refresh Visibility protected void OnValueChanged(Vector2 normalizedPosition) { // If ever set to false, must refresh all once @@ -137,22 +169,22 @@ protected void OnValueChanged(Vector2 normalizedPosition) this._disabledRefreshCells = false; this.Refresh(); } - else this.RefreshCellVisibilityWithCheck(); + else this.RefreshVisibleCells(); // Invoke callback this.onValueChanged?.Invoke(normalizedPosition); } - public void RefreshCellVisibilityWithCheck() + /// + /// Refresh visible cells + /// + public void RefreshVisibleCells() { if (!this.IsInitialized()) return; - this.RefreshCellVisibility(); + this.DoRefreshVisibleCells(); } - /// - /// Refresh visible cells - /// - protected abstract void RefreshCellVisibility(); + protected abstract void DoRefreshVisibleCells(); /// /// Refresh scrollView @@ -163,20 +195,20 @@ public void RefreshCellVisibilityWithCheck() protected abstract void DoRefresh(bool disabledRefreshCells); - protected abstract UniTask DelayToRefresh(bool disabledRefreshCells); - - protected abstract void RefreshAndCheckVisibleInfo(); + protected abstract UniTask DoDelayRefresh(bool disabledRefreshCells); protected bool IsInitialized() { if (!this.isInitialized) { - Debug.Log("[InfiniteScrollView] Please InitializePool first!!!"); + Debug.Log("[InfiniteScrollView] Please InitializePool first!!!"); return false; } return true; } + #endregion + #region Cell Operation /// /// Add cell data /// @@ -187,11 +219,10 @@ public virtual void Add(InfiniteCellData data, bool autoRefresh = false) { if (!this.IsInitialized()) return; - _dataList.Add(data); - _cellList.Add(null); - this.RefreshCellDataIndex(_dataList.Count - 1); + this._dataList.Add(data); + this._cellList.Add(null); + this.RefreshCellDataIndex(this._dataList.Count - 1); if (autoRefresh) this.Refresh(); - this.RefreshAndCheckVisibleInfo(); } /// @@ -205,12 +236,12 @@ public virtual bool Insert(int index, InfiniteCellData data) if (!this.IsInitialized()) return false; // Insert including max count - if (index > _dataList.Count || + if (index > this._dataList.Count || index < 0) return false; - _dataList.Insert(index, data); - _cellList.Insert(index, null); + this._dataList.Insert(index, data); + this._cellList.Insert(index, null); this.RefreshCellDataIndex(index); return true; } @@ -225,20 +256,44 @@ public virtual bool Remove(int index, bool autoRefresh = true) { if (!this.IsInitialized()) return false; - if (index >= _dataList.Count || + if (index >= this._dataList.Count || index < 0) return false; this._dataList[index].Dispose(); this._dataList.RemoveAt(index); this.RefreshCellDataIndex(index); - RecycleCell(index); - _cellList.RemoveAt(index); + this.RecycleCell(index); + this._cellList.RemoveAt(index); if (autoRefresh) this.Refresh(); - this.RefreshAndCheckVisibleInfo(); return true; } + /// + /// Clear cell data + /// + /// + public virtual void Clear() + { + if (!this.IsInitialized()) return; + + this.scrollRect.velocity = Vector2.zero; + this.scrollRect.content.anchoredPosition = Vector2.zero; + for (int i = 0; i < this._dataList.Count; i++) + { + this._dataList[i].Dispose(); + } + this._dataList.Clear(); + for (int i = 0; i < this._cellList.Count; i++) + { + this.RecycleCell(i); + } + this._cellList.Clear(); + this.Refresh(); + } + #endregion + + #region Scroll Operation /// /// Scroll to top /// @@ -275,6 +330,18 @@ public void ScrollToRight() this.scrollRect.horizontalNormalizedPosition = 1; } + public float VerticalNormalizedPosition() + { + if (this.scrollRect == null) return -1; + return this.scrollRect.verticalNormalizedPosition; + } + + public float HorizontalNormalizedPosition() + { + if (this.scrollRect == null) return -1; + return this.scrollRect.horizontalNormalizedPosition; + } + /// /// Check view to top /// @@ -324,7 +391,9 @@ public bool IsAtRight() bool result = this._contentDirCoeff > 0 ? this._isAtLeft : this._isAtRight; return result; } + #endregion + #region Snapping /// /// Move to specific cell by index /// @@ -338,7 +407,8 @@ public bool IsAtRight() /// public void SnapFirst(float duration) { - Snap(0, duration); + int index = this.dataOrder == DataOrder.Normal ? 0 : this._dataList.Count - 1; + this.Snap(index, duration); } /// @@ -347,7 +417,7 @@ public void SnapFirst(float duration) /// public void SnapMiddle(float duration) { - Snap((_dataList.Count - 1) >> 1, duration); + this.Snap((this._dataList.Count - 1) >> 1, duration); } /// @@ -356,13 +426,13 @@ public void SnapMiddle(float duration) /// public void SnapLast(float duration) { - Snap(_dataList.Count - 1, duration); + int index = this.dataOrder == DataOrder.Normal ? this._dataList.Count - 1 : 0; + this.Snap(index, duration); } protected void DoSnapping(Vector2 target, float duration) { - StopSnapping(); - + this.StopSnapping(); this._cts = new CancellationTokenSource(); this.ProcessSnapping(target, duration).Forget(); } @@ -379,27 +449,27 @@ private void StopSnapping() private async UniTask ProcessSnapping(Vector2 target, float duration) { - scrollRect.velocity = Vector2.zero; + this.scrollRect.velocity = Vector2.zero; if (duration <= 0) { - scrollRect.content.anchoredPosition = target; + this.scrollRect.content.anchoredPosition = target; if (this._disabledRefreshCells) { this._disabledRefreshCells = false; this.Refresh(); } - else this.RefreshCellVisibilityWithCheck(); + else this.RefreshVisibleCells(); } else { - Vector2 startPos = scrollRect.content.anchoredPosition; + Vector2 startPos = this.scrollRect.content.anchoredPosition; float t = 0; while (t < 1f) { t += Time.deltaTime / duration; - scrollRect.content.anchoredPosition = Vector2.Lerp(startPos, target, t); - var normalizedPos = scrollRect.normalizedPosition; + this.scrollRect.content.anchoredPosition = Vector2.Lerp(startPos, target, t); + var normalizedPos = this.scrollRect.normalizedPosition; if (normalizedPos.y < 0 || normalizedPos.x > 1) { break; @@ -418,77 +488,6 @@ private async UniTask ProcessSnapping(Vector2 target, float duration) this._cts = null; } - protected void SetupCell(InfiniteCell cell, int index, Vector2 pos) - { - if (cell != null) - { - _cellList[index] = cell; - cell.CellData = _dataList[index]; - cell.RectTransform.anchoredPosition = pos; - cell.onSelected += OnCellSelected; - cell.gameObject.SetActive(true); - } - } - - protected void RecycleCell(int index) - { - if (_cellList[index] != null) - { - var cell = _cellList[index]; - _cellList[index] = null; - cell.onSelected -= OnCellSelected; - cell.gameObject.SetActive(false); - cell.OnRecycle(); - _cellPool.Enqueue(cell); - } - } - - private void OnCellSelected(InfiniteCell selectedCell) - { - onCellSelected?.Invoke(selectedCell); - } - - /// - /// Clear cell data - /// - /// - public virtual void Clear() - { - if (!this.IsInitialized()) return; - - scrollRect.velocity = Vector2.zero; - scrollRect.content.anchoredPosition = Vector2.zero; - for (int i = 0; i < _dataList.Count; i++) - { - _dataList[i].Dispose(); - } - _dataList.Clear(); - for (int i = 0; i < _cellList.Count; i++) - { - RecycleCell(i); - } - _cellList.Clear(); - this.Refresh(); - } - - protected override void OnRectTransformDimensionsChange() - { - base.OnRectTransformDimensionsChange(); - if (scrollRect) - { - onRectTransformDimensionsChanged?.Invoke(); - } - } - - private void RefreshCellDataIndex(int beginIndex) - { - // Optimized refresh efficiency - for (int i = beginIndex; i < _dataList.Count; i++) - { - _dataList[i].index = i; - } - } - protected float CalculateSnapPos(ScrollType scrollType, SnapAlign snapPosType, float originValue, InfiniteCellData cellData) { float newValue = 0; @@ -500,16 +499,16 @@ protected float CalculateSnapPos(ScrollType scrollType, SnapAlign snapPosType, f { case ScrollType.Horizontal: - viewPortRectSizeValue = scrollRect.viewport.rect.width; - contentRectSizeValue = scrollRect.content.rect.width; + viewPortRectSizeValue = this.scrollRect.viewport.rect.width; + contentRectSizeValue = this.scrollRect.content.rect.width; cellSizeValue = cellData.cellSize.x; break; case ScrollType.Vertical: - viewPortRectSizeValue = scrollRect.viewport.rect.height; - contentRectSizeValue = scrollRect.content.rect.height; + viewPortRectSizeValue = this.scrollRect.viewport.rect.height; + contentRectSizeValue = this.scrollRect.content.rect.height; cellSizeValue = cellData.cellSize.y; break; @@ -533,5 +532,47 @@ protected float CalculateSnapPos(ScrollType scrollType, SnapAlign snapPosType, f return newValue; } + #endregion + + #region Pool Operation + protected void SetupCell(InfiniteCell cell, int index, Vector2 pos) + { + if (cell != null) + { + this._cellList[index] = cell; + cell.CellData = this._dataList[index]; + cell.RectTransform.anchoredPosition = pos; + cell.onSelected += this.OnCellSelected; + cell.gameObject.SetActive(true); + } + } + + protected void RecycleCell(int index) + { + if (this._cellList[index] != null) + { + var cell = this._cellList[index]; + this._cellList[index] = null; + cell.onSelected -= this.OnCellSelected; + cell.gameObject.SetActive(false); + cell.OnRecycle(); + this._cellPool.Enqueue(cell); + } + } + + private void OnCellSelected(InfiniteCell selectedCell) + { + this.onCellSelected?.Invoke(selectedCell); + } + + private void RefreshCellDataIndex(int beginIndex) + { + // Optimized refresh efficiency + for (int i = beginIndex; i < this._dataList.Count; i++) + { + this._dataList[i].index = i; + } + } + #endregion } } \ No newline at end of file diff --git a/Assets/InfiniteScrollView/Scripts/Runtime/VerticalGridInfiniteScrollView.cs b/Assets/InfiniteScrollView/Scripts/Runtime/VerticalGridInfiniteScrollView.cs index f74bbce..3d50010 100644 --- a/Assets/InfiniteScrollView/Scripts/Runtime/VerticalGridInfiniteScrollView.cs +++ b/Assets/InfiniteScrollView/Scripts/Runtime/VerticalGridInfiniteScrollView.cs @@ -8,77 +8,160 @@ public class VerticalGridInfiniteScrollView : InfiniteScrollView public Vector2 spacing; public int columeCount = 1; - protected override void RefreshCellVisibility() + #region Override + protected override void DoRefreshVisibleCells() { - if (columeCount <= 0) + if (this.columeCount <= 0) { - columeCount = 1; + this.columeCount = 1; } + // Reset visible count + this.visibleCount = 0; + // Viewport - float viewportInterval = scrollRect.viewport.rect.height; + float viewportInterval = this.scrollRect.viewport.rect.height; // Check content direction pivot - if (this._contentDirCoeff == 0) this._contentDirCoeff = scrollRect.content.pivot.y > 0 ? 1f : -1f; + if (this._contentDirCoeff == 0) this._contentDirCoeff = this.scrollRect.content.pivot.y > 0 ? 1f : -1f; // Set content direction - float minViewport = scrollRect.content.anchoredPosition.y * this._contentDirCoeff; + float minViewport = this.scrollRect.content.anchoredPosition.y * this._contentDirCoeff; Vector2 viewportRange = new Vector2(minViewport - extendVisibleRange, minViewport + viewportInterval + extendVisibleRange); // Hide - float contentHeight = padding.top; - for (int i = 0; i < _dataList.Count; i += columeCount) + float contentHeight = this.padding.top; + switch (this.dataOrder) { - for (int j = 0; j < columeCount; j++) - { - int index = i + j; - if (index >= _dataList.Count) - break; - var visibleRange = new Vector2(contentHeight, contentHeight + _dataList[index].cellSize.y); - if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + case DataOrder.Normal: + for (int i = 0; i < this._dataList.Count; i += this.columeCount) { - RecycleCell(index); + for (int j = 0; j < this.columeCount; j++) + { + int index = i + j; + if (index >= this._dataList.Count) + break; + var visibleRange = new Vector2(contentHeight, contentHeight + this._dataList[index].cellSize.y); + if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + { + this.RecycleCell(index); + } + } + contentHeight += this._dataList[i].cellSize.y + this.spacing.y; } - } - contentHeight += _dataList[i].cellSize.y + spacing.y; + break; + case DataOrder.Reverse: + for (int i = this._dataList.Count - 1; i >= 0; i -= this.columeCount) + { + for (int j = 0; j < this.columeCount; j++) + { + int index = i - j; + if (index < 0 || + index >= this._dataList.Count) + break; + var visibleRange = new Vector2(contentHeight, contentHeight + this._dataList[index].cellSize.y); + if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + { + this.RecycleCell(index); + } + } + contentHeight += this._dataList[i].cellSize.y + this.spacing.y; + } + break; } // Show - contentHeight = padding.top; - for (int i = 0; i < _dataList.Count; i += columeCount) + contentHeight = this.padding.top; + float lastVisibleHeight = 0f; + switch (this.dataOrder) { - for (int j = 0; j < columeCount; j++) - { - int index = i + j; - if (index >= _dataList.Count) - break; - var visibleRange = new Vector2(contentHeight, contentHeight + _dataList[index].cellSize.y); - if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + case DataOrder.Normal: + for (int i = 0; i < this._dataList.Count; i += this.columeCount) { - InfiniteCell cell = null; - if (_cellList[index] == null) + for (int j = 0; j < this.columeCount; j++) { - if (_cellPool.Count > 0) cell = _cellPool.Dequeue(); - else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + int index = i + j; + if (index >= this._dataList.Count) + break; + var visibleRange = new Vector2(contentHeight, contentHeight + this._dataList[index].cellSize.y); + if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + { + // Calcuate visible count + this.visibleCount++; + if (this.visibleCount % this.columeCount == 0) lastVisibleHeight = visibleRange.y; + + InfiniteCell cell = null; + if (this._cellList[index] == null) + { + if (this._cellPool.Count > 0) cell = this._cellPool.Dequeue(); + else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + } + // Check cell direciton pivot + float dirCoeff = 1f; + if (cell != null) dirCoeff = cell.RectTransform.pivot.y > 0 ? -1f : 1f; + this.SetupCell(cell, index, new Vector2((this._dataList[index].cellSize.x + this.spacing.x) * j + (this.padding.left - this.padding.right), contentHeight * dirCoeff)); + if (visibleRange.y >= viewportRange.x) + this._cellList[index]?.transform.SetAsLastSibling(); + else + this._cellList[index]?.transform.SetAsFirstSibling(); + } } - // Check cell direciton pivot - float dirCoeff = 1f; - if (cell != null) dirCoeff = cell.RectTransform.pivot.y > 0 ? -1f : 1f; - SetupCell(cell, index, new Vector2((_dataList[index].cellSize.x + spacing.x) * j + (padding.left - padding.right), contentHeight * dirCoeff)); - if (visibleRange.y >= viewportRange.x) - _cellList[index]?.transform.SetAsLastSibling(); - else - _cellList[index]?.transform.SetAsFirstSibling(); + contentHeight += this._dataList[i].cellSize.y + this.spacing.y; } - } - contentHeight += _dataList[i].cellSize.y + spacing.y; + break; + case DataOrder.Reverse: + for (int i = this._dataList.Count - 1; i >= 0; i -= this.columeCount) + { + for (int j = 0; j < this.columeCount; j++) + { + int index = i - j; + if (index < 0 || + index >= this._dataList.Count) + break; + var visibleRange = new Vector2(contentHeight, contentHeight + this._dataList[index].cellSize.y); + if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + { + // Calcuate visible count + this.visibleCount++; + if (this.visibleCount % this.columeCount == 0) lastVisibleHeight = visibleRange.y; + + InfiniteCell cell = null; + if (this._cellList[index] == null) + { + if (this._cellPool.Count > 0) cell = this._cellPool.Dequeue(); + else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + } + // Check cell direciton pivot + float dirCoeff = 1f; + if (cell != null) dirCoeff = cell.RectTransform.pivot.y > 0 ? -1f : 1f; + this.SetupCell(cell, index, new Vector2((this._dataList[index].cellSize.x + this.spacing.x) * j + (this.padding.left - this.padding.right), contentHeight * dirCoeff)); + if (visibleRange.y >= viewportRange.x) + this._cellList[index]?.transform.SetAsLastSibling(); + else + this._cellList[index]?.transform.SetAsFirstSibling(); + } + } + contentHeight += this._dataList[i].cellSize.y + this.spacing.y; + } + break; + } + + // Calculate fill status + float visibleRangeHeight = viewportRange.y; + float visibleRangeSize = viewportRange.y - viewportRange.x; + this.isVisibleRangeFilled = lastVisibleHeight >= visibleRangeHeight; + if (this.visibleCount > this.lastMaxVisibleCount || + visibleRangeSize != this.lastVisibleRangeSize) + { + this.lastMaxVisibleCount = this.visibleCount; + this.lastVisibleRangeSize = visibleRangeSize; } // Check scroll position - if (scrollRect.content.sizeDelta.y > viewportInterval) + if (this.scrollRect.content.sizeDelta.y > viewportInterval) { - this._isAtTop = viewportRange.x + extendVisibleRange <= _dataList[0].cellSize.y; - this._isAtBottom = scrollRect.content.sizeDelta.y - viewportRange.y + extendVisibleRange <= _dataList[_dataList.Count - 1].cellSize.y; + this._isAtTop = viewportRange.x + extendVisibleRange <= this._dataList[0].cellSize.y; + this._isAtBottom = this.scrollRect.content.sizeDelta.y - viewportRange.y + extendVisibleRange <= this._dataList[this._dataList.Count - 1].cellSize.y; } else { @@ -89,121 +172,81 @@ protected override void RefreshCellVisibility() } } + public override void Snap(int index, float duration) + { + if (!this.IsInitialized()) + return; + if (index >= this._dataList.Count || + index < 0) + return; + var rowNumber = index / this.columeCount; + float height = this.padding.top; + for (int i = 0; i < rowNumber; i++) + { + height += this._dataList[i * this.columeCount].cellSize.y + this.spacing.y; + } + + height = this.CalculateSnapPos(ScrollType.Vertical, this.snapAlign, height, this._dataList[index]); + + if (this.scrollRect.content.anchoredPosition.y != height) + { + // Check content direction pivot + this.DoSnapping(new Vector2(0, height * this._contentDirCoeff), duration); + } + } + #endregion + + #region Sealed Override public sealed override void Refresh(bool disabledRefreshCells = false) { - if (!IsInitialized()) return; + if (!this.IsInitialized()) return; - if (scrollRect.viewport.rect.height == 0) + if (this.scrollRect.viewport.rect.height == 0) { - DelayToRefresh(disabledRefreshCells).Forget(); + this.DoDelayRefresh(disabledRefreshCells).Forget(); } else { - DoRefresh(disabledRefreshCells); + this.DoRefresh(disabledRefreshCells); } } protected sealed override void DoRefresh(bool disabledRefreshCells) { - if (scrollRect == null) return; + if (this.scrollRect == null) return; if (!disabledRefreshCells) { // Refresh content size - float height = padding.top; - for (int i = 0; i < _dataList.Count; i += columeCount) + float height = this.padding.top; + for (int i = 0; i < this._dataList.Count; i += this.columeCount) { - height += _dataList[i].cellSize.y + spacing.y; + height += this._dataList[i].cellSize.y + this.spacing.y; } - height += padding.bottom; - scrollRect.content.sizeDelta = new Vector2(scrollRect.content.sizeDelta.x, height); + height += this.padding.bottom; + this.scrollRect.content.sizeDelta = new Vector2(this.scrollRect.content.sizeDelta.x, height); // Recycle all cells first for (int i = 0; i < _cellList.Count; i++) { - RecycleCell(i); + this.RecycleCell(i); } // Refresh cells view - this.RefreshCellVisibility(); + this.DoRefreshVisibleCells(); // Invoke onRefresh callback - onRefreshed?.Invoke(); + this.onRefreshed?.Invoke(); } // Mark flag for refresh at next scrolling else this._disabledRefreshCells = true; } - protected sealed override async UniTask DelayToRefresh(bool disabledRefreshCells) + protected sealed override async UniTask DoDelayRefresh(bool disabledRefreshCells) { await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate); - DoRefresh(disabledRefreshCells); - } - - protected override void RefreshAndCheckVisibleInfo() - { - // Reset visible count - this.visibleCount = 0; - - // Viewport - float viewportInterval = scrollRect.viewport.rect.height; - - // Check content direction pivot - if (this._contentDirCoeff == 0) this._contentDirCoeff = scrollRect.content.pivot.y > 0 ? 1f : -1f; - - // Set content direction - float minViewport = scrollRect.content.anchoredPosition.y * this._contentDirCoeff; - Vector2 viewportRange = new Vector2(minViewport - extendVisibleRange, minViewport + viewportInterval + extendVisibleRange); - - // Show - float contentHeight = padding.top; - for (int i = 0; i < _dataList.Count; i += columeCount) - { - for (int j = 0; j < columeCount; j++) - { - int index = i + j; - if (index >= _dataList.Count) - break; - var visibleRange = new Vector2(contentHeight, contentHeight + _dataList[index].cellSize.y); - if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) - { - // Calcuate visible count - this.visibleCount++; - - // Check filled flag - if (_cellList[index] == null) this.isVisibleRangeFilled = false; - else this.isVisibleRangeFilled = true; - } - } - contentHeight += _dataList[i].cellSize.y + spacing.y; - } - - // Adjust filled flag while cell removing - if (this.visibleCount < this.lastMaxVisibleCount) this.isVisibleRangeFilled = false; - this.lastMaxVisibleCount = this.visibleCount; - } - - public override void Snap(int index, float duration) - { - if (!IsInitialized()) - return; - if (index >= _dataList.Count || - index < 0) - return; - var rowNumber = index / columeCount; - float height = padding.top; - for (int i = 0; i < rowNumber; i++) - { - height += _dataList[i * columeCount].cellSize.y + spacing.y; - } - - height = this.CalculateSnapPos(ScrollType.Vertical, this.snapAlign, height, _dataList[index]); - - if (scrollRect.content.anchoredPosition.y != height) - { - // Check content direction pivot - DoSnapping(new Vector2(0, height * this._contentDirCoeff), duration); - } + this.DoRefresh(disabledRefreshCells); } + #endregion } } \ No newline at end of file diff --git a/Assets/InfiniteScrollView/Scripts/Runtime/VerticalInfiniteScrollView.cs b/Assets/InfiniteScrollView/Scripts/Runtime/VerticalInfiniteScrollView.cs index 88efdf8..1085e18 100644 --- a/Assets/InfiniteScrollView/Scripts/Runtime/VerticalInfiniteScrollView.cs +++ b/Assets/InfiniteScrollView/Scripts/Runtime/VerticalInfiniteScrollView.cs @@ -7,63 +7,129 @@ public class VerticalInfiniteScrollView : InfiniteScrollView { public float spacing; - protected override void RefreshCellVisibility() + #region Override + protected override void DoRefreshVisibleCells() { - if (_dataList.Count == 0) - return; + // Reset visible count + this.visibleCount = 0; // Viewport - float viewportInterval = scrollRect.viewport.rect.height; + float viewportInterval = this.scrollRect.viewport.rect.height; // Check content direction pivot - if (this._contentDirCoeff == 0) this._contentDirCoeff = scrollRect.content.pivot.y > 0 ? 1f : -1f; + if (this._contentDirCoeff == 0) this._contentDirCoeff = this.scrollRect.content.pivot.y > 0 ? 1f : -1f; // Set content direction - float minViewport = scrollRect.content.anchoredPosition.y * this._contentDirCoeff; + float minViewport = this.scrollRect.content.anchoredPosition.y * this._contentDirCoeff; Vector2 viewportRange = new Vector2(minViewport - extendVisibleRange, minViewport + viewportInterval + extendVisibleRange); // Hide - float contentHeight = padding.top; - for (int i = 0; i < _dataList.Count; i++) + float contentHeight = this.padding.top; + switch (this.dataOrder) { - var visibleRange = new Vector2(contentHeight, contentHeight + _dataList[i].cellSize.y); - if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) - { - RecycleCell(i); - } - contentHeight += _dataList[i].cellSize.y + spacing; + case DataOrder.Normal: + for (int i = 0; i < this._dataList.Count; i++) + { + var visibleRange = new Vector2(contentHeight, contentHeight + this._dataList[i].cellSize.y); + if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + { + this.RecycleCell(i); + } + contentHeight += this._dataList[i].cellSize.y + this.spacing; + } + break; + case DataOrder.Reverse: + for (int i = this._dataList.Count - 1; i >= 0; i--) + { + var visibleRange = new Vector2(contentHeight, contentHeight + this._dataList[i].cellSize.y); + if (visibleRange.y < viewportRange.x || visibleRange.x > viewportRange.y) + { + this.RecycleCell(i); + } + contentHeight += this._dataList[i].cellSize.y + this.spacing; + } + break; } // Show - contentHeight = padding.top; - for (int i = 0; i < _dataList.Count; i++) + contentHeight = this.padding.top; + float lastVisibleHeight = 0f; + switch (this.dataOrder) { - var visibleRange = new Vector2(contentHeight, contentHeight + _dataList[i].cellSize.y); - if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) - { - InfiniteCell cell = null; - if (_cellList[i] == null) + case DataOrder.Normal: + for (int i = 0; i < this._dataList.Count; i++) { - if (_cellPool.Count > 0) cell = _cellPool.Dequeue(); - else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + var visibleRange = new Vector2(contentHeight, contentHeight + this._dataList[i].cellSize.y); + if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + { + // Calcuate visible count + this.visibleCount++; + lastVisibleHeight = visibleRange.y; + + InfiniteCell cell = null; + if (this._cellList[i] == null) + { + if (this._cellPool.Count > 0) cell = this._cellPool.Dequeue(); + else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + } + // Check cell direciton pivot + float dirCoeff = 1f; + if (cell != null) dirCoeff = cell.RectTransform.pivot.y > 0 ? -1f : 1f; + this.SetupCell(cell, i, new Vector2(this.padding.left - this.padding.right, contentHeight * dirCoeff)); + if (visibleRange.y >= viewportRange.x) + this._cellList[i]?.transform.SetAsLastSibling(); + else + this._cellList[i]?.transform.SetAsFirstSibling(); + } + contentHeight += this._dataList[i].cellSize.y + this.spacing; } - // Check cell direciton pivot - float dirCoeff = 1f; - if (cell != null) dirCoeff = cell.RectTransform.pivot.y > 0 ? -1f : 1f; - SetupCell(cell, i, new Vector2(padding.left - padding.right, contentHeight * dirCoeff)); - if (visibleRange.y >= viewportRange.x) - _cellList[i]?.transform.SetAsLastSibling(); - else - _cellList[i]?.transform.SetAsFirstSibling(); - } - contentHeight += _dataList[i].cellSize.y + spacing; + break; + case DataOrder.Reverse: + for (int i = this._dataList.Count - 1; i >= 0; i--) + { + var visibleRange = new Vector2(contentHeight, contentHeight + this._dataList[i].cellSize.y); + if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) + { + // Calcuate visible count + this.visibleCount++; + lastVisibleHeight = visibleRange.y; + + InfiniteCell cell = null; + if (this._cellList[i] == null) + { + if (this._cellPool.Count > 0) cell = this._cellPool.Dequeue(); + else Debug.Log("The cell display error occurred, not enough cells in the cell pool!!!"); + } + // Check cell direciton pivot + float dirCoeff = 1f; + if (cell != null) dirCoeff = cell.RectTransform.pivot.y > 0 ? -1f : 1f; + this.SetupCell(cell, i, new Vector2(this.padding.left - this.padding.right, contentHeight * dirCoeff)); + if (visibleRange.y >= viewportRange.x) + this._cellList[i]?.transform.SetAsLastSibling(); + else + this._cellList[i]?.transform.SetAsFirstSibling(); + } + contentHeight += this._dataList[i].cellSize.y + this.spacing; + } + break; + } + + // Calculate fill status + float visibleRangeHeight = viewportRange.y; + float visibleRangeSize = viewportRange.y - viewportRange.x; + this.isVisibleRangeFilled = lastVisibleHeight >= visibleRangeHeight; + if (this.visibleCount > this.lastMaxVisibleCount || + visibleRangeSize != this.lastVisibleRangeSize) + { + this.lastMaxVisibleCount = this.visibleCount; + this.lastVisibleRangeSize = visibleRangeSize; } // Check scroll position - if (scrollRect.content.sizeDelta.y > viewportInterval) + if (this.scrollRect.content.sizeDelta.y > viewportInterval) { this._isAtTop = viewportRange.x + extendVisibleRange <= 0.001f; - this._isAtBottom = scrollRect.content.sizeDelta.y - viewportRange.y + extendVisibleRange <= 0.001f; + this._isAtBottom = this.scrollRect.content.sizeDelta.y - viewportRange.y + extendVisibleRange <= 0.001f; } else { @@ -74,13 +140,53 @@ protected override void RefreshCellVisibility() } } + public override void Snap(int index, float duration) + { + if (!this.IsInitialized()) + return; + if (index >= this._dataList.Count || + index < 0) + return; + if (this.scrollRect.content.rect.height < this.scrollRect.viewport.rect.height) + return; + float height = this.padding.top; + for (int i = 0; i < index; i++) + { + height += this._dataList[i].cellSize.y + this.spacing; + } + + height = this.CalculateSnapPos(ScrollType.Vertical, this.snapAlign, height, this._dataList[index]); + + if (this.scrollRect.content.anchoredPosition.y != height) + { + // Check content direction pivot + this.DoSnapping(new Vector2(0, height * this._contentDirCoeff), duration); + } + } + + public override bool Remove(int index, bool withRefresh = true) + { + if (!this.IsInitialized()) + return false; + if (index >= this._dataList.Count || + index < 0) + return false; + + var removeCell = this._dataList[index]; + bool result = base.Remove(index, withRefresh); + this.scrollRect.content.anchoredPosition -= new Vector2(0, removeCell.cellSize.y + this.spacing); + return result; + } + #endregion + + #region Sealed Override public sealed override void Refresh(bool disabledRefreshCells = false) { - if (!IsInitialized()) return; + if (!this.IsInitialized()) return; - if (scrollRect.viewport.rect.height == 0) + if (this.scrollRect.viewport.rect.height == 0) { - this.DelayToRefresh(disabledRefreshCells).Forget(); + this.DoDelayRefresh(disabledRefreshCells).Forget(); } else { @@ -90,114 +196,40 @@ public sealed override void Refresh(bool disabledRefreshCells = false) protected sealed override void DoRefresh(bool disabledRefreshCells) { - if (scrollRect == null) return; + if (this.scrollRect == null) return; if (!disabledRefreshCells) { // Refresh content size - float height = padding.top; - for (int i = 0; i < _dataList.Count; i++) + float height = this.padding.top; + for (int i = 0; i < this._dataList.Count; i++) { - height += _dataList[i].cellSize.y + spacing; + height += this._dataList[i].cellSize.y + this.spacing; } - height += padding.bottom; - scrollRect.content.sizeDelta = new Vector2(scrollRect.content.sizeDelta.x, height); + height += this.padding.bottom; + this.scrollRect.content.sizeDelta = new Vector2(this.scrollRect.content.sizeDelta.x, height); // Recycle all cells first - for (int i = 0; i < _cellList.Count; i++) + for (int i = 0; i < this._cellList.Count; i++) { - RecycleCell(i); + this.RecycleCell(i); } // Refresh cells view - this.RefreshCellVisibility(); + this.DoRefreshVisibleCells(); // Invoke onRefresh callback - onRefreshed?.Invoke(); + this.onRefreshed?.Invoke(); } // Mark flag for refresh at next scrolling else this._disabledRefreshCells = true; } - protected sealed override async UniTask DelayToRefresh(bool disabledRefreshCells) + protected sealed override async UniTask DoDelayRefresh(bool disabledRefreshCells) { await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate); - DoRefresh(disabledRefreshCells); - } - - protected override void RefreshAndCheckVisibleInfo() - { - // Reset visible count - this.visibleCount = 0; - - // Viewport - float viewportInterval = scrollRect.viewport.rect.height; - - // Check content direction pivot - if (this._contentDirCoeff == 0) this._contentDirCoeff = scrollRect.content.pivot.y > 0 ? 1f : -1f; - - // Set content direction - float minViewport = scrollRect.content.anchoredPosition.y * this._contentDirCoeff; - Vector2 viewportRange = new Vector2(minViewport - extendVisibleRange, minViewport + viewportInterval + extendVisibleRange); - - // Show - float contentHeight = padding.top; - for (int i = 0; i < _dataList.Count; i++) - { - var visibleRange = new Vector2(contentHeight, contentHeight + _dataList[i].cellSize.y); - if (visibleRange.y >= viewportRange.x && visibleRange.x <= viewportRange.y) - { - // Calcuate visible count - this.visibleCount++; - - // Check filled flag - if (_cellList[i] == null) this.isVisibleRangeFilled = false; - else this.isVisibleRangeFilled = true; - } - contentHeight += _dataList[i].cellSize.y + spacing; - } - - // Adjust filled flag while cell removing - if (this.visibleCount < this.lastMaxVisibleCount) this.isVisibleRangeFilled = false; - this.lastMaxVisibleCount = this.visibleCount; - } - - public override void Snap(int index, float duration) - { - if (!IsInitialized()) - return; - if (index >= _dataList.Count || - index < 0) - return; - if (scrollRect.content.rect.height < scrollRect.viewport.rect.height) - return; - float height = padding.top; - for (int i = 0; i < index; i++) - { - height += _dataList[i].cellSize.y + spacing; - } - - height = this.CalculateSnapPos(ScrollType.Vertical, this.snapAlign, height, _dataList[index]); - - if (scrollRect.content.anchoredPosition.y != height) - { - // Check content direction pivot - DoSnapping(new Vector2(0, height * this._contentDirCoeff), duration); - } - } - - public override bool Remove(int index, bool withRefresh = true) - { - if (!this.IsInitialized()) - return false; - if (index >= _dataList.Count || - index < 0) - return false; - - var removeCell = _dataList[index]; - bool result = base.Remove(index, withRefresh); - scrollRect.content.anchoredPosition -= new Vector2(0, removeCell.cellSize.y + spacing); - return result; + this.DoRefresh(disabledRefreshCells); } + #endregion } } \ No newline at end of file diff --git a/Assets/InfiniteScrollView/Scripts/Samples~/05_ChatRoom/TestGUI_05.cs b/Assets/InfiniteScrollView/Scripts/Samples~/05_ChatRoom/TestGUI_05.cs index 753ebcc..321f995 100644 --- a/Assets/InfiniteScrollView/Scripts/Samples~/05_ChatRoom/TestGUI_05.cs +++ b/Assets/InfiniteScrollView/Scripts/Samples~/05_ChatRoom/TestGUI_05.cs @@ -55,7 +55,7 @@ private void AddChatData(ChatCellData chatCellData) var chatMessageHeight = heightInstrument.preferredHeight + baseCellHeight; var infiniteData = new InfiniteCellData(new Vector2(0, chatMessageHeight), chatCellData); chatScrollView.Add(infiniteData); - if (!chatScrollView.isVisibleRangeFilled) chatScrollView.Refresh(); - else chatScrollView.Refresh(true); + // DisabledRefreshCells, if filled + chatScrollView.Refresh(chatScrollView.isVisibleRangeFilled); } -} +} \ No newline at end of file diff --git a/Assets/InfiniteScrollView/Scripts/package.json b/Assets/InfiniteScrollView/Scripts/package.json index b53e5f4..a535771 100644 --- a/Assets/InfiniteScrollView/Scripts/package.json +++ b/Assets/InfiniteScrollView/Scripts/package.json @@ -2,7 +2,7 @@ "name": "com.michaelo.infinitescrollview.unitask", "displayName": "InfiniteScrollView with UniTask", "description": "InfiniteScrollView is made for Unity extension, that support use as less as possible gameObject count to achieve large infinite scrolling content. Developed by native UGUI system, no any magical code inside, so you can easily modify and extend by yourself.", - "version": "1.4.1", + "version": "1.5.0", "unity": "2021.3", "license": "MIT", "samples": [