透明小地图&列表点击切换&海拔图头像位移

This commit is contained in:
lishuo committed 2022-03-29 18:41:32 +08:00
1 parent bf48989a9c
commit 6ec4c0101b
114 files changed
+12787 -6517

No files matched your search

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 954ddfcde675c304baf014480514e599
folderAsset: yes
timeCreated: 1598985910
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
//MIT License
//Copyright (c) 2020 Mohammed Iqubal Hussain
//Website : Polyandcode.com
/// <summary>
/// Interface for creating a Cell.
/// Prototype Cell must have a monobeviour inheriting from ICell
/// </summary>
namespace PolyAndCode.UI
{
public interface ICell
{
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: d4ad8716ee240469ca93bc22214fddfe
timeCreated: 1577787384
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
//MIT License
//Copyright (c) 2020 Mohammed Iqubal Hussain
//Website : Polyandcode.com
/// <summary>
/// Interface for creating DataSource
/// Recyclable Scroll Rect must be provided a Data source which must inherit from this.
/// </summary>
namespace PolyAndCode.UI
{
public interface IRecyclableScrollRectDataSource
{
int GetItemCount();
void SetCell(ICell cell, int index);
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 7ef29d4c7afcd43079f3cfc4d4e1ecbf
timeCreated: 1580410346
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,154 @@
//MIT License
//Copyright (c) 2020 Mohammed Iqubal Hussain
//Website : Polyandcode.com
using System;
using UnityEngine;
using UnityEngine.UI;
namespace PolyAndCode.UI
{
/// <summary>
/// Entry for the recycling system. Extends Unity's inbuilt ScrollRect.
/// </summary>
public class RecyclableScrollRect : ScrollRect
{
[HideInInspector]
public IRecyclableScrollRectDataSource DataSource;
public bool IsGrid;
//Prototype cell can either be a prefab or present as a child to the content(will automatically be disabled in runtime)
public RectTransform PrototypeCell;
//If true the intiziation happens at Start. Controller must assign the datasource in Awake.
//Set to false if self init is not required and use public init API.
public bool SelfInitialize = true;
public enum DirectionType
{
Vertical,
Horizontal
}
public DirectionType Direction;
//Segments : coloums for vertical and rows for horizontal.
public int Segments
{
set
{
_segments = Math.Max(value, 2);
}
get
{
return _segments;
}
}
[SerializeField]
private int _segments;
private RecyclingSystem _recyclingSystem;
private Vector2 _prevAnchoredPos;
protected override void Start()
{
//defafult(built-in) in scroll rect can have both directions enabled, Recyclable scroll rect can be scrolled in only one direction.
//setting default as vertical, Initialize() will set this again.
vertical = true;
horizontal = false;
if (!Application.isPlaying) return;
if (SelfInitialize) Initialize();
}
/// <summary>
/// Initialization when selfInitalize is true. Assumes that data source is set in controller's Awake.
/// </summary>
private void Initialize()
{
//Contruct the recycling system.
if (Direction == DirectionType.Vertical)
{
_recyclingSystem = new VerticalRecyclingSystem(PrototypeCell, viewport, content, DataSource, IsGrid, Segments);
}
else if (Direction == DirectionType.Horizontal)
{
_recyclingSystem = new HorizontalRecyclingSystem(PrototypeCell, viewport, content, DataSource, IsGrid, Segments);
}
vertical = Direction == DirectionType.Vertical;
horizontal = Direction == DirectionType.Horizontal;
_prevAnchoredPos = content.anchoredPosition;
onValueChanged.RemoveListener(OnValueChangedListener);
//Adding listener after pool creation to avoid any unwanted recycling behaviour.(rare scenerio)
StartCoroutine(_recyclingSystem.InitCoroutine(() =>
onValueChanged.AddListener(OnValueChangedListener)
));
}
/// <summary>
/// public API for Initializing when datasource is not set in controller's Awake. Make sure selfInitalize is set to false.
/// </summary>
public void Initialize(IRecyclableScrollRectDataSource dataSource)
{
DataSource = dataSource;
Initialize();
}
/// <summary>
/// Added as a listener to the OnValueChanged event of Scroll rect.
/// Recycling entry point for recyling systems.
/// </summary>
/// <param name="direction">scroll direction</param>
public void OnValueChangedListener(Vector2 normalizedPos)
{
Vector2 dir = content.anchoredPosition - _prevAnchoredPos;
m_ContentStartPosition += _recyclingSystem.OnValueChangedListener(dir);
_prevAnchoredPos = content.anchoredPosition;
}
/// <summary>
///Reloads the data. Call this if a new datasource is assigned.
/// </summary>
public void ReloadData()
{
ReloadData(DataSource);
}
/// <summary>
/// Overloaded ReloadData with dataSource param
///Reloads the data. Call this if a new datasource is assigned.
/// </summary>
public void ReloadData(IRecyclableScrollRectDataSource dataSource)
{
if (_recyclingSystem != null)
{
StopMovement();
onValueChanged.RemoveListener(OnValueChangedListener);
_recyclingSystem.DataSource = dataSource;
StartCoroutine(_recyclingSystem.InitCoroutine(() =>
onValueChanged.AddListener(OnValueChangedListener)
));
_prevAnchoredPos = content.anchoredPosition;
}
}
/*
#region Testing
private void OnDrawGizmos()
{
if (_recyclableScrollRect is VerticalRecyclingSystem)
{
((VerticalRecyclingSystem)_recyclableScrollRect).OnDrawGizmos();
}
if (_recyclableScrollRect is HorizontalRecyclingSystem)
{
((HorizontalRecyclingSystem)_recyclableScrollRect).OnDrawGizmos();
}
}
#endregion
*/
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 248d9fa993d3e44daa165a75b0063088
timeCreated: 1580416764
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 6b7325ceb7a524d43bf38b93ad0c7118, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d1b6a61b2a8a958489c9e8598a56feda
folderAsset: yes
timeCreated: 1598985947
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,383 @@
//MIT License
//Copyright (c) 2020 Mohammed Iqubal Hussain
//Website : Polyandcode.com
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace PolyAndCode.UI
{
/// <summary>
/// Recyling system for horizontal type.
/// </summary>
public class HorizontalRecyclingSystem : RecyclingSystem
{
//Assigned by constructor
private readonly int _rows;
//Cell dimensions
private float _cellWidth, _cellHeight;
//Pool Generation
private List<RectTransform> _cellPool;
private List<ICell> _cachedCells;
private Bounds _recyclableViewBounds;
//Temps, Flags
private readonly Vector3[] _corners = new Vector3[4];
private bool _recycling;
//Trackers
private int currentItemCount; //item count corresponding to the datasource.
private int leftMostCellIndex, rightMostCellIndex; //Topmost and bottommost cell in the List
private int _leftMostCellRow, _RightMostCellRow; // used for recyling in Grid layout. leftmost and rightmost row
//Cached zero vector
private Vector2 zeroVector = Vector2.zero;
#region INIT
public HorizontalRecyclingSystem(RectTransform prototypeCell, RectTransform viewport, RectTransform content, IRecyclableScrollRectDataSource dataSource, bool isGrid, int rows)
{
PrototypeCell = prototypeCell;
Viewport = viewport;
Content = content;
DataSource = dataSource;
IsGrid = isGrid;
_rows = isGrid ? rows : 1;
_recyclableViewBounds = new Bounds();
}
/// <summary>
/// Corotuine for initiazation.
/// Using coroutine for init because few UI stuff requires a frame to update
/// </summary>
/// <param name="onInitialized">callback when init done</param>
/// <returns></returns>
public override IEnumerator InitCoroutine(Action onInitialized)
{
//Setting up container and bounds
SetLeftAnchor(Content);
Content.anchoredPosition = Vector3.zero;
yield return null;
SetRecyclingBounds();
//Cell Poool
CreateCellPool();
currentItemCount = _cellPool.Count;
leftMostCellIndex = 0;
rightMostCellIndex = _cellPool.Count - 1;
//Set content width according to no of coloums
int coloums = Mathf.CeilToInt((float)_cellPool.Count / _rows);
float contentXSize = coloums * _cellWidth;
Content.sizeDelta = new Vector2(contentXSize, Content.sizeDelta.y);
SetLeftAnchor(Content);
if (onInitialized != null) onInitialized();
}
/// <summary>
/// Sets the uppper and lower bounds for recycling cells.
/// </summary>
private void SetRecyclingBounds()
{
Viewport.GetWorldCorners(_corners);
float threshHold = RecyclingThreshold * (_corners[2].x - _corners[0].x);
_recyclableViewBounds.min = new Vector3(_corners[0].x - threshHold, _corners[0].y);
_recyclableViewBounds.max = new Vector3(_corners[2].x + threshHold, _corners[2].y);
}
/// <summary>
/// Creates cell Pool for recycling, Caches ICells
/// </summary>
private void CreateCellPool()
{
//Reseting Pool
if (_cellPool != null)
{
_cellPool.ForEach((RectTransform item) => UnityEngine.Object.Destroy(item.gameObject));
_cellPool.Clear();
_cachedCells.Clear();
}
else
{
_cachedCells = new List<ICell>();
_cellPool = new List<RectTransform>();
}
//Set the prototype cell active and set cell anchor as top
PrototypeCell.gameObject.SetActive(true);
SetLeftAnchor(PrototypeCell);
//set new cell size according to its aspect ratio
_cellHeight = Content.rect.height / _rows;
_cellWidth = PrototypeCell.sizeDelta.x / PrototypeCell.sizeDelta.y * _cellHeight;
//Reset
_leftMostCellRow = _RightMostCellRow = 0;
//Temps
float currentPoolCoverage = 0;
int poolSize = 0;
float posX = 0;
float posY = 0;
//Get the required pool coverage and mininum size for the Cell pool
float requriedCoverage = MinPoolCoverage * Viewport.rect.width;
int minPoolSize = Math.Min(MinPoolSize, DataSource.GetItemCount());
//create cells untill the Pool area is covered and pool size is the minimum required
while ((poolSize < minPoolSize || currentPoolCoverage < requriedCoverage) && poolSize < DataSource.GetItemCount())
{
//Instantiate and add to Pool
RectTransform item = (UnityEngine.Object.Instantiate(PrototypeCell.gameObject)).GetComponent<RectTransform>();
item.name = "Cell";
item.sizeDelta = new Vector2(_cellWidth, _cellHeight);
_cellPool.Add(item);
item.SetParent(Content, false);
if (IsGrid)
{
posY = -_RightMostCellRow * _cellHeight;
item.anchoredPosition = new Vector2(posX, posY);
if (++_RightMostCellRow >= _rows)
{
_RightMostCellRow = 0;
posX += _cellWidth;
currentPoolCoverage += item.rect.width;
}
}
else
{
item.anchoredPosition = new Vector2(posX, 0);
posX = item.anchoredPosition.x + item.rect.width;
currentPoolCoverage += item.rect.width;
}
//Setting data for Cell
_cachedCells.Add(item.GetComponent<ICell>());
DataSource.SetCell(_cachedCells[_cachedCells.Count - 1], poolSize);
//Update the Pool size
poolSize++;
}
if (IsGrid)
{
_RightMostCellRow = (_RightMostCellRow - 1 + _rows) % _rows;
}
//Deactivate prototype cell if it is not a prefab(i.e it's present in scene)
if (PrototypeCell.gameObject.scene.IsValid())
{
PrototypeCell.gameObject.SetActive(false);
}
}
#endregion
#region RECYCLING
/// <summary>
/// Recyling entry point
/// </summary>
/// <param name="direction">scroll direction </param>
/// <returns></returns>
public override Vector2 OnValueChangedListener(Vector2 direction)
{
if (_recycling || _cellPool == null || _cellPool.Count == 0) return zeroVector;
//Updating Recyclable view bounds since it can change with resolution changes.
SetRecyclingBounds();
if (direction.x < 0 && _cellPool[rightMostCellIndex].MinX() < _recyclableViewBounds.max.x)
{
return RecycleLeftToRight();
}
else if (direction.x > 0 && _cellPool[leftMostCellIndex].MaxX() > _recyclableViewBounds.min.x)
{
return RecycleRightToleft();
}
return zeroVector;
}
/// <summary>
/// Recycles cells from Left to Right in the List heirarchy
/// </summary>
private Vector2 RecycleLeftToRight()
{
_recycling = true;
int n = 0;
float posX = IsGrid ? _cellPool[rightMostCellIndex].anchoredPosition.x : 0;
float posY = 0;
//to determine if content size needs to be updated
int additionalColoums = 0;
//Recycle until cell at left is avaiable and current item count smaller than datasource
while (_cellPool[leftMostCellIndex].MaxX() < _recyclableViewBounds.min.x && currentItemCount < DataSource.GetItemCount())
{
if (IsGrid)
{
if (++_RightMostCellRow >= _rows)
{
n++;
_RightMostCellRow = 0;
posX = _cellPool[rightMostCellIndex].anchoredPosition.x + _cellWidth;
additionalColoums++;
}
//Move Left most cell to right
posY = -_RightMostCellRow * _cellHeight;
_cellPool[leftMostCellIndex].anchoredPosition = new Vector2(posX, posY);
if (++_leftMostCellRow >= _rows)
{
_leftMostCellRow = 0;
additionalColoums--;
}
}
else
{
//Move Left most cell to right
posX = _cellPool[rightMostCellIndex].anchoredPosition.x + _cellPool[rightMostCellIndex].sizeDelta.x;
_cellPool[leftMostCellIndex].anchoredPosition = new Vector2(posX, _cellPool[leftMostCellIndex].anchoredPosition.y);
}
//Cell for row at
DataSource.SetCell(_cachedCells[leftMostCellIndex], currentItemCount);
//set new indices
rightMostCellIndex = leftMostCellIndex;
leftMostCellIndex = (leftMostCellIndex + 1) % _cellPool.Count;
currentItemCount++;
if (!IsGrid) n++;
}
//Content size adjustment
if (IsGrid)
{
Content.sizeDelta += additionalColoums * Vector2.right * _cellWidth;
if (additionalColoums > 0)
{
n -= additionalColoums;
}
}
//Content anchor position adjustment.
_cellPool.ForEach((RectTransform cell) => cell.anchoredPosition -= n * Vector2.right * _cellPool[leftMostCellIndex].sizeDelta.x);
Content.anchoredPosition += n * Vector2.right * _cellPool[leftMostCellIndex].sizeDelta.x;
_recycling = false;
return n * Vector2.right * _cellPool[leftMostCellIndex].sizeDelta.x;
}
/// <summary>
/// Recycles cells from Right to Left in the List heirarchy
/// </summary>
private Vector2 RecycleRightToleft()
{
_recycling = true;
int n = 0;
float posX = IsGrid ? _cellPool[leftMostCellIndex].anchoredPosition.x : 0;
float posY = 0;
//to determine if content size needs to be updated
int additionalColoums = 0;
//Recycle until cell at Right end is avaiable and current item count is greater than cellpool size
while (_cellPool[rightMostCellIndex].MinX() > _recyclableViewBounds.max.x && currentItemCount > _cellPool.Count)
{
if (IsGrid)
{
if (--_leftMostCellRow < 0)
{
n++;
_leftMostCellRow = _rows - 1;
posX = _cellPool[leftMostCellIndex].anchoredPosition.x - _cellWidth;
additionalColoums++;
}
//Move Right most cell to left
posY = -_leftMostCellRow * _cellHeight;
_cellPool[rightMostCellIndex].anchoredPosition = new Vector2(posX, posY);
if (--_RightMostCellRow < 0)
{
_RightMostCellRow = _rows - 1;
additionalColoums--;
}
}
else
{
//Move Right most cell to left
posX = _cellPool[leftMostCellIndex].anchoredPosition.x - _cellPool[leftMostCellIndex].sizeDelta.x;
_cellPool[rightMostCellIndex].anchoredPosition = new Vector2(posX, _cellPool[rightMostCellIndex].anchoredPosition.y);
n++;
}
currentItemCount--;
//Cell for row at
DataSource.SetCell(_cachedCells[rightMostCellIndex], currentItemCount - _cellPool.Count);
//set new indices
leftMostCellIndex = rightMostCellIndex;
rightMostCellIndex = (rightMostCellIndex - 1 + _cellPool.Count) % _cellPool.Count;
}
//Content size adjustment
if (IsGrid)
{
Content.sizeDelta += additionalColoums * Vector2.right * _cellWidth;
if (additionalColoums > 0)
{
n -= additionalColoums;
}
}
//Content anchor position adjustment.
_cellPool.ForEach((RectTransform cell) => cell.anchoredPosition += n * Vector2.right * _cellPool[leftMostCellIndex].sizeDelta.x);
Content.anchoredPosition -= n * Vector2.right * _cellPool[leftMostCellIndex].sizeDelta.x;
_recycling = false;
return -n * Vector2.right * _cellPool[leftMostCellIndex].sizeDelta.x;
}
#endregion
#region HELPERS
/// <summary>
/// Anchoring cell and content rect transforms to top preset. Makes repositioning easy.
/// </summary>
/// <param name="rectTransform"></param>
private void SetLeftAnchor(RectTransform rectTransform)
{
//Saving to reapply after anchoring. Width and height changes if anchoring is change.
float width = rectTransform.rect.width;
float height = rectTransform.rect.height;
Vector2 pos = IsGrid ? new Vector2(0, 1) : new Vector2(0, 0.5f);
//Setting top anchor
rectTransform.anchorMin = pos;
rectTransform.anchorMax = pos;
rectTransform.pivot = pos;
//Reapply size
rectTransform.sizeDelta = new Vector2(width, height);
}
#endregion
#region TESTING
public void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawLine(_recyclableViewBounds.min - new Vector3(0, 2000), _recyclableViewBounds.min + new Vector3(0, 2000));
Gizmos.color = Color.red;
Gizmos.DrawLine(_recyclableViewBounds.max - new Vector3(0, 2000), _recyclableViewBounds.max + new Vector3(0, 2000));
}
#endregion
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 18e7b2b6055ccc94590a39b5401147a8
timeCreated: 1595684755
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,29 @@
//MIT License
//Copyright (c) 2020 Mohammed Iqubal Hussain
//Website : Polyandcode.com
using System.Collections;
using UnityEngine;
namespace PolyAndCode.UI
{
/// <summary>
/// Absract Class for creating a Recycling system.
/// </summary>
public abstract class RecyclingSystem
{
public IRecyclableScrollRectDataSource DataSource;
protected RectTransform Viewport, Content;
protected RectTransform PrototypeCell;
protected bool IsGrid;
protected float MinPoolCoverage = 1.5f; // The recyclable pool must cover (viewPort * _poolCoverage) area.
protected int MinPoolSize = 10; // Cell pool must have a min size
protected float RecyclingThreshold = .2f; //Threshold for recycling above and below viewport
public abstract IEnumerator InitCoroutine(System.Action onInitialized = null);
public abstract Vector2 OnValueChangedListener(Vector2 direction);
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 52518c8e74351d0469a587c411d4438f
timeCreated: 1595684222
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,404 @@
//MIT License
//Copyright (c) 2020 Mohammed Iqubal Hussain
//Website : Polyandcode.com
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace PolyAndCode.UI
{
/// <summary>
/// Recyling system for Vertical type.
/// </summary>
public class VerticalRecyclingSystem : RecyclingSystem
{
//Assigned by constructor
private readonly int _coloumns;
//Cell dimensions
private float _cellWidth, _cellHeight;
//Pool Generation
private List<RectTransform> _cellPool;
private List<ICell> _cachedCells;
private Bounds _recyclableViewBounds;
//Temps, Flags
private readonly Vector3[] _corners = new Vector3[4];
private bool _recycling;
//Trackers
private int currentItemCount; //item count corresponding to the datasource.
private int topMostCellIndex, bottomMostCellIndex; //Topmost and bottommost cell in the heirarchy
private int _topMostCellColoumn, _bottomMostCellColoumn; // used for recyling in Grid layout. top-most and bottom-most coloumn
//Cached zero vector
private Vector2 zeroVector = Vector2.zero;
#region INIT
public VerticalRecyclingSystem(RectTransform prototypeCell, RectTransform viewport, RectTransform content, IRecyclableScrollRectDataSource dataSource, bool isGrid, int coloumns)
{
PrototypeCell = prototypeCell;
Viewport = viewport;
Content = content;
DataSource = dataSource;
IsGrid = isGrid;
_coloumns = isGrid ? coloumns : 1;
_recyclableViewBounds = new Bounds();
}
/// <summary>
/// Corotuine for initiazation.
/// Using coroutine for init because few UI stuff requires a frame to update
/// </summary>
/// <param name="onInitialized">callback when init done</param>
/// <returns></returns>>
public override IEnumerator InitCoroutine(System.Action onInitialized)
{
SetTopAnchor(Content);
Content.anchoredPosition = Vector3.zero;
yield return null;
SetRecyclingBounds();
//Cell Poool
CreateCellPool();
currentItemCount = _cellPool.Count;
topMostCellIndex = 0;
bottomMostCellIndex = _cellPool.Count - 1;
//Set content height according to no of rows
int noOfRows = (int)Mathf.Ceil((float)_cellPool.Count / (float)_coloumns);
float contentYSize = noOfRows * _cellHeight;
Content.sizeDelta = new Vector2(Content.sizeDelta.x, contentYSize);
SetTopAnchor(Content);
if (onInitialized != null) onInitialized();
}
/// <summary>
/// Sets the uppper and lower bounds for recycling cells.
/// </summary>
private void SetRecyclingBounds()
{
Viewport.GetWorldCorners(_corners);
float threshHold = RecyclingThreshold * (_corners[2].y - _corners[0].y);
_recyclableViewBounds.min = new Vector3(_corners[0].x, _corners[0].y - threshHold);
_recyclableViewBounds.max = new Vector3(_corners[2].x, _corners[2].y + threshHold);
}
/// <summary>
/// Creates cell Pool for recycling, Caches ICells
/// </summary>
private void CreateCellPool()
{
//Reseting Pool
if (_cellPool != null)
{
_cellPool.ForEach((RectTransform item) => UnityEngine.Object.Destroy(item.gameObject));
_cellPool.Clear();
_cachedCells.Clear();
}
else
{
_cachedCells = new List<ICell>();
_cellPool = new List<RectTransform>();
}
//Set the prototype cell active and set cell anchor as top
PrototypeCell.gameObject.SetActive(true);
if (IsGrid)
{
SetTopLeftAnchor(PrototypeCell);
}
else
{
SetTopAnchor(PrototypeCell);
}
//Reset
_topMostCellColoumn = _bottomMostCellColoumn = 0;
//Temps
float currentPoolCoverage = 0;
int poolSize = 0;
float posX = 0;
float posY = 0;
//set new cell size according to its aspect ratio
_cellWidth = Content.rect.width / _coloumns;
_cellHeight = PrototypeCell.sizeDelta.y / PrototypeCell.sizeDelta.x * _cellWidth;
//Get the required pool coverage and mininum size for the Cell pool
float requriedCoverage = MinPoolCoverage * Viewport.rect.height;
int minPoolSize = Math.Min(MinPoolSize, DataSource.GetItemCount());
//create cells untill the Pool area is covered and pool size is the minimum required
while ((poolSize < minPoolSize || currentPoolCoverage < requriedCoverage) && poolSize < DataSource.GetItemCount())
{
//Instantiate and add to Pool
RectTransform item = (UnityEngine.Object.Instantiate(PrototypeCell.gameObject)).GetComponent<RectTransform>();
item.name = "Cell";
item.sizeDelta = new Vector2(_cellWidth, _cellHeight);
_cellPool.Add(item);
item.SetParent(Content, false);
if (IsGrid)
{
posX = _bottomMostCellColoumn * _cellWidth;
item.anchoredPosition = new Vector2(posX, posY);
if (++_bottomMostCellColoumn >= _coloumns)
{
_bottomMostCellColoumn = 0;
posY -= _cellHeight;
currentPoolCoverage += item.rect.height;
}
}
else
{
item.anchoredPosition = new Vector2(0, posY);
posY = item.anchoredPosition.y - item.rect.height;
currentPoolCoverage += item.rect.height;
}
//Setting data for Cell
_cachedCells.Add(item.GetComponent<ICell>());
DataSource.SetCell(_cachedCells[_cachedCells.Count - 1], poolSize);
//Update the Pool size
poolSize++;
}
//TODO : you alrady have a _currentColoumn varaiable. Why this calculation?????
if (IsGrid)
{
_bottomMostCellColoumn = (_bottomMostCellColoumn - 1 + _coloumns) % _coloumns;
}
//Deactivate prototype cell if it is not a prefab(i.e it's present in scene)
if (PrototypeCell.gameObject.scene.IsValid())
{
PrototypeCell.gameObject.SetActive(false);
}
}
#endregion
#region RECYCLING
/// <summary>
/// Recyling entry point
/// </summary>
/// <param name="direction">scroll direction </param>
/// <returns></returns>
public override Vector2 OnValueChangedListener(Vector2 direction)
{
if (_recycling || _cellPool == null || _cellPool.Count == 0) return zeroVector;
//Updating Recyclable view bounds since it can change with resolution changes.
SetRecyclingBounds();
if (direction.y > 0 && _cellPool[bottomMostCellIndex].MaxY() > _recyclableViewBounds.min.y)
{
return RecycleTopToBottom();
}
else if (direction.y < 0 && _cellPool[topMostCellIndex].MinY() < _recyclableViewBounds.max.y)
{
return RecycleBottomToTop();
}
return zeroVector;
}
/// <summary>
/// Recycles cells from top to bottom in the List heirarchy
/// </summary>
private Vector2 RecycleTopToBottom()
{
_recycling = true;
int n = 0;
float posY = IsGrid ? _cellPool[bottomMostCellIndex].anchoredPosition.y : 0;
float posX = 0;
//to determine if content size needs to be updated
int additionalRows = 0;
//Recycle until cell at Top is avaiable and current item count smaller than datasource
while (_cellPool[topMostCellIndex].MinY() > _recyclableViewBounds.max.y && currentItemCount < DataSource.GetItemCount())
{
if (IsGrid)
{
if (++_bottomMostCellColoumn >= _coloumns)
{
n++;
_bottomMostCellColoumn = 0;
posY = _cellPool[bottomMostCellIndex].anchoredPosition.y - _cellHeight;
additionalRows++;
}
//Move top cell to bottom
posX = _bottomMostCellColoumn * _cellWidth;
_cellPool[topMostCellIndex].anchoredPosition = new Vector2(posX, posY);
if (++_topMostCellColoumn >= _coloumns)
{
_topMostCellColoumn = 0;
additionalRows--;
}
}
else
{
//Move top cell to bottom
posY = _cellPool[bottomMostCellIndex].anchoredPosition.y - _cellPool[bottomMostCellIndex].sizeDelta.y;
_cellPool[topMostCellIndex].anchoredPosition = new Vector2(_cellPool[topMostCellIndex].anchoredPosition.x, posY);
}
//Cell for row at
DataSource.SetCell(_cachedCells[topMostCellIndex], currentItemCount);
//set new indices
bottomMostCellIndex = topMostCellIndex;
topMostCellIndex = (topMostCellIndex + 1) % _cellPool.Count;
currentItemCount++;
if (!IsGrid) n++;
}
//Content size adjustment
if (IsGrid)
{
Content.sizeDelta += additionalRows * Vector2.up * _cellHeight;
//TODO : check if it is supposed to be done only when > 0
if (additionalRows > 0)
{
n -= additionalRows;
}
}
//Content anchor position adjustment.
_cellPool.ForEach((RectTransform cell) => cell.anchoredPosition += n * Vector2.up * _cellPool[topMostCellIndex].sizeDelta.y);
Content.anchoredPosition -= n * Vector2.up * _cellPool[topMostCellIndex].sizeDelta.y;
_recycling = false;
return -new Vector2(0, n * _cellPool[topMostCellIndex].sizeDelta.y);
}
/// <summary>
/// Recycles cells from bottom to top in the List heirarchy
/// </summary>
private Vector2 RecycleBottomToTop()
{
_recycling = true;
int n = 0;
float posY = IsGrid ? _cellPool[topMostCellIndex].anchoredPosition.y : 0;
float posX = 0;
//to determine if content size needs to be updated
int additionalRows = 0;
//Recycle until cell at bottom is avaiable and current item count is greater than cellpool size
while (_cellPool[bottomMostCellIndex].MaxY() < _recyclableViewBounds.min.y && currentItemCount > _cellPool.Count)
{
if (IsGrid)
{
if (--_topMostCellColoumn < 0)
{
n++;
_topMostCellColoumn = _coloumns - 1;
posY = _cellPool[topMostCellIndex].anchoredPosition.y + _cellHeight;
additionalRows++;
}
//Move bottom cell to top
posX = _topMostCellColoumn * _cellWidth;
_cellPool[bottomMostCellIndex].anchoredPosition = new Vector2(posX, posY);
if (--_bottomMostCellColoumn < 0)
{
_bottomMostCellColoumn = _coloumns - 1;
additionalRows--;
}
}
else
{
//Move bottom cell to top
posY = _cellPool[topMostCellIndex].anchoredPosition.y + _cellPool[topMostCellIndex].sizeDelta.y;
_cellPool[bottomMostCellIndex].anchoredPosition = new Vector2(_cellPool[bottomMostCellIndex].anchoredPosition.x, posY);
n++;
}
currentItemCount--;
//Cell for row at
DataSource.SetCell(_cachedCells[bottomMostCellIndex], currentItemCount - _cellPool.Count);
//set new indices
topMostCellIndex = bottomMostCellIndex;
bottomMostCellIndex = (bottomMostCellIndex - 1 + _cellPool.Count) % _cellPool.Count;
}
if (IsGrid)
{
Content.sizeDelta += additionalRows * Vector2.up * _cellHeight;
//TODOL : check if it is supposed to be done only when > 0
if (additionalRows > 0)
{
n -= additionalRows;
}
}
_cellPool.ForEach((RectTransform cell) => cell.anchoredPosition -= n * Vector2.up * _cellPool[topMostCellIndex].sizeDelta.y);
Content.anchoredPosition += n * Vector2.up * _cellPool[topMostCellIndex].sizeDelta.y;
_recycling = false;
return new Vector2(0, n * _cellPool[topMostCellIndex].sizeDelta.y);
}
#endregion
#region HELPERS
/// <summary>
/// Anchoring cell and content rect transforms to top preset. Makes repositioning easy.
/// </summary>
/// <param name="rectTransform"></param>
private void SetTopAnchor(RectTransform rectTransform)
{
//Saving to reapply after anchoring. Width and height changes if anchoring is change.
float width = rectTransform.rect.width;
float height = rectTransform.rect.height;
//Setting top anchor
rectTransform.anchorMin = new Vector2(0.5f, 1);
rectTransform.anchorMax = new Vector2(0.5f, 1);
rectTransform.pivot = new Vector2(0.5f, 1);
//Reapply size
rectTransform.sizeDelta = new Vector2(width, height);
}
private void SetTopLeftAnchor(RectTransform rectTransform)
{
//Saving to reapply after anchoring. Width and height changes if anchoring is change.
float width = rectTransform.rect.width;
float height = rectTransform.rect.height;
//Setting top anchor
rectTransform.anchorMin = new Vector2(0, 1);
rectTransform.anchorMax = new Vector2(0, 1);
rectTransform.pivot = new Vector2(0, 1);
//Reapply size
rectTransform.sizeDelta = new Vector2(width, height);
}
#endregion
#region TESTING
public void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawLine(_recyclableViewBounds.min - new Vector3(2000, 0), _recyclableViewBounds.min + new Vector3(2000, 0));
Gizmos.color = Color.red;
Gizmos.DrawLine(_recyclableViewBounds.max - new Vector3(2000, 0), _recyclableViewBounds.max + new Vector3(2000, 0));
}
#endregion
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 360d60b04d7e6a64ab1104c09a3335b0
timeCreated: 1595684786
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 50cfd769583befc46a5b6c78d9d6f4b8
folderAsset: yes
timeCreated: 1598985967
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
//MIT License
//Copyright (c) 2020 Mohammed Iqubal Hussain
//Website : Polyandcode.com
using UnityEngine;
/// <summary>
/// Extension methods for Rect Transform
/// </summary>
public static class UIExtension
{
public static Vector3[] GetCorners(this RectTransform rectTransform)
{
Vector3[] corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
return corners;
}
public static float MaxY(this RectTransform rectTransform)
{
return rectTransform.GetCorners()[1].y;
}
public static float MinY(this RectTransform rectTransform)
{
return rectTransform.GetCorners()[0].y;
}
public static float MaxX(this RectTransform rectTransform)
{
return rectTransform.GetCorners()[2].x;
}
public static float MinX(this RectTransform rectTransform)
{
return rectTransform.GetCorners()[0].x;
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 12216d152b4b943e88a3d40b029c3e3e
timeCreated: 1580410439
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: