基于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