基于Scrollview封装的TableView,实现对视野外的Cell回收利用,减少创建Cell的开销。
核心逻辑如下:
/***************************************动态使用cell核心逻辑开始
**************************************///计算所有cell的坐标信息
private void CaculateCellPosition()
{int cellsCount = _dataSource.NumberOfCellsInTableView(this);if (cellsCount > 0){_cellsPositionsList.Clear();float currentPos = 0;for (int i = 0; i < cellsCount; i++){_cellsPositionsList.Add(currentPos);float rowSize = _dataSource.TableCellSizeForIndex(this, i);currentPos += rowSize;}_cellsPositionsList.Add(currentPos);}
}//更新内嵌容器的size
private void UpdateContentSize()
{int cellsCount = _dataSource.NumberOfCellsInTableView(this);if (cellsCount > 0){float maxPosition = _cellsPositionsList[cellsCount];if (IsHorizontal){if (maxPosition > TableViewSize.width){InnerContainerSizeDelta = maxPosition - TableViewSize.width;}}else {InnerContainerSizeDelta = maxPosition;}}
}//获取指定位置对应cell的索引
private int GetIndexFromOffset(float offset)
{int index = 0;int maxIdx = _dataSource.NumberOfCellsInTableView(this) - 1;index = this.CaculateIndexFromOffset(offset);if (index != INVALID_INDEX){index = Math.Max(0, index);if (index > maxIdx){index = INVALID_INDEX;}}return index;
}//计算指定位置对应cell的索引
private int CaculateIndexFromOffset(float offset)
{int low = 0;int high = _dataSource.NumberOfCellsInTableView(this) - 1;float search = offset;while (high >= low){int index = low + (high - low) / 2;float cellStart = _cellsPositionsList[index];float cellEnd = _cellsPositionsList[index + 1];if (search >= cellStart && search <= cellEnd){return index;}else if (search < cellStart){high = index - 1;}else{low = index + 1;}}if (low <= 0){return 0;}return INVALID_INDEX;
}//更新视野内cell的显示
private void UpdateCellsShow()
{int countOfItems = _dataSource.NumberOfCellsInTableView(this);if (0 == countOfItems){return;}int startIdx = 0, endIdx = 0, idx = 0, maxIdx = 0;float offset = this.GetContentOffset();maxIdx = Mathf.Max(countOfItems - 1, 0);endIdx = this.GetIndexFromOffset(offset);if (endIdx == INVALID_INDEX){endIdx = countOfItems - 1;}offset -= IsHorizontal ? -TableViewSize.width : this.TableViewSize.height;startIdx = this.GetIndexFromOffset(offset);if (startIdx == -1){startIdx = countOfItems - 1;}if (IsHorizontal){//横向与纵向相反int tmp = startIdx;startIdx = endIdx;endIdx = tmp;}//--_usingCellsList.Sort(new TableViewCellComparer());//--检测可回收的cell--BEGINif (_usingCellsList.Count > 0){var cell = _usingCellsList[0];idx = cell.Index;while (idx < startIdx){this.MoveCellOutOfSight(cell);if (_usingCellsList.Count > 0){cell = _usingCellsList[0];idx = cell.Index;}else{break;}}}if (_usingCellsList.Count > 0){var cell = _usingCellsList[_usingCellsList.Count - 1];idx = cell.Index;while (idx <= maxIdx && idx > endIdx){this.MoveCellOutOfSight(cell);if (_usingCellsList.Count > 0){cell = _usingCellsList[_usingCellsList.Count - 1];idx = cell.Index;}else{break;}}}//--检测可回收的cell--ENDfor (int i = startIdx; i <= endIdx; i++){if (_cellUsingIdxs.Contains(i)){continue;}this.UpdateCellByIndex(i);}
}
//更新指定cell的显示
private void UpdateCellByIndex(int index)
{TableViewCell cell = _dataSource.TableCellAtIndex(this, index);cell.SetIndex(index);cell.ClickEvent.RemoveListener(CellDidClick);cell.ClickEvent.AddListener(CellDidClick);if (cell.gameObject.activeSelf == false){cell.gameObject.SetActive(true);}//--float cellSize = _dataSource.TableCellSizeForIndex(this, index);Vector2 pos = new Vector2();if (IsHorizontal){pos.x = _cellsPositionsList[index] - InnerContainerSizeDelta * 0.5f - 0.5f * TableViewSize.width + cellSize * 0.5f;pos.y = 0;}else {pos.x = 0;pos.y = _cellsPositionsList[index] - InnerContainerSizeDelta * 0.5f + cellSize * 0.5f;}cell.gameObject.GetComponent<RectTransform>().anchoredPosition = pos;//--_usingCellsList.Add(cell);_cellUsingIdxs.Add(cell.Index);
}//回收视野外的cell
private void MoveCellOutOfSight(TableViewCell cell)
{_freedCellsStack.Push(cell);_usingCellsList.Remove(cell);_cellUsingIdxs.Remove(cell.Index);cell.ClickEvent.RemoveListener(CellDidClick);cell.ResetCell();
}/***************************************动态使用cell核心逻辑结束
**************************************/
如何使用呢?按照下面的流程操作即可。
1.创建Test 脚本,脚本继承ITableViewDataSource并实现对应的方法。【ITableViewDelegate根据具体情况决定继承与否】
public interface ITableViewDataSource{int NumberOfCellsInTableView(TableView tableView);float TableCellSizeForIndex(TableView tableView, int index);TableViewCell TableCellAtIndex(TableView tableView, int index);}
public class Test : MonoBehaviour, ITableViewDataSource, ITableViewDelegate{public TableView tableView;public GameObject hCell = null;public GameObject vCell = null;void Start(){}public int NumberOfCellsInTableView(TableView tableView){return 20;}public float TableCellSizeForIndex(TableView tableView, int index){return 90;}public TableViewCell TableCellAtIndex(TableView tableView, int index){TableViewCell cell = tableView.ReusableCell();if (cell == null){GameObject obj = Instantiate(tableView.IsHorizontal ? hCell : vCell, tableView.InnerContainerContent().transform);cell = obj.GetComponent<TableViewCell>();}cell.name = "Cell " + index;return cell;}public void TableCellClicked(TableView tableView, int index){Debug.Log("TableViewDidSelectCellForRow : " + index);}}
2.脚本挂载到载体,并绑定对应的变量【HCell和VCell只是为了方便测试横/纵向滚动】
3.创建TestCell脚本,并继承TableViewCell
public class TestCell : TableViewCell{public Text txt;public override void UpdateDisplay(){txt.text = "Index " + Index;}}
4.将TestCell脚本挂载到Scrollview中显示的预制体上
5.Test脚本中设置TableView的必要属性,调用ReloadData()接口
void Start()
{tableView.Delegate = this;tableView.DataSource = this;tableView.ReloadData();
}
效果如下:
u3d-demo
项目地址:https://github.com/jjinglover/UnityTableView