NGUI 無限滑動,支援定位顯示指定資料
阿新 • • 發佈:2019-02-09
為什麼需要無限滑動:
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 = 1; //行數 (預製體所佔的總行數)
private int columns = 1; //列數 (預製體所佔的總列數)
private float extents = 0; //所有預製體所佔長度或者是高度的一半 用在迴圈的時候計算item的座標
private int itemCount = 10; //預製體item的數量
初始化資料:
/// <summary>
/// 初始化資料
/// </summary>
public void initData ()
{
int itemCount = childers.Count;
if (scrollView.movement == UIScrollView.Movement.Horizontal)
{
rows = maxPerLine; //行數
columns = itemCount / maxPerLine;
extents = columns * width * 0.5f;
}
else
{
columns = maxPerLine; //列數
rows = itemCount / maxPerLine;
extents = rows * height * 0.5f;
}
updateAllItem();
}
/// <summary>
///建立item
/// </summary>
private void creatItem()
{
if (itemCount == 0)
{
itemCount = grid.transform.childCount;
}
else
{
int childCount = grid.transform.childCount;
int count = itemCount - childCount;
for (int i = 0; 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 = 0; i < 4; ++i) //轉換成區域性座標
{
Vector3 v = corners[i];
v = grid.transform.InverseTransformPoint(v);
corners[i] = v;
}
Vector3[] localCorners = corners;
Vector3 center = (localCorners[0] + localCorners[2]) * 0.5f; // 邊角的中心點座標
bool allWithinRange = true;
//0:bottom - left 1:top - left 2:top - right 3:bottom - right
if (scrollView.movement == UIScrollView.Movement.Horizontal)
{
float min = localCorners[0].x - width;
float max = localCorners[2].x + width;
int count = childers.Count;
for (int i = 0; i < count; i++)
{
Transform item = childers[i];
Vector3 localPos = item.localPosition;
float distance = localPos.x - center.x; //用來判斷是在左邊還是右邊 計算item座標離中心的距離
Vector2 pos = item.localPosition;
int realIndex = 0;
if (distance < -extents || distance > extents)
{
if (distance < -extents) // 向左拉的時候(向右移動)
{
pos.x += extents * 2;
realIndex = getRealIndexByPos(pos);
}
else if (distance > extents) //向右拉 (向左移動)
{
pos.x -= extents * 2;
realIndex = getRealIndexByPos(pos);
}
if (realIndex >=0 && realIndex < dataCount)
{
item.localPosition = pos;
updateItem(item,i, realIndex);
}
else allWithinRange = false;
}
}
}
else if (scrollView.movement == UIScrollView.Movement.Vertical)
{
float min = localCorners[0].x - width;
float max = localCorners[2].x + width;
int count = childers.Count;
for (int i = 0; i < count; i++)
{
Transform item = childers[i];
Vector3 localPos = item.localPosition;
float distance = localPos.y - center.y; //用來判斷是在上邊還是下邊 計算item座標離中心的距離
Vector2 pos = item.localPosition;
int realIndex = 0;
if (distance < -extents || distance > extents)
{
if (distance < -extents) // 向左拉的時候(向右移動)
{
pos.y += extents * 2;
realIndex = getRealIndexByPos(pos);
}
else if (distance > extents) //向右拉 (向左移動)
{
pos.y -= extents * 2;
realIndex = getRealIndexByPos(pos);
}
if (realIndex >= 0 && 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; // 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 < 0)
{
dataIndex = 0;
}
else if(dataIndex >= dataCount)
{
dataIndex = dataCount - 1;
}
int currentColumns = 0; //列數 從0開始
int currentRows = 0; //行數 從0開始
int maxRows = 0; //最大行數
int maxColumns = 0; //最大列數
float posY = 0;
float posX = 0;
int showCountItem = rows * columns;
if (scrollView.movement == UIScrollView.Movement.Horizontal)
{
currentColumns = dataIndex / maxPerLine;
maxColumns = (dataCount - 1) / maxPerLine;
currentRows = dataIndex % maxPerLine;
int startColumns = 0; //開始的列數
int offsetColums = 0; //偏移的列數
int maxShowColumns = (int)(panel.width / width);
if (currentColumns + maxShowColumns / 2 < maxShowColumns)//如果要顯示的列數小於螢幕顯示的最大列數 就直接從0列開始顯示
{
startColumns = 0;
offsetColums = startColumns;
}
else if (currentColumns + maxShowColumns/2 >= maxColumns) //如果當前顯示的列數加上螢幕顯示的最大列數大於最大的列數就從 最大列數-螢幕顯示列數 開始顯示
{
startColumns = maxColumns - columns + 1;
offsetColums = maxColumns - maxShowColumns + 1;
}
else //正常顯示 這裡可以改成- N
{
//(很多時候坑逼策劃會讓你居中顯示, 下面程式碼可以改成 startColumns = currentColumns - (columns / 2) +1
startColumns = currentColumns - 1;
offsetColums = startColumns;
}
int line = -1;
//計算裁剪區域offset以及panel的座標
Vector2 offset = new Vector2((offsetColums) * width, 0);
float x = panel.transform.localPosition.x + panel.clipOffset.x;
panel.clipOffset = offset;
panel.transform.localPosition = new Vector3(x - offset.x, 0, 0);
for (int i = 0; i < showCountItem; i++)
{
Transform item = childers[i];
if (i % rows == 0) //下一列了
{
line += 1;
}
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 - 1) / maxPerLine;
currentColumns = dataIndex % maxPerLine;
int startRows = 0; //開始的行數
int offsetRows = 0; // 偏移的行數
int maxShowRows = (int)(panel.height / height); //裁剪區域能顯示的最大行數
if (currentRows + maxShowRows / 2 < maxShowRows)//
{
startRows = 0;
offsetRows = startRows;
}
else if (currentRows + maxShowRows/2 >= maxRows)
{
startRows = maxRows - rows + 1;
offsetRows = maxRows - maxShowRows +1;
}
else
{
startRows = currentRows - 1;
offsetRows = startRows;
}
int line = -1;
//計算裁剪以及panel的座標
Vector2 offset = new Vector2(0, 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, 0);
for (int i = 0; i < showCountItem; i++)
{
Transform item = childers[i];
if (i % columns == 0) //下一行了
{
line += 1;
}
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