为什么需要无限滑动:
1:背包,玩家如果获取了1000个道具,如果没有无限滑动的话就只能做成按页来显示或者生成1000个格子来显示
2:排行榜,和背包同理
无限滑动应用场景在与需要显示非常多的item的时候
百度了很多ngui的无限滑动都不支持定位显示数据,这个需求一般用在新手引导或者需要直接跳到某条数据显示在当前界面显示的场景
无限滑动思路:
计算出四个边角的局部坐标 再根据是左右滑动还是上下滑动来确定边角离中心的距离,当滑动的时候,item离中心点的距离大于的边角离中心点的距离,item就设置坐标在反方向,例子:如果是向左滑动,当item离中心点的距离大于边角离中心点的距离,该item就设置坐标在最右边。
下面放代码:
定义变量
/// <summary>
/// item , item下标 , 数据下标 (都是从0开始)
/// </summary>
public Action<Transform,int,int> renderItem; //渲染item
public Action renderAllItemCallBack; // 渲染完所有item的回调
public UIGrid grid;// 排序组件
public GameObject itemPrefab; //item预制体
public UIScrollView scrollView; //滑动组件
private int dataCount; //数据个数
private List<Transform> childers; //item列表
private UIPanel panel; //滑动的panel组件
private float width { get { return grid.cellWidth; } } // item宽度
private float height { get { return grid.cellHeight; } } //item高度
//If the arrangement is horizontal, this denotes the number of columns.
// If the arrangement is vertical, this stands for the number of rows.
private int maxPerLine { get { return grid.maxPerLine; } }
private int rows = ; //行数 (预制体所占的总行数)
private int columns = ; //列数 (预制体所占的总列数)
private float extents = ; //所有预制体所占长度或者是高度的一半 用在循环的时候计算item的坐标
private int itemCount = ; //预制体item的数量
初始化数据:
/// <summary>
/// 初始化数据
/// </summary>
public void initData ()
{
int itemCount = childers.Count;
if (scrollView.movement == UIScrollView.Movement.Horizontal)
{
rows = maxPerLine; //行数
columns = itemCount / maxPerLine;
extents = columns * width * f;
}
else
{
columns = maxPerLine; //列数
rows = itemCount / maxPerLine;
extents = rows * height * f;
}
updateAllItem();
}
/// <summary>
///创建item
/// </summary>
private void creatItem()
{
if (itemCount == )
{
itemCount = grid.transform.childCount;
}
else
{
int childCount = grid.transform.childCount;
int count = itemCount - childCount;
for (int i = ; i < count; i++)
{
GameObject go = GameObject.Instantiate(itemPrefab) as GameObject;
go.transform.SetParent(grid.transform, false);
childers.Add(go.transform);
}
}
grid.pivot = UIWidget.Pivot.TopLeft; //强制锚点为左上
grid.Reposition();
}
无限滑动重点代码:
/// <summary>
/// 滑动回调
/// </summary>
/// <param name="panel"></param>
private void onMove(UIPanel panel)
{
Vector3[] corners = panel.worldCorners;
for (int i = ; i < ; ++i) //转换成局部坐标
{
Vector3 v = corners[i];
v = grid.transform.InverseTransformPoint(v);
corners[i] = v;
}
Vector3[] localCorners = corners;
Vector3 center = (localCorners[] + localCorners[]) * f; // 边角的中心点坐标
bool allWithinRange = true;
//0:bottom - left 1:top - left 2:top - right 3:bottom - right
if (scrollView.movement == UIScrollView.Movement.Horizontal)
{
float min = localCorners[].x - width;
float max = localCorners[].x + width;
int count = childers.Count;
for (int i = ; i < count; i++)
{
Transform item = childers[i];
Vector3 localPos = item.localPosition;
float distance = localPos.x - center.x; //用来判断是在左边还是右边 计算item坐标离中心的距离
Vector2 pos = item.localPosition;
int realIndex = ;
if (distance < -extents || distance > extents)
{
if (distance < -extents) // 向左拉的时候(向右移动)
{
pos.x += extents * ;
realIndex = getRealIndexByPos(pos);
}
else if (distance > extents) //向右拉 (向左移动)
{
pos.x -= extents * ;
realIndex = getRealIndexByPos(pos);
}
if (realIndex >= && realIndex < dataCount)
{
item.localPosition = pos;
updateItem(item,i, realIndex);
}
else allWithinRange = false;
}
}
}
else if (scrollView.movement == UIScrollView.Movement.Vertical)
{
float min = localCorners[].x - width;
float max = localCorners[].x + width;
int count = childers.Count;
for (int i = ; i < count; i++)
{
Transform item = childers[i];
Vector3 localPos = item.localPosition;
float distance = localPos.y - center.y; //用来判断是在上边还是下边 计算item坐标离中心的距离
Vector2 pos = item.localPosition;
int realIndex = ;
if (distance < -extents || distance > extents)
{
if (distance < -extents) // 向左拉的时候(向右移动)
{
pos.y += extents * ;
realIndex = getRealIndexByPos(pos);
}
else if (distance > extents) //向右拉 (向左移动)
{
pos.y -= extents * ;
realIndex = getRealIndexByPos(pos);
}
if (realIndex >= && realIndex < dataCount)
{
item.localPosition = pos;
updateItem(item, i, realIndex);
}
else allWithinRange = false;
}
}
}
onRenderCompelte();
scrollView.restrictWithinPanel = !allWithinRange;
scrollView.InvalidateBounds();
}
根据item的局部坐标来获取对应的数据下标:
private int getRealIndexByPos(Vector2 pos)
{
int realIndex = ; // 0 - dataCount-1
int currentRows = (int)(-pos.y / height) ; //行数
int currentColumns = (int)(pos.x / width); //列数
if (scrollView.movement == UIScrollView.Movement.Horizontal)
{
realIndex = currentRows + maxPerLine * currentColumns;
}
else
{
realIndex = currentRows * maxPerLine + currentColumns;
}
return realIndex;
}
显示item的时候调用注册的方法以及显示完成的回调:
private void updateItem(Transform item,int itemIndex, int index)
{
if (renderItem != null)
{
renderItem(item,itemIndex,index);
}
}
private void onRenderCompelte()
{
if (renderAllItemCallBack != null)
{
renderAllItemCallBack();
}
}
根据需要显示的数据下标来定位的刷新item
public void renderItemByIndex(int dataIndex)
{
if (dataIndex < )
{
dataIndex = ;
}
else if(dataIndex >= dataCount)
{
dataIndex = dataCount - ;
}
int currentColumns = ; //列数 从0开始
int currentRows = ; //行数 从0开始
int maxRows = ; //最大行数
int maxColumns = ; //最大列数
float posY = ;
float posX = ;
int showCountItem = rows * columns;
if (scrollView.movement == UIScrollView.Movement.Horizontal)
{
currentColumns = dataIndex / maxPerLine;
maxColumns = (dataCount - ) / maxPerLine;
currentRows = dataIndex % maxPerLine;
int startColumns = ; //开始的列数
int offsetColums = ; //偏移的列数
int maxShowColumns = (int)(panel.width / width);
if (currentColumns + maxShowColumns / < maxShowColumns)//如果要显示的列数小于屏幕显示的最大列数 就直接从0列开始显示
{
startColumns = ;
offsetColums = startColumns;
}
else if (currentColumns + maxShowColumns/ >= maxColumns) //如果当前显示的列数加上屏幕显示的最大列数大于最大的列数就从 最大列数-屏幕显示列数 开始显示
{
startColumns = maxColumns - columns + ;
offsetColums = maxColumns - maxShowColumns + ;
}
else //正常显示 这里可以改成- N
{
//(很多时候坑逼策划会让你居中显示, 下面代码可以改成 startColumns = currentColumns - (columns / 2) +1
startColumns = currentColumns - ;
offsetColums = startColumns;
}
int line = -;
//计算裁剪区域offset以及panel的坐标
Vector2 offset = new Vector2((offsetColums) * width, );
float x = panel.transform.localPosition.x + panel.clipOffset.x;
panel.clipOffset = offset;
panel.transform.localPosition = new Vector3(x - offset.x, , );
for (int i = ; i < showCountItem; i++)
{
Transform item = childers[i];
if (i % rows == ) //下一列了
{
line += ;
}
int column = (startColumns + line);
//if (column > maxColumns)
//{
// break;//超标了 直接退出
//}
posX = column * width;
posY = i % rows * -height;
Vector3 newPos = new Vector3(posX,posY);
item.localPosition = newPos;
int realIndex = getRealIndexByPos(newPos);
updateItem(item, i, realIndex);
}
}
else
{
currentRows = dataIndex / maxPerLine;
maxRows = (dataCount - ) / maxPerLine;
currentColumns = dataIndex % maxPerLine;
int startRows = ; //开始的行数
int offsetRows = ; // 偏移的行数
int maxShowRows = (int)(panel.height / height); //裁剪区域能显示的最大行数
if (currentRows + maxShowRows / < maxShowRows)//
{
startRows = ;
offsetRows = startRows;
}
else if (currentRows + maxShowRows/ >= maxRows)
{
startRows = maxRows - rows + ;
offsetRows = maxRows - maxShowRows +;
}
else
{
startRows = currentRows - ;
offsetRows = startRows;
}
int line = -;
//计算裁剪以及panel的坐标
Vector2 offset = new Vector2(, offsetRows * -height);
float y = panel.transform.localPosition.y + panel.clipOffset.y;
panel.clipOffset = offset;
panel.transform.localPosition = new Vector3(panel.transform.localPosition.x, y - offset.y, );
for (int i = ; i < showCountItem; i++)
{
Transform item = childers[i];
if (i % columns == ) //下一行了
{
line += ;
}
int row = (startRows + line);
if (row > maxRows)
{
break;//超标了 直接退出
}
posY = row * -height;
posX = i % columns * width;
Vector3 newPos = new Vector3(posX, posY);
item.localPosition = newPos;
int realIndex = getRealIndexByPos(newPos);
updateItem(item, i, realIndex);
}
}
onRenderCompelte();
}
写在最后:
本篇教程是结合ngui 的uigrid排序组件,以及uipanel,UIScrollView滑动组件来做的,uigrid的pivot 必须为 UIWidget.Pivot.TopLeft
如果是其他的锚点需要根据这篇教程的思路自己去实现
注意点:grid.arrangement 与UIScrollView.movement必须相反, uigrid的pivot 必须为 UIWidget.Pivot.TopLeft