ScrollView 最优使用方案

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]


你可能感兴趣的:(Unity,C#)