Purpose: ScrollView 一直是UI界面最费的地方,这里的解决方案可谓是集对象池,自定义各种效果等之大乘。代码很长,但有demo可以观摩。不足之处,多多赐教。 这个小插件的借助了 Grid Layout Group组件的各个参数,利用拖动事件驱动所有的滑动效果。自动控制ScrollView下的Item对象, 利用对象池技术,最优化实现滚动显示信息的功能。 详细使用方法请看源代码。 Author: 赵光辉 Data: 2016/11/20
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
///
/// 控制类
///
public class ScrollRectAutoItem : MonoBehaviour, IEndDragHandler, IDragHandler
{
///
/// 拖拽事件回调
///
public Action OnDragBack;
///
/// 拖拽事件回调
///
public Action OnEndDragBack;
///
/// 滚动事件回调
///
public Action ScrollItemBack;
///
/// Item控制模式
///
public ItemState ItemState = ItemState.RollItem;
///
/// 组件
///
private ScrollRect customScrollRect;
private RectTransform content;
private GridLayoutGroup gridLayoutGroup;
///
/// item预置物
///
private GameObject itemUnit;
///
/// item预置物路径
///
private string itemUnitPath;
///
/// Item对象池
///
private GameObject pool;
///
/// 当前滑动的区域id
///
private float itemIdFloatDown = 0;
private float itemIdFloatUp = 0;
private int itemIdIntDown = 0;
private int itemIdIntUp = 0;
///
/// customScrollRect数据
///
private Vector2 sizeDelta;
///
/// content数据
///
private Vector3 contentLocalPos;
///
/// GridLayoutGroup数据
///
private int constraintCount = 0;
private Vector2 cellSize = Vector2.zero;
private Vector2 spacing = Vector2.zero;
private RectOffset padding;
///
/// 子对象个数
///
private int childCount;
///
/// 子对象行数或者列数
///
private int rowOrColNum;
///
/// 每一个item的位置
///
private Dictionary itemPosDic;
///
/// 维护子对象的使用
///
private Stack itemStack;
///
/// item信息类
///
private Type handlerInfo;
///
/// 参数
///
private List parList;
///
/// 参数类型
///
private Type paramType = null;
///
/// Item的信息类集合
///
private Dictionary handlerInfoDic;
//缓存---用于滚动优化
private int itemIdIntDownCache = -1;
private int itemIdIntUpCache = -1;
private int childCountCache = -1;
private int toItemId = -1;
///
/// 锁
///
private System.Object thisLock = new System.Object();
///
/// 点击Item移动结束事件回调
///
private Action MoveToItenIdBack;
private void Awake()
{
//获取组件
customScrollRect = this.transform.GetComponent();
content = this.transform.FindChild("Content").GetComponent();
gridLayoutGroup = content.GetComponent();
//获取Item池子
Transform poolTrans = this.transform.FindChild("Pool");
if (poolTrans == null)
{
poolTrans = new GameObject("Pool").GetComponent();
poolTrans.SetParent(this.transform);
initGameObj(poolTrans.gameObject);
}
pool = poolTrans.gameObject;
//设置滑动方向
if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedColumnCount)
{
customScrollRect.vertical = true;
customScrollRect.horizontal = false;
}
else if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedRowCount)
{
customScrollRect.vertical = false;
customScrollRect.horizontal = true;
}
else
{
customScrollRect.vertical = true;
customScrollRect.horizontal = true;
}
//获取customScrollRect数据
sizeDelta = customScrollRect.GetComponent().sizeDelta;
//设置滑动回调事件
customScrollRect.onValueChanged.AddListener(ScrollRectEvent);
customScrollRect.decelerationRate = 0.001f;
//记录content数据
contentLocalPos = content.localPosition;
UIHelper.ClearChildObj(content.transform);
//获取GridLayoutGroup数据
constraintCount = gridLayoutGroup.constraintCount;
cellSize = gridLayoutGroup.cellSize;
spacing = gridLayoutGroup.spacing;
padding = gridLayoutGroup.padding;
//禁用
ContentSizeFitter sizeFitter = content.GetComponent();
if (sizeFitter != null)
{
sizeFitter.enabled = false;
}
gridLayoutGroup.enabled = false;
//item挂载的信息类
handlerInfoDic = new Dictionary();
//初始栈空间
itemStack = new Stack();
}
///
/// 设置惯性
///
public void SetInertia(bool boo)
{
if (customScrollRect != null)
{
customScrollRect.inertia = boo;
}
}
///
/// 根据子对象的数量初始化
///
/// 子对象预置物路径
/// 子对象数量
/// 脚本
/// 参数
public void Init(string itemPath, int childCount, Type type)
{
Init(itemPath, childCount, type, null);
}
///
/// 根据子对象的数量初始化
///
/// 子对象预置物路径
/// 子对象数量
/// 脚本
/// 参数
public void Init(string itemPath, int childCount, Type type, List pList = null)
{
//item预置物 得到的是一个引用,非实例化
this.itemUnitPath = itemPath;
itemUnit = Resources.Load(itemPath) as GameObject;
Init(itemUnit, childCount, type, pList);
}
///
/// 根据子对象的数量初始化
///
/// 子对象预置物路径
/// 子对象数量
/// 脚本
/// 参数
public void Init(GameObject item, int childCount, Type type, List pList = null)
{
//子对象个数
this.childCount = childCount;
//参数集合
if (pList != null)
{
this.parList = new List();
for (int i = 0; i < pList.Count; i++)
{
this.parList.Add(pList[i]);
}
this.paramType = typeof(T);
}
else
{
this.parList = null;
}
//子对象
itemUnit = item;
//item挂载的信息类
handlerInfo = type;
handlerInfoDic.Clear();
//设置Content宽高
float width = 0;
float height = 0;
rowOrColNum = 0;
if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedColumnCount)
{
rowOrColNum = childCount / constraintCount;
rowOrColNum = (childCount % gridLayoutGroup.constraintCount == 0) ? rowOrColNum : rowOrColNum + 1;
height = rowOrColNum * cellSize.y + (rowOrColNum - 1) * spacing.y + padding.top + padding.bottom;
width = constraintCount * cellSize.x + (constraintCount - 1) * spacing.x + padding.left + padding.right;
}
else if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedRowCount)
{
rowOrColNum = childCount / constraintCount;
rowOrColNum = (childCount % gridLayoutGroup.constraintCount == 0) ? rowOrColNum : rowOrColNum + 1;
width = rowOrColNum * cellSize.x + (rowOrColNum - 1) * spacing.x + padding.left + padding.right;
height = constraintCount * cellSize.y + (constraintCount - 1) * spacing.y + padding.top + padding.bottom;
}
else
{
return;
}
content.sizeDelta = new Vector2(width, height);
//记录每一个item的位置
itemPosDic = new Dictionary();
Vector3 pos = Vector3.zero;
Vector3 paddingV2 = new Vector3(cellSize.x / 2 + padding.left, -1f * cellSize.y / 2 - padding.top, 0);
if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedColumnCount)
{
for (int i = 0; i < rowOrColNum; i++)
{
for (int j = 0; j < constraintCount; j++)
{
pos = new Vector3(cellSize.x * j, -1f * cellSize.y * i, 0) + paddingV2;
itemPosDic.Add(i * constraintCount + j, pos);
}
}
}
else if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedRowCount)
{
for (int i = 0; i < rowOrColNum; i++)
{
for (int j = 0; j < constraintCount; j++)
{
pos = new Vector3(cellSize.x * i, -1f * cellSize.y * j, 0) + paddingV2;
itemPosDic.Add(i * constraintCount + j, pos);
}
}
}
else
{
return;
}
//实例化Item
if (ItemState == ItemState.RollItem)
{
this.InitRollItem();
}
else if (ItemState == ItemState.HideItem)
{
this.InitHideItem();
}
}
///
/// 初始化Item
///
private void InitRollItem()
{
//初始栈空间
int initNum = 0;
if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedColumnCount)
{
initNum = ((int)(sizeDelta.y / cellSize.y) + 1) * constraintCount;
}
else if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedRowCount)
{
initNum = ((int)(sizeDelta.x / cellSize.x) + 1) * constraintCount;
}
else
{
return;
}
initNum = initNum > childCount ? childCount : initNum;
//初始化item显示信息
ItemStackClear();
if (childCountCache != childCount)
{
this.childCountCache = childCount;
InitContent(initNum);
}
else
{
UpdataItem();
}
}
///
/// 初始化Item
///
private void InitHideItem()
{
UIHelper.CreateChildIcon(this.childCount, content, this.itemUnitPath);
GameObject go = null;
for (int i = 0; i < this.childCount; i++)
{
go = content.transform.GetChild(i).gameObject;
go.transform.localPosition = itemPosDic[i];
go.transform.localScale = Vector3.one;
go.name = i.ToString();
var handler = go.GetComponent();
if (handler == null)
{
handler = go.AddComponent(handlerInfo) as ABaseItemHandler;
handler.Init();
handler.ParamType = this.paramType;
}
if (this.parList != null)
{
handler.Show(i, this.parList[i]);
}
else
{
handler.Show(i);
}
handler.OriginalPos = itemPosDic[i];
if (!handlerInfoDic.ContainsKey(i))
{
handlerInfoDic.Add(i, handler);
}
else
{
handlerInfoDic[i] = handler;
}
}
//模拟滚动回调 获取上下边界点
ScrollRectEvent(Vector2.zero);
int id = (itemIdIntUp - 1) * constraintCount;
if (id > 0 && childCount > id)
{
for (int i = 0; i < id; i++)
{
handlerInfoDic[i].ItemTrans.gameObject.SetActive(false);
}
}
id = (itemIdIntDown + 1) * constraintCount;
if (id > 0 && childCount > id)
{
for (int i = id; i < childCount; i++)
{
handlerInfoDic[i].ItemTrans.gameObject.SetActive(false);
}
}
}
///
/// 初始化Content
///
///
private void InitContent(int num)
{
UIHelper.ClearChildObj(content);
for (int i = 0; i < num; i++)
{
PopStack(i);
}
}
///
/// 更新界面信息
///
public void UpdataItem()
{
ABaseItemHandler handler = null;
for (int i = 0; i < content.transform.childCount; i++)
{
handler = content.transform.GetChild(i).GetComponent();
if (handler != null)
{
if (this.parList != null)
{
handler.Show(handler.ItemId, this.parList[handler.ItemId]);
}
else
{
handler.Show(handler.ItemId);
}
}
else
{
this.childCountCache = -1;
InitRollItem();
return;
}
if (!handlerInfoDic.ContainsValue(handler))
{
handlerInfoDic.Add(handler.ItemId, handler);
}
}
}
///
/// 拖动回调事件
///
///
private void ScrollRectEvent(Vector2 v2)
{
//定位上下两个边界点
if (customScrollRect.horizontal)
{
itemIdFloatDown = (-1f * content.localPosition.x + sizeDelta.x / 2 - padding.left) / cellSize.x;
itemIdFloatUp = -1f * (content.localPosition.x - contentLocalPos.x + padding.left) / cellSize.x;
}
else if (customScrollRect.vertical)
{
itemIdFloatDown = (content.localPosition.y + sizeDelta.y / 2 + padding.top) / cellSize.y;
itemIdFloatUp = (content.localPosition.y - contentLocalPos.y - padding.top) / cellSize.y;
}
else
{
return;
}
//控制边界
itemIdFloatDown = itemIdFloatDown < 0 ? 0 : itemIdFloatDown;
itemIdFloatUp = itemIdFloatUp < 0 ? 0 : itemIdFloatUp;
//回调---实现自由控制item变化
if (ScrollItemBack != null)
{
ScrollItemBack.Invoke(itemIdFloatUp, itemIdFloatDown);
}
itemIdIntDown = GetIntValue(itemIdFloatDown);
itemIdIntUp = GetIntValue(itemIdFloatUp);
//Debug.Log("itemIdIntDown " + itemIdIntDown * constraintCount);
//Debug.Log("itemIdIntUp " + itemIdIntUp * constraintCount);
//优化效率问题
if (itemIdIntDownCache != itemIdIntDown || itemIdIntUpCache != itemIdIntUp)
{
this.ControlBound();
itemIdIntDownCache = itemIdIntDown;
itemIdIntUpCache = itemIdIntUp;
}
}
///
/// 边界控制
///
private void ControlBound()
{
//临时变量
Transform transItem = null;
int id = 0;
for (int i = 0; i < constraintCount; i++)
{
//上
id = (itemIdIntUp - 1) * constraintCount + i;
if (id >= 0 && childCount > id)
{
transItem = content.FindChild(id.ToString());
if (transItem != null)
{
if (ItemState == ItemState.RollItem)
{
PushStack(transItem.gameObject); //进栈
}
else if (ItemState == ItemState.HideItem)
{
SetActive(transItem.gameObject, false);
}
}
}
id = itemIdIntUp * constraintCount + i;
if (id >= 0 && childCount > id)
{
transItem = content.FindChild(id.ToString());
if (transItem == null)
{
if (ItemState == ItemState.RollItem)
{
PopStack(id); //出栈
}
}
else
{
if (ItemState == ItemState.HideItem)
{
SetActive(transItem.gameObject, true);
}
}
}
//下
id = itemIdIntDown * constraintCount + i;
if (id >= 0 && childCount > id)
{
transItem = content.FindChild(id.ToString());
if (transItem == null)
{
if (ItemState == ItemState.RollItem)
{
PopStack(id); //出栈
}
}
else
{
if (ItemState == ItemState.HideItem)
{
SetActive(transItem.gameObject, true);
}
}
}
id = (itemIdIntDown + 1) * constraintCount + i;
if (id >= 0 && childCount > id)
{
transItem = content.FindChild(id.ToString());
if (transItem != null)
{
if (ItemState == ItemState.RollItem)
{
PushStack(transItem.gameObject); //进栈
}
else if (ItemState == ItemState.HideItem)
{
SetActive(transItem.gameObject, false);
}
}
}
}
}
///
/// 初始化num个子对象放进栈里面
///
///
private void InitStack(int num)
{
GameObject obj = Instantiate(itemUnit);
PushStack(obj);
}
///
/// 进栈维护
///
///
private void PushStack(GameObject obj)
{
if (obj == null)
{
return;
}
lock (this.thisLock)
{
obj.transform.SetParent(pool.transform);
initGameObj(obj);
obj.SetActive(false);
itemStack.Push(obj);
//移除信息类
RemoveItemInfo(obj);
}
}
///
/// 出栈维护
///
///
private GameObject PopStack(int itemId)
{
GameObject obj = null;
if (itemStack.Count == 0)
{
obj = Instantiate(itemUnit);
initGameObj(obj);
}
else
{
lock (this.thisLock)
{
obj = itemStack.Pop();
}
}
obj.transform.SetParent(content);
obj.transform.localPosition = itemPosDic[itemId];
obj.transform.localScale = Vector3.one;
obj.name = itemId.ToString();
obj.SetActive(true);
//挂载信息类
AddItemInfo(obj, itemId);
return obj;
}
///
/// 删除栈里面的元素
///
///
private void DelectStackItem(int num)
{
for (int i = 0; i < num; i++)
{
lock (this.thisLock)
{
GameObject obj = itemStack.Pop();
GameObject.Destroy(obj);
}
}
}
///
/// 清理栈空间
///
private void ItemStackClear()
{
if (itemStack.Count > 0)
{
for (int i = 0; i < itemStack.Count; i++)
{
GameObject go = itemStack.Pop();
GameObject.Destroy(go);
}
}
UIHelper.ClearChildObj(pool.transform);
}
///
/// 挂载Item信息类
///
///
///
private void AddItemInfo(GameObject go, int itemId)
{
ABaseItemHandler handler = go.GetComponent();
if (handler == null)
{
handler = go.AddComponent(handlerInfo) as ABaseItemHandler;//待修改
handler.Init();
handler.ParamType = this.paramType;
}
if (this.parList == null)
{
handler.Show(itemId);
}
else
{
if (this.parList.Count > itemId)
{
handler.Show(itemId, this.parList[itemId]);
}
}
if (!handlerInfoDic.ContainsKey(itemId))
{
handlerInfoDic.Add(itemId, handler);
}
else
{
handlerInfoDic[itemId] = handler;
}
}
///
/// 移除Item信息类
///
///
private void RemoveItemInfo(GameObject go)
{
ABaseItemHandler handler = go.GetComponent();
if (handler != null)
{
handlerInfoDic.Remove(handler.ItemId);
}
}
///
/// 获取Item信息类
///
///
///
public ABaseItemHandler GetHandler(int itemId)
{
ABaseItemHandler handler = null;
if (!handlerInfoDic.TryGetValue(itemId, out handler))
{
Debug.Log("未能找到id=" + itemId + " 的信息类");
}
return handler;
}
///
/// 拖拽中
///
///
public void OnDrag(PointerEventData eventData)
{
if (this.OnDragBack != null)
{
this.OnDragBack.Invoke(itemIdFloatUp, itemIdFloatDown);
}
}
///
/// 拖拽结束 可以实现自定义Item变化
///
///
public void OnEndDrag(PointerEventData eventData)
{
if (OnEndDragBack != null)
{
OnEndDragBack.Invoke(itemIdFloatUp, itemIdFloatDown);
}
}
///
/// 点击移动到Item
///
///
///
public void ClickToItem(int itemId, float speed = 500f, Action call = null)
{
if (toItemId != itemId)
{
toItemId = itemId;
DragToItemId(itemId, speed, call);
}
}
///
/// 移动到某一个Item
///
///
public void DragToItemId(int itemId, float speed = 500f, Action call = null)
{
this.MoveToItenIdBack = call;
Vector3 pos = Vector3.zero;
if (customScrollRect.horizontal)
{
pos = new Vector3(((itemId + 0.5f) * cellSize.x + padding.left) * -1f, content.transform.localPosition.y, 0);
}
else if (customScrollRect.vertical)
{
pos = new Vector3(content.transform.localPosition.x, ((itemId + 0.5f) * cellSize.y + padding.top), 0);
}
Hashtable msg = new Hashtable();
msg.Add("position", pos);
msg.Add("islocal", true);
msg.Add("speed", speed);
msg.Add("oncomplete", "MoveStop");
msg.Add("oncompletetarget", this.gameObject);
msg.Add("oncompleteparams", itemId);
msg.Add("easetype", iTween.EaseType.easeOutCubic);
iTween.MoveTo(content.gameObject, msg);
}
///
/// 移动结束事件
///
private void MoveStop(int itemId)
{
toItemId = itemId;
if (MoveToItenIdBack != null)
{
MoveToItenIdBack.Invoke(itemId);
MoveToItenIdBack = null;
}
}
private int GetIntValue(float value)
{
int id = Mathf.FloorToInt(value);
id = id > rowOrColNum - 1 ? rowOrColNum - 1 : id;
id = id < 0 ? 0 : id;
return id;
}
private void initGameObj(GameObject obj)
{
obj.transform.localPosition = Vector3.zero;
obj.transform.localScale = Vector3.one;
obj.transform.eulerAngles = Vector3.zero;
}
private void SetActive(GameObject go, bool boo)
{
bool bo = go.activeSelf == boo;
if (!bo)
{
go.SetActive(boo);
}
}
}
///
/// Item控制模式
///
public enum ItemState
{
///
/// 隐藏模式
///
HideItem,
///
/// 滚动模式
///
RollItem,
}
///
/// 约束Item的抽象信息类
///
public abstract class ABaseItemHandler : MonoBehaviour
{
///
/// 参数类型
///
public Type ParamType;
///
/// 参数
///
public object Param;
///
/// 原始位置
///
public Vector3 OriginalPos;
///
/// 当前itemId
///
public int ItemId;
///
/// item对象
///
public RectTransform ItemTrans;
///
/// 初始化
///
virtual public void Init()
{
ItemTrans = this.GetComponent();
OriginalPos = this.ItemTrans.localPosition;
}
///
/// 显示信息
///
///
virtual public void Show(int itemId, object e = null)
{
this.ItemId = itemId;
this.Param = e;
}
///
/// 修改信息
///
///
virtual public void Modify(object e = null)
{
}
///
/// 添加事件
///
virtual public void AddEvent()
{
}
}
///
/// UI辅助类
///
using UnityEngine;
public class UIHelper
{
///
/// 创建一定数量的子对象
///
///
///
///
public static void CreateChildIcon(int needNum, Transform content, string childPath)
{
//子对象个数
int num = content.childCount;
if (num > needNum)
{
for (int i = num - 1; i >= needNum; i--)
{
GameObject.Destroy(content.GetChild(i).gameObject);
}
}
else
{
for (int i = num; i < needNum; i++)
{
GameObject obj = Resources.Load(childPath) as GameObject;
obj.transform.SetParent(content);
obj.name = i.ToString();
}
}
}
///
/// 清除子对象
///
///
public static void ClearChildObj(Transform go)
{
for (int i = 0; i < go.childCount; i++)
{
GameObject obj = go.GetChild(i).gameObject;
UnityEngine.Object.Destroy(obj);
}
}
}
详细使用方法,请下载示例代码工程。
链接:http://pan.baidu.com/s/1b9Fm8I 密码:uhxr
联系方式:[email protected]