造个轮子,Unity UGUI 循环对象池ScrollView

造个轮子,Unity UGUI 循环对象池ScrollView

只用Mask做显示切割,不依赖其他任何UGUI控件,一个纯粹的轮子。
轮子纯粹,兼容性就高。方便各种改造。欢迎交流讨论。

使用到的MonoPool参考我的另一片文章链接: 留个档,MonoBehaviour对象池


核心代码:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ScrollViewEX : MonoBehaviour
{
    public enum Type : byte
    {
        Horizontal, // 横向
        Vertical    // 纵向
    }

    [Header("Base")]
    [SerializeField] Type m_eDirection = Type.Vertical;
    [SerializeField] ScrollViewEX_Item m_prefabItem = null;
    [SerializeField] Image m_PanelRect = null;
    [SerializeField] float m_nDragScale = 0;    // 拖动距离的缩放;

    // data
    private object[] m_arrDatas = null;     // 所有数据
    private MonoPool<ScrollViewEX_Item> m_poolItems = null;   // 对象池
    private float m_nPanelSize = 0;   // 限时范围
    private float m_nItemSize = 0;    // 单个cell尺寸
    private float m_nPosMax = 0;        // 滚动坐标上限

    // runtime
    private Dictionary<int, ScrollViewEX_Item> m_dicShowingItems = new Dictionary<int, ScrollViewEX_Item>(); // 显示中的内容
    private float m_nPos = 0;           // 当前坐标
    private float m_nPosCache = 0;      // 拖拽过程中的坐标

    private void Awake()
    {
        GameObject go = m_PanelRect.gameObject;
        UIEventListener.Get(go).onBeginDrag = OnDragBegin;
        UIEventListener.Get(go).onDrag = OnDraging;
        UIEventListener.Get(go).onEndDrag = OnDragEnd;
        m_poolItems = new MonoPool<ScrollViewEX_Item>(m_prefabItem, this.transform, null, true);

        Vector2 v2PanelSize = m_PanelRect.rectTransform.sizeDelta;
        m_nItemSize = m_eDirection == Type.Horizontal ? m_prefabItem.Size.x : m_prefabItem.Size.y;
        m_nPanelSize = m_eDirection == Type.Horizontal ? v2PanelSize.x : v2PanelSize.y;
    }

    private void Start()
    {
        // 这是一个测试
        Init(new object[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 });
    }

    public void Init(object[] arrDatas)
    {
        m_arrDatas = arrDatas;

        m_nPosMax = m_arrDatas.Length * m_nItemSize - m_nPanelSize;
        if (m_eDirection == Type.Horizontal)
        {
            m_nPosMax = 0 - m_nPosMax;
        }

        Reposition();
    }

    public void Reposition()
    {
        m_nPos = 0;
        MathViewIndexs(0);
        SyncItemsPos(0);
    }
    
    /// <summary>
    /// 刷新范围内需要显示的内容
    /// </summary>
    private void MathViewIndexs(float nPos)
    {
        if (m_eDirection == Type.Horizontal)
        {
            nPos = 0 - nPos;
        }
        float ne = nPos + m_nPanelSize;
        int nCountFrom = (int)(nPos / m_nItemSize);
        int nCountTo = (int)(ne / m_nItemSize);
        if (nCountFrom < 0)
        {
            nCountFrom = 0 - nCountFrom;
        }
        if (nCountTo < 0)
        {
            nCountTo = 0 - nCountTo;
        }
        RefreshList(nCountFrom, nCountTo);
    }

    private void RefreshList(int nFrom, int nTo)
    {
        List<int> listShowing = new List<int>(m_dicShowingItems.Keys);
        for (int i = nFrom; i <= nTo; i++)
        {
            listShowing.Remove(i);
        }
        for (int i = 0; i < listShowing.Count; i++)
        {
            int nKey = listShowing[i];
            var one = m_dicShowingItems[nKey];
            m_dicShowingItems.Remove(nKey);
            one.Release();
            m_poolItems.FreeOne(one);
        }
        for (int i = nFrom; i <= nTo; i++)
        {
            if (i >= m_arrDatas.Length)
            {
                break;
            }
            if (!m_dicShowingItems.ContainsKey(i))
            {
                var item = m_poolItems.GetOne();
                item.m_nDefaultPos = m_nItemSize * 0.5f + i * m_nItemSize;
                item.Init(m_arrDatas[i]);
                m_dicShowingItems.Add(i, item);
            }
        }
    }

    /// <summary>
    /// 刷新所有内容坐标
    /// </summary>
    private void SyncItemsPos(float nPos = 0)
    {
        foreach (var item in m_dicShowingItems)
        {
            SyncItemPos(item.Value, nPos);
        }
    }

    /// <summary>
    /// 同步偏移坐标
    /// </summary>
    private void SyncItemPos(ScrollViewEX_Item item, float nPos)
    {
        Vector3 pos = item.transform.localPosition;
        if (m_eDirection == Type.Horizontal)
        {
            pos.x = item.m_nDefaultPos - m_nPanelSize * 0.5f + nPos;
        }
        else
        {
            pos.y = -item.m_nDefaultPos + m_nPanelSize * 0.5f + nPos;
        }
        item.transform.localPosition = pos;
    }


    private void OnDragBegin(PointerEventData e)
    {
        m_nPosCache = m_nPos;
        m_nPosLastFrame = m_nPosCurFrame = m_nPos;
        m_isPlaySpring = false;
    }

    private void OnDraging(PointerEventData e)
    {
        Vector2 v2Move = e.position - e.pressPosition;
        var movepos = (m_eDirection == Type.Horizontal ? v2Move.x : v2Move.y) * m_nDragScale;
        m_nPosCache = FilterPosInRange(m_nPos + movepos);
        MathViewIndexs(m_nPosCache);
        SyncItemsPos(m_nPosCache);

        m_nPosLastFrame = m_nPosCurFrame;
        m_nPosCurFrame = m_nPosCache;
    }

    private void OnDragEnd(PointerEventData e)
    {
        m_nPos = m_nPosCache;
        PlaySpring();
    }

    [Header("Base-Spring")]
    [SerializeField] float m_nSpringLenScale = 0;           // spring距离倍数
    [SerializeField] float m_nSpringSmoothTime = 0;         // spring时间
    [SerializeField] float m_nSpringStopThreshold = 0;      // 判断是否缓动静止

    private bool m_isPlaySpring = false;

    private float m_nPosLastFrame = 0;  // 记录两帧之间的差异
    private float m_nPosCurFrame = 0;   // 记录两帧之间的差异


    private float m_nPosTarget = 0;     // 缓动 目标位置

    private void PlaySpring()
    {
        m_nPosTarget = m_nPos + (m_nPosCurFrame - m_nPosLastFrame) * m_nSpringLenScale;

        nVelocity = 0;
        m_isPlaySpring = true;
    }

    private float nVelocity = 0.0f; // SmoothDamp 的必要参数

    void Update()
    {
        if (!m_isPlaySpring)
        {
            return;
        }
        m_nPos = Mathf.SmoothDamp(m_nPos, m_nPosTarget, ref nVelocity, m_nSpringSmoothTime);
        m_nPos = FilterPosInRange(m_nPos);
        MathViewIndexs(m_nPos);
        SyncItemsPos(m_nPos);

        float yv = nVelocity < 0 ? (0 - nVelocity) : nVelocity;
        if (yv <= m_nSpringStopThreshold)
        {
            m_isPlaySpring = false;
        }
    }

    private float FilterPosInRange(float nPos)
    {
        float nResult =
            Mathf.Clamp
            (
                nPos,
                m_eDirection == Type.Horizontal ? m_nPosMax : 0,
                m_eDirection == Type.Horizontal ? 0 : m_nPosMax
            );
        return nResult;
    }
}

单个内容对象基类

using UnityEngine;

public class ScrollViewEX_Item : MonoBehaviour
{
    [SerializeField] Vector2 m_size = Vector2.zero;

    public virtual Vector2 Size
    {
        get
        {
            return m_size;
        }
    }

    [HideInInspector] public float m_nDefaultPos = 0;

    public virtual void Init(object data)
    {
    }

    public virtual void Release()
    {
    }
}

测试用内容对象脚本

using UnityEngine;
using UnityEngine.UI;

public class TestRoundItem : ScrollViewEX_Item
{
    [SerializeField] Text m_txt = null;

    public override void Init(object data)
    {
        m_txt.text = ((int)data).ToString();
    }

    public override void Release()
    {
        m_txt.text = "";
    }
}

unity里的面板:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


程序学无止尽。
欢迎大家沟通,有啥不明确的,或者不对的,也可以和我私聊
我的QQ 334524067 神一般的狄狄

猜你喜欢

转载自blog.csdn.net/qq_37776196/article/details/126401290