ScrollView 最优使用方案
发表于2017-12-11
ScrollView 一直是UI界面里最难厉害的,这里的解决方案可谓是集对象池,自定义各种效果等之大乘。代码很长,但有demo可以观摩。
这个小插件的借助了 Grid Layout Group组件的各个参数,利用拖动事件驱动所有的滑动效果。
自动控制ScrollView下的Item对象,利用对象池技术,最优化实现滚动显示信息的功能。
详细使用方法请看源代码:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; /// <summary> /// 控制类 /// </summary> public class ScrollRectAutoItem : MonoBehaviour, IEndDragHandler, IDragHandler { /// <summary> /// 拖拽事件回调 /// </summary> public Action<float, float> OnDragBack; /// <summary> /// 拖拽事件回调 /// </summary> public Action<float, float> OnEndDragBack; /// <summary> /// 滚动事件回调 /// </summary> public Action<float, float> ScrollItemBack; /// <summary> /// Item控制模式 /// </summary> public ItemState ItemState = ItemState.RollItem; /// <summary> /// 组件 /// </summary> private ScrollRect customScrollRect; private RectTransform content; private GridLayoutGroup gridLayoutGroup; /// <summary> /// item预置物 /// </summary> private GameObject itemUnit; /// <summary> /// item预置物路径 /// </summary> private string itemUnitPath; /// <summary> /// Item对象池 /// </summary> private GameObject pool; /// <summary> /// 当前滑动的区域id /// </summary> private float itemIdFloatDown = 0; private float itemIdFloatUp = 0; private int itemIdIntDown = 0; private int itemIdIntUp = 0; /// <summary> /// customScrollRect数据 /// </summary> private Vector2 sizeDelta; /// <summary> /// content数据 /// </summary> private Vector3 contentLocalPos; /// <summary> /// GridLayoutGroup数据 /// </summary> private int constraintCount = 0; private Vector2 cellSize = Vector2.zero; private Vector2 spacing = Vector2.zero; private RectOffset padding; /// <summary> /// 子对象个数 /// </summary> private int childCount; /// <summary> /// 子对象行数或者列数 /// </summary> private int rowOrColNum; /// <summary> /// 每一个item的位置 /// </summary> private Dictionary<int, Vector3> itemPosDic; /// <summary> /// 维护子对象的使用 /// </summary> private Stack<GameObject> itemStack; /// <summary> /// item信息类 /// </summary> private Type handlerInfo; /// <summary> /// 参数 /// </summary> private List<object> parList; /// <summary> /// 参数类型 /// </summary> private Type paramType = null; /// <summary> /// Item的信息类集合 /// </summary> private Dictionary<int, ABaseItemHandler> handlerInfoDic; //缓存---用于滚动优化 private int itemIdIntDownCache = -1; private int itemIdIntUpCache = -1; private int childCountCache = -1; private int toItemId = -1; /// <summary> /// 锁 /// </summary> private System.Object thisLock = new System.Object(); /// <summary> /// 点击Item移动结束事件回调 /// </summary> private Action<int> MoveToItenIdBack; private void Awake() { //获取组件 customScrollRect = this.transform.GetComponent<ScrollRect>(); content = this.transform.FindChild("Content").GetComponent<RectTransform>(); gridLayoutGroup = content.GetComponent<GridLayoutGroup>(); //获取Item池子 Transform poolTrans = this.transform.FindChild("Pool"); if (poolTrans == null) { poolTrans = new GameObject("Pool").GetComponent<Transform>(); 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<RectTransform>().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<ContentSizeFitter>(); if (sizeFitter != null) { sizeFitter.enabled = false; } gridLayoutGroup.enabled = false; //item挂载的信息类 handlerInfoDic = new Dictionary<int, ABaseItemHandler>(); //初始栈空间 itemStack = new Stack<GameObject>(); } /// <summary> /// 设置惯性 /// </summary> public void SetInertia(bool boo) { if (customScrollRect != null) { customScrollRect.inertia = boo; } } /// <summary> /// 根据子对象的数量初始化 /// </summary> /// <param name="itemPath">子对象预置物路径</param> /// <param name="childCount">子对象数量</param> /// <param name="type">脚本</param> /// <param name="parList">参数</param> public void Init(string itemPath, int childCount, Type type) { Init<object>(itemPath, childCount, type, null); } /// <summary> /// 根据子对象的数量初始化 /// </summary> /// <param name="itemPath">子对象预置物路径</param> /// <param name="childCount">子对象数量</param> /// <param name="type">脚本</param> /// <param name="parList">参数</param> public void Init<T>(string itemPath, int childCount, Type type, List<T> pList = null) { //item预置物 得到的是一个引用,非实例化 this.itemUnitPath = itemPath; itemUnit = Resources.Load(itemPath) as GameObject; Init<T>(itemUnit, childCount, type, pList); } /// <summary> /// 根据子对象的数量初始化 /// </summary> /// <param name="itemPath">子对象预置物路径</param> /// <param name="childCount">子对象数量</param> /// <param name="type">脚本</param> /// <param name="parList">参数</param> public void Init<T>(GameObject item, int childCount, Type type, List<T> pList = null) { //子对象个数 this.childCount = childCount; //参数集合 if (pList != null) { this.parList = new List<object>(); 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<int, Vector3>(); 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(); } } /// <summary> /// 初始化Item /// </summary> 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(); } } /// <summary> /// 初始化Item /// </summary> 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<ABaseItemHandler>(); 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); } } } /// <summary> /// 初始化Content /// </summary> /// <param name="num"></param> private void InitContent(int num) { UIHelper.ClearChildObj(content); for (int i = 0; i < num; i++) { PopStack(i); } } /// <summary> /// 更新界面信息 /// </summary> public void UpdataItem() { ABaseItemHandler handler = null; for (int i = 0; i < content.transform.childCount; i++) { handler = content.transform.GetChild(i).GetComponent<ABaseItemHandler>(); 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); } } } /// <summary> /// 拖动回调事件 /// </summary> /// <param name="v2"></param> 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; } } /// <summary> /// 边界控制 /// </summary> 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); } } } } } /// <summary> /// 初始化num个子对象放进栈里面 /// </summary> /// <param name="num"></param> private void InitStack(int num) { GameObject obj = Instantiate<GameObject>(itemUnit); PushStack(obj); } /// <summary> /// 进栈维护 /// </summary> /// <param name="go"></param> 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); } } /// <summary> /// 出栈维护 /// </summary> /// <returns></returns> private GameObject PopStack(int itemId) { GameObject obj = null; if (itemStack.Count == 0) { obj = Instantiate<GameObject>(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; } /// <summary> /// 删除栈里面的元素 /// </summary> /// <param name="go"></param> private void DelectStackItem(int num) { for (int i = 0; i < num; i++) { lock (this.thisLock) { GameObject obj = itemStack.Pop(); GameObject.Destroy(obj); } } } /// <summary> /// 清理栈空间 /// </summary> 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); } /// <summary> /// 挂载Item信息类 /// </summary> /// <param name="go"></param> /// <param name="itemId"></param> private void AddItemInfo(GameObject go, int itemId) { ABaseItemHandler handler = go.GetComponent<ABaseItemHandler>(); 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; } } /// <summary> /// 移除Item信息类 /// </summary> /// <param name="go"></param> private void RemoveItemInfo(GameObject go) { ABaseItemHandler handler = go.GetComponent<ABaseItemHandler>(); if (handler != null) { handlerInfoDic.Remove(handler.ItemId); } } /// <summary> /// 获取Item信息类 /// </summary> /// <param name="itemId"></param> /// <returns></returns> public ABaseItemHandler GetHandler(int itemId) { ABaseItemHandler handler = null; if (!handlerInfoDic.TryGetValue(itemId, out handler)) { Debug.Log("未能找到id=" + itemId + " 的信息类"); } return handler; } /// <summary> /// 拖拽中 /// </summary> /// <param name="eventData"></param> public void OnDrag(PointerEventData eventData) { if (this.OnDragBack != null) { this.OnDragBack.Invoke(itemIdFloatUp, itemIdFloatDown); } } /// <summary> /// 拖拽结束 可以实现自定义Item变化 /// </summary> /// <param name="eventData"></param> public void OnEndDrag(PointerEventData eventData) { if (OnEndDragBack != null) { OnEndDragBack.Invoke(itemIdFloatUp, itemIdFloatDown); } } /// <summary> /// 点击移动到Item /// </summary> /// <param name="itemId"></param> /// <param name="call"></param> public void ClickToItem(int itemId, float speed = 500f, Action<int> call = null) { if (toItemId != itemId) { toItemId = itemId; DragToItemId(itemId, speed, call); } } /// <summary> /// 移动到某一个Item /// </summary> /// <param name="itemId"></param> public void DragToItemId(int itemId, float speed = 500f, Action<int> 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); } /// <summary> /// 移动结束事件 /// </summary> 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); } } } /// <summary> /// Item控制模式 /// </summary> public enum ItemState { /// <summary> /// 隐藏模式 /// </summary> HideItem, /// <summary> /// 滚动模式 /// </summary> RollItem, } /// <summary> /// 约束Item的抽象信息类 /// </summary> public abstract class ABaseItemHandler : MonoBehaviour { /// <summary> /// 参数类型 /// </summary> public Type ParamType; /// <summary> /// 参数 /// </summary> public object Param; /// <summary> /// 原始位置 /// </summary> public Vector3 OriginalPos; /// <summary> /// 当前itemId /// </summary> public int ItemId; /// <summary> /// item对象 /// </summary> public RectTransform ItemTrans; /// <summary> /// 初始化 /// </summary> virtual public void Init() { ItemTrans = this.GetComponent<RectTransform>(); OriginalPos = this.ItemTrans.localPosition; } /// <summary> /// 显示信息 /// </summary> /// <param name="info"></param> virtual public void Show(int itemId, object e = null) { this.ItemId = itemId; this.Param = e; } /// <summary> /// 修改信息 /// </summary> /// <param name="e"></param> virtual public void Modify(object e = null) { } /// <summary> /// 添加事件 /// </summary> virtual public void AddEvent() { } }
/// <summary> /// UI辅助类 /// </summary> using UnityEngine; public class UIHelper { /// <summary> /// 创建一定数量的子对象 /// </summary> /// <param name="needNum"></param> /// <param name="content"></param> /// <param name="childPath"></param> 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(); } } } /// <summary> /// 清除子对象 /// </summary> /// <param name="go"></param> 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