基于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--BEGIN
if (_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--END
for (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