Unity 无限循环缩放列表

美术需要一个可以无限滑动的列表 然后当前选中的节点需要放大这样的需求.

自己花了点时间研究了下.代码如下

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

public class LoopList : MonoBehaviour, IEndDragHandler, IDragHandler, IBeginDragHandler
{
    public RectTransform item;
    public RectTransform content;

    [Tooltip("初始化时选中的下标")]
    public int ViewStartPos = 0;

    /// <summary>
    /// 速度
    /// </summary>
    [Tooltip("列表滑动的速度"), Range(0, 1)]
    public float MoveSpeed = 0.2f;

    /// <summary>
    /// 大小的缩放倍数
    /// </summary>
    [Tooltip("Item的缩放比例 列表中间的Item会被改变大小 就是根据这个的值来的")]
    public float Scaling = 1.2f;

    /// <summary>
    /// 单个Item移动到目标位置需要的时间
    /// </summary>
    [Tooltip("单个Item的移动时间 从他的原始位置移动到一个位置时需要的时间")]
    public float SingleMoveItemTargetTime = 0.5f;

    /// <summary>
    /// 移动列表时,移动到下一个Item时的移动距离
    /// </summary>
    [Tooltip("移动列表时,移动到下一个Item时的移动距离")]
    public int CanMoveItemOffsetX = 70;

    /// <summary>
    /// false的情况上下滑动
    /// </summary>
    [Tooltip("是否是水平滑动")]
    public bool IsHorizontal = true;

    /// <summary>
    /// item的创建数量
    /// </summary>
    private int CreatCount;

    /// <summary>
    /// item的总数量
    /// </summary>
    private int Count;

    private List<Item> ItemList = new List<Item>();

    /// <summary>创建item后外部初始化item事件</summary>
    Action<GameObject, int, int> CallBackList;
    /// <summary>玩家滑动到item的回调事件</summary>
    Action<GameObject, int, int> CallBackMoveTargetEnd;

    private RectTransform Rect;

    int LeftIndex;
    int RightIndex;
    int CenterObjListIndex = 0;

    /// <summary>最中心的X数值</summary>
    float CenterXPos;

    /// <summary>开始缩放的左右X坐标</summary>
    float LeftXPos;
    float RightXPos;
    /// <summary>生成Item宽度的一半用于计算放大系数</summary>
    float Offset;

    float Epsilon = 0.001f;

    /// <summary>创建物体的宽度</summary>
    private float ItemSize;

    /// <summary>右侧边界位置</summary>
    private Vector2 LastPos;
    public void Init(int count, Action<GameObject, int, int> callBackList = null, Action<GameObject, int, int> moveTargetEnd = null)
    {
        Rect = GetComponent<RectTransform>();
        Count = count;
        CallBackList = callBackList;
        CallBackMoveTargetEnd = moveTargetEnd;

        CreatItem();
    }

    bool isCanEndDrag;
    private Vector3 dragPos;
    private Vector3 beginDragPos;

    public void OnBeginDrag(PointerEventData eventData)
    {
        isCanEndDrag = true;
        dragPos = eventData.position;
        beginDragPos = eventData.position;
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (isMoveing)
        {
            StopAutoMove();
        }

        float xMoveDis;
        if (IsHorizontal)
            xMoveDis = dragPos.x - eventData.position.x;
        else
            xMoveDis = dragPos.y - eventData.position.y;
        MoveList(xMoveDis);
        dragPos = eventData.position;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        if (!isCanEndDrag)
        {
            return;
        }
        int index = -1;
        int objIndex = -1;
        float minDis = CenterXPos;
        for (int i = 0; i < CreatCount; i++)
        {
            Item _item = ItemList[i];
            float x = GetItemPos(_item);
            float dis = Mathf.Abs(x - CenterXPos);
            if (dis < minDis)
            {
                minDis = dis;
                index = _item.index;
                objIndex = i;
            }
        }

        if (objIndex == CenterObjListIndex)
        {
            float distance = IsHorizontal ? (eventData.position.x - beginDragPos.x) * MoveSpeed : (eventData.position.y - beginDragPos.y) * MoveSpeed;
            if (Mathf.Abs(distance) > CanMoveItemOffsetX && CanMoveItemOffsetX != 0)
            {
                objIndex = distance > 0 ? objIndex - 1 : objIndex + 1;
                index = 0;
            }
        }

        if (objIndex < 0)
        {
            objIndex = CreatCount - 1;
        }

        if (objIndex >= CreatCount)
        {
            objIndex = 0;
        }

        if (index != -1)
        {
            MoveToIndex(ItemList[objIndex].index, objIndex);
        }

        isCanEndDrag = false;
    }

    /// <summary>
    /// 刷新列表 当时数量发生改变时执行这个
    /// </summary>
    public void ManaRefresh(int count, int moveToIndex = -1, Action<GameObject, int, int> callBack = null)
    {
        Count = count;
        int centerIndex = moveToIndex != -1 ? moveToIndex : ItemList[CenterObjListIndex].index;

        float xMin = 0;
        float xMax = 0;

        for (int i = 0; i < CreatCount; i++)
        {
            Item _item = ItemList[i];
            int offset = centerIndex + (i - 2);
            int index = offset;
            if (offset <= 0)
            {
                index = Count + offset;
            }
            else if (offset > Count)
            {
                index = offset - Count;
            }

            _item.index = index;
            _item.go.name = i.ToString();

            _item.rect.anchoredPosition = new Vector2(offset + ItemSize * i, 0);
            _item.go.transform.localScale = Vector3.one;

            if (xMin == 0 || _item.rect.anchoredPosition.x < xMin)
            {
                LeftIndex = _item.index;
                xMin = _item.rect.anchoredPosition.x;
            }
            if (xMax == 0 || _item.rect.anchoredPosition.x > xMax)
            {
                RightIndex = _item.index;
                xMax = _item.rect.anchoredPosition.x;
            }
            CallBackList?.Invoke(_item.go, index, i);
        }

        CaluteScaleOffset(true);

        callBack?.Invoke(ItemList[CenterObjListIndex].go, ItemList[CenterObjListIndex].index, CenterObjListIndex);
    }

    public void Refresh(int index = 0)
    {
        for (int i = 0; i < CreatCount; i++)
        {
            Item _item = ItemList[i];
            if (index != 0 || index == _item.index)
            {
                CallBackList?.Invoke(_item.go, _item.index, i);
            }
        }
    }

    /// <summary>
    /// 移动到指定下标
    /// </summary>
    public void MoveToIndex(int index, int objListIndex = -1)
    {
        if (isMoveing)
        {
            return;
        }
        index = index > Count ? Count : index;

        Item _item = ItemList[CenterObjListIndex];


        float moveDir = 1;

        int rightDisCount = (index - _item.index + Count) % Count;
        int leftDisCount = (_item.index - index + Count) % Count;
        int moveItemCount = rightDisCount;

        if (leftDisCount < rightDisCount)
        {
            moveDir = -1;
            moveItemCount = leftDisCount;
        }

        if (index == -1)
        {
            moveDir = -1;
            moveItemCount = 1;
        }
        else if (index == -2)
        {
            moveDir = 1;
            moveItemCount = 1;
        }

        float pos = CenterXPos - GetItemPos(_item);
        float moveDistance = moveItemCount * ItemSize * moveDir - pos;

        if (objListIndex >= 0)
        {
            moveDistance = GetItemPos(ItemList[objListIndex]) - CenterXPos;
        }
        float dis = Mathf.Abs(pos);
        float scaleOffset = Mathf.Abs(_item.rect.transform.localScale.x - Scaling);
        if ((_item.index == index && scaleOffset < Epsilon && dis < Epsilon) || Mathf.Abs(moveDistance) < Epsilon)
        {
            CallBackMoveTargetEnd?.Invoke(_item.go, _item.index, _item.index);
            return;
        }
        float moveTargetTime = SingleMoveItemTargetTime * moveItemCount;
        StartAutoMove(moveDistance, moveTargetTime);
    }

    private bool isMoveing = false;
    private float TargetOffsetX;
    private float MoveTargetTime;
    private float ElapsedTime;
    private float MoveDuration;
    private float MovedX;
    private void Update()
    {
        if (MoveTargetTime == 0)
            return;
        if (ElapsedTime < MoveTargetTime)
        {
            isMoveing = true;
            ElapsedTime += Time.deltaTime;
            float t = Mathf.Clamp01(ElapsedTime / MoveTargetTime);
            MoveDuration = Mathf.Lerp(0, TargetOffsetX, t);
            MoveList(MoveDuration - MovedX);
            MovedX = MoveDuration;
        }
        else
        {
            StopAutoMove();
            CallBackMoveTargetEnd?.Invoke(ItemList[CenterObjListIndex].go, ItemList[CenterObjListIndex].index, CenterObjListIndex);
        }
    }

    private void StartAutoMove(float targetOffsetX, float moveTargetTime)
    {
        TargetOffsetX = targetOffsetX / MoveSpeed;
        if (moveTargetTime == 0 && targetOffsetX != 0)
            MoveTargetTime = SingleMoveItemTargetTime;
        else
            MoveTargetTime = moveTargetTime;
    }

    private void StopAutoMove()
    {
        TargetOffsetX = 0;
        MoveTargetTime = 0;
        ElapsedTime = 0;
        MoveDuration = 0;
        MovedX = 0;
        isMoveing = false;
    }

    //移动列表
    void MoveList(float x)
    {
        x = -x * MoveSpeed;
        if (x == 0)
        {
            return;
        }

        bool refreshItem = false;
        if (IsHorizontal)
        {
            for (int i = 0; i < CreatCount; i++)
            {
                Item _item = ItemList[i];
                Vector2 pos = new Vector2(_item.rect.anchoredPosition.x + x, 0);
                if (pos.x < 0)
                {
                    //把它放到最右边 (防止单次偏移过大进行额外计算 如 -4 这种值 直接放到该处会导致节点偏移)
                    pos += LastPos;
                    int index = (RightIndex + 1) % Count;
                    if (++LeftIndex == Count)
                        LeftIndex = 0;
                    RightIndex = index;
                    _item.index = index;
                    refreshItem = true;
                    _item.go.gameObject.SetActive(true);
                    int centerObjIndex = CenterObjListIndex + 1;
                    centerObjIndex = centerObjIndex > (CreatCount - 1) ? 0 : centerObjIndex;
                    CenterObjListIndex = centerObjIndex;
                }
                else if (pos.x > LastPos.x)
                {
                    //把它放到最左边
                    pos -= LastPos;
                    int index = LeftIndex - 1 < 0 ? Count - 1 : --LeftIndex;
                    if (--RightIndex < 0)
                        RightIndex = Count - 1;
                    LeftIndex = index;
                    _item.index = index;
                    refreshItem = true;
                    _item.go.gameObject.SetActive(true);
                    int centerObjIndex = CenterObjListIndex - 1;
                    centerObjIndex = centerObjIndex < 0 ? CreatCount - 1 : centerObjIndex;
                    CenterObjListIndex = centerObjIndex;
                }
                _item.rect.anchoredPosition = pos;
                if (refreshItem)
                {
                    refreshItem = false;
                    CallBackList?.Invoke(_item.go, _item.index, i);
                }
            }
        }
        else
        {
            for (int i = 0; i < CreatCount; i++)
            {
                Item _item = ItemList[i];
                Vector2 pos = new Vector2(0, _item.rect.anchoredPosition.y + x);
                if (pos.y < 0)
                {
                    //把它放到最上边
                    pos += LastPos;
                    int index = (RightIndex + 1) % Count;
                    if (++LeftIndex == Count)
                        LeftIndex = 0;
                    RightIndex = index;
                    _item.index = index;
                    refreshItem = true;
                    _item.go.gameObject.SetActive(true);
                    int centerObjIndex = CenterObjListIndex + 1;
                    centerObjIndex = centerObjIndex > (CreatCount - 1) ? 0 : centerObjIndex;
                    CenterObjListIndex = centerObjIndex;
                }
                else if (pos.y > LastPos.y)
                {
                    //把它放到最下边
                    pos -= LastPos;
                    int index = LeftIndex - 1 < 0 ? Count - 1 : --LeftIndex;
                    if (--RightIndex < 0)
                        RightIndex = Count - 1;
                    LeftIndex = index;
                    _item.index = index;
                    refreshItem = true;
                    _item.go.gameObject.SetActive(true);
                    int centerObjIndex = CenterObjListIndex - 1;
                    centerObjIndex = centerObjIndex < 0 ? CreatCount - 1 : centerObjIndex;
                    CenterObjListIndex = centerObjIndex;
                }
                _item.rect.anchoredPosition = pos;
                if (refreshItem)
                {
                    refreshItem = false;
                    CallBackList?.Invoke(_item.go, _item.index, i);
                }
            }
        }

        CaluteScaleOffset();
    }

    /// <summary>
    /// 立即移动到指定位置  没有过程
    /// </summary>
    /// <param name="index"></param>
    public void MoveToIndexImmediately(int index)
    {
        int startIndex = index - CreatCount / 2;
        if (startIndex < 0)
            startIndex += Count;

        for (int i = 0; i < ItemList.Count; i++)
        {
            int _index = (startIndex + i) % Count;
            var _item = ItemList[i];
            _item.rect.anchoredPosition = IsHorizontal ? new Vector2(Offset + ItemSize * i, 0) : new Vector2(0, Offset + ItemSize * i); ;
            _item.index = _index;
            CallBackList?.Invoke(_item.go, _item.index, i);
        }
        LeftIndex = ItemList[0].index;
        RightIndex = ItemList[CreatCount - 1].index;
        CaluteScaleOffset(true);
    }

    /// <summary>
    /// 计算缩放
    /// </summary>
    private void CaluteScaleOffset(bool isInit = false)
    {
        for (int i = 0; i < CreatCount; i++)
        {
            Item _item = ItemList[i];

            float x = GetItemPos(_item);

            float dis;
            float offset;

            if (x >= CenterXPos)
            {
                if (RightXPos - x <= 0)
                {
                    dis = 0;
                }
                else
                {
                    offset = RightXPos - x;
                    dis = offset / Offset;
                }
            }
            else
            {
                if (x - LeftXPos <= 0)
                {
                    dis = 0;
                }
                else
                {
                    offset = x - LeftXPos;
                    dis = offset / Offset;
                }
            }

            Vector3 newScale = Vector3.one + (Vector3.one * Scaling - Vector3.one) * dis;
            _item.rect.localScale = newScale;

            float offsetDis = Mathf.Abs(1 - dis);

            if (offsetDis < Epsilon || dis == 1)
            {
                if (isInit)
                {
                    CallBackMoveTargetEnd?.Invoke(_item.go, _item.index, i + 1);
                }

                CenterObjListIndex = i;
            }
        }
    }

    void CreatItem()
    {
        if (Count == 0)
        {
            item.gameObject.SetActive(false);
            return;
        }
        ItemSize = IsHorizontal ? item.rect.width : item.rect.height;
        int count = (int)(IsHorizontal ? Rect.rect.width / ItemSize : Rect.rect.height / ItemSize);
        if (count > Count)
            count = Count;
        CreatCount = (count % 2 != 0) ? count : count + 1;
        CreatCount += 2;

        ItemList.Clear();
        int startIndex = ViewStartPos - CreatCount / 2;
        if (startIndex < 0)
            startIndex += Count;
        Vector2 centerPos = new Vector2(0.5f, 0.5f);
        content.anchorMin = centerPos;
        content.anchorMax = centerPos;
        content.pivot = centerPos;
        if (IsHorizontal)
            content.sizeDelta = new Vector2(ItemSize * CreatCount, Rect.sizeDelta.y);
        else
            content.sizeDelta = new Vector2(Rect.sizeDelta.x, ItemSize * CreatCount);
        content.anchoredPosition = Vector2.zero;

        Vector2 anchors;
        if (IsHorizontal)
        {
            LastPos = new Vector2(ItemSize * CreatCount, 0);
            anchors = new Vector2(0, 0.5f);
        }
        else
        {
            LastPos = new Vector2(0, ItemSize * CreatCount);
            anchors = new Vector2(0.5f, 0);
        }
        Offset = ItemSize / 2;
        CenterXPos = ItemSize * (CreatCount * 0.5f);
        LeftXPos = CenterXPos - Offset;
        RightXPos = CenterXPos + Offset;

        RectTransform rect = item.GetComponent<RectTransform>();
        rect.anchorMin = anchors;
        rect.anchorMax = anchors;
        rect.pivot = centerPos;

        for (int i = 0; i < CreatCount; i++)
        {
            Transform children = i < content.transform.childCount ? content.transform.GetChild(i) : null;
            GameObject go = children != null ? children.gameObject : GameObject.Instantiate(item.gameObject);
            RectTransform goRect = go.GetComponent<RectTransform>();
            go.transform.SetParent(content.transform);
            goRect.anchoredPosition = IsHorizontal ? new Vector2(Offset + ItemSize * i, 0) : new Vector2(0, Offset + ItemSize * i);
            go.name = i.ToString();
            int index = (startIndex + i) % Count;
            Item _item = new Item()
            {
                go = go,
                rect = goRect,
                index = index
            };

            CallBackList?.Invoke(go, _item.index, i);
            ItemList.Add(_item);
        }

        LeftIndex = ItemList[0].index;
        RightIndex = ItemList[CreatCount - 1].index;

        CaluteScaleOffset(true);
    }

    private float GetItemPos(Item item)
    {
        return IsHorizontal ? item.rect.anchoredPosition.x : item.rect.anchoredPosition.y;
    }

    class Item
    {
        public GameObject go;
        public RectTransform rect;
        public int index;
    }
}

使用方法为 创建一个Scroll Rect 然后把ViewPort拖出来. Scroll Rect就可以删除了.然后给ViewProt添加该脚本并把Content拖到参数栏中.最后把需要生成的item也拖到参数栏的Item中,后续的拖动手感可以根据提供的参数自行调整.

如果使用上有问题,或者觉得有更好的方式也欢迎大家来讨论

PS:需要在代码中其他地方调用下该组件的Init方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值