Unity3d C# UGUI实现一个自动循环滚动的列表(ScrollRect)的功能(含工程源码)

Source

前言

如题的功能在项目中经常用到,滚动的信息内容,我们用scrollbar的value来控制滚动是可以实现的,不过当value为1时,我们从0继续循环会造成有闪烁的情况而且比较突兀,经过一段时间的研究终于实现了该功能。

效果

分别方向的移动

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实现

自动滚动的思路就是不断的增加某一个方向的偏移值就可以实现,循环滚动时,将最早移出的节点移至滚动队列的最尾端即可,同时计算新的偏移值并同步,让列表看不出抖动,即可实现一直循环滚动,在此过程中将禁用ScrollRect组件,因为ScrollRect组件在节点顺序变化时会造成显示抖动,同时还需根据排序的组件(HorizontalOrVerticalLayoutGroup/GridLayoutGroup)进行间隔的计算。当鼠标悬停时启用ScrollRect组件并暂停滚动,鼠标离开时启用自动滚动并禁用ScrollRect。

搭建UI

如图的搭建一个列表

在这里插入图片描述

定义滚动方向

public enum ScrollDir
{
    
      
    BottomToTop = 1,
    TopToBottom = 2,
    LeftToRight = 3,
    RightToLeft = 4
}

定义如上四个方向方便选择设置和分开处理。

初始化数值

        scrollrect = gameObject.GetComponent<ScrollRect>();
        scrolltran = scrollrect.GetComponent<RectTransform>();
        LayoutGroup = scrollrect.content.GetComponent<HorizontalOrVerticalLayoutGroup>();
        GridGroup = scrollrect.content.GetComponent<GridLayoutGroup>();

        et = gameObject.GetComponent<EventTrigger>();
        if (et == null)
            et = gameObject.AddComponent<EventTrigger>();

        //设置滚动间隔
        if (LayoutGroup != null)
            Space = LayoutGroup.spacing;
        else if (GridGroup != null)
        {
    
      
            switch (AutoScrollDir)
            {
    
      
                case ScrollDir.BottomToTop://由底至顶滚动  向上
                case ScrollDir.TopToBottom://由顶至底滚动  向下
                    Space = GridGroup.spacing.y;
                    break;
                case ScrollDir.LeftToRight://由左至右滚动 →
                case ScrollDir.RightToLeft://由右至左滚动 ←
                    Space = GridGroup.spacing.x;
                    break;
                default:
                    Space = 0;
                    break;
            }

        }

        //设置子节点高度和宽度
        if (LayoutGroup != null && scrollrect.content.childCount > 0)
        {
    
      
            ItemWidth = scrollrect.content.GetChild(0).GetComponent<RectTransform>().sizeDelta.x;
            ItemHeight = scrollrect.content.GetChild(0).GetComponent<RectTransform>().sizeDelta.y;
        }
        else if (GridGroup != null)
        {
    
      
            ItemWidth = GridGroup.cellSize.x;
            ItemHeight = GridGroup.cellSize.y;
        }
        AddETEvent(et, EventTriggerType.PointerEnter, OnPointerIn);
        AddETEvent(et, EventTriggerType.PointerExit, OnPointerOut);
    

如上代码初始化时候,主要寻找相关组件,排序组件和滚动组件等,同时根据不同组件获取间隔数值,还有排序对象的高宽度,以及事件绑定。这里通过添加EventTrigger组件,并绑定PointerEnter 和 PointerExit来实现鼠标悬停和退出功能。

界面的配置如图:

在这里插入图片描述

自动滚动

 //开始自动滑动
    void DoAutoScroll()
    {
    
      
        switch (AutoScrollDir)
        {
    
      
            //由底至顶滚动  向上
            case ScrollDir.BottomToTop:
                {
    
      
                    if (scrollrect.content.sizeDelta.y > scrolltran.sizeDelta.y + (ItemHeight + Space))
                    {
    
      
                        scrollrect.content.anchoredPosition3D += new Vector3(0, step, 0);
                        if (scrollrect.content.anchoredPosition3D.y >= (scrollrect.content.sizeDelta.y - scrolltran.sizeDelta.y) / 2)
                        {
    
      
                            if (GridGroup != null && GridGroup.constraintCount > 1)
                            {
    
      
                                for (int i = 0; i < GridGroup.constraintCount; i++)
                                    scrollrect.content.GetChild(0).transform.SetAsLastSibling();
                                scrollrect.content.anchoredPosition3D -= new Vector3(0, (ItemHeight + Space), 0);
                            }
                            else
                            {
    
      
                                scrollrect.content.GetChild(0).transform.SetAsLastSibling();
                                scrollrect.content.anchoredPosition3D -= new Vector3(0, (ItemHeight + Space), 0);
                            }
                        }
                    }
                }
                break;
            //由顶至底滚动  向下
            case ScrollDir.TopToBottom:
                {
    
      
                    if (scrollrect.content.sizeDelta.y > scrolltran.sizeDelta.y + (ItemHeight + Space))
                    {
    
      
                        scrollrect.content.anchoredPosition3D -= new Vector3(0, step, 0);
                        if (scrollrect.content.anchoredPosition3D.y <= (scrollrect.content.sizeDelta.y - scrolltran.sizeDelta.y) / 2)
                        {
    
      
                            if (GridGroup != null && GridGroup.constraintCount > 1)
                            {
    
      
                                for (int i = 0; i < GridGroup.constraintCount; i++)
                                    scrollrect.content.GetChild(scrollrect.content.childCount - 1).transform.SetAsFirstSibling();
                                scrollrect.content.anchoredPosition3D += new Vector3(0, (ItemHeight + Space), 0);
                            }
                            else
                            {
    
      
                                scrollrect.content.GetChild(scrollrect.content.childCount - 1).transform.SetAsFirstSibling();
                                scrollrect.content.anchoredPosition3D += new Vector3(0, (ItemHeight + Space), 0);
                            }
                        }
                    }
                }
                break;

            //由左至右滚动 →
            case ScrollDir.LeftToRight:
                {
    
      
                    if (scrollrect.content.sizeDelta.x > scrolltran.sizeDelta.x + (ItemWidth + Space))
                    {
    
      
                        scrollrect.content.anchoredPosition3D += new Vector3(step, 0, 0);
                        if (scrollrect.content.anchoredPosition3D.x >= -(scrollrect.content.sizeDelta.x - scrolltran.sizeDelta.x) / 2)
                        {
    
      
                            if (GridGroup != null && GridGroup.constraintCount > 1)
                            {
    
      
                                for (int i = 0; i < GridGroup.constraintCount; i++)
                                    scrollrect.content.GetChild(scrollrect.content.childCount - 1).transform.SetAsFirstSibling();
                                scrollrect.content.anchoredPosition3D -= new Vector3((ItemWidth + Space), 0, 0);
                            }
                            else
                            {
    
      
                                scrollrect.content.GetChild(scrollrect.content.childCount - 1).transform.SetAsFirstSibling();
                                scrollrect.content.anchoredPosition3D -= new Vector3((ItemWidth + Space), 0, 0);
                            }
                        }
                    }
                }
                break;
            //由右至左滚动 ←
            case ScrollDir.RightToLeft:
                {
    
      
                    if (scrollrect.content.sizeDelta.x > scrolltran.sizeDelta.x + (ItemWidth + Space))
                    {
    
      
                        scrollrect.content.anchoredPosition3D -= new Vector3(step, 0, 0);
                        if (scrollrect.content.anchoredPosition3D.x <= -(scrollrect.content.sizeDelta.x - scrolltran.sizeDelta.x) / 2)
                        {
    
      
                            if (GridGroup != null && GridGroup.constraintCount > 1)
                            {
    
      
                                for (int i = 0; i < GridGroup.constraintCount; i++)
                                    scrollrect.content.GetChild(0).transform.SetAsLastSibling();
                                scrollrect.content.anchoredPosition3D += new Vector3((ItemWidth + Space), 0, 0);
                            }
                            else
                            {
    
      
                                scrollrect.content.GetChild(0).transform.SetAsLastSibling();
                                scrollrect.content.anchoredPosition3D += new Vector3((ItemWidth + Space), 0, 0);
                            }
                        }
                    }
                }
                break;
            default:
                break;
        }
    }

这一段就是核心的代码,分别处理了四个方向滚动的过程,大致思路如前面提到的。主要还是Content具备自动滚动的条件:Content的高或者宽超过了视窗+1倍高宽和间隔的长度。开始自动滚动。
移动首个移出节点至队尾的条件:移动的位置已经超过一半。

其中的还有些GridLayoutGroup组件的处理,因为移动节点可能需要同时移动一排 或者一列,具体看脚本。有些特定的设置后面会进行说明。

工程源码

https://download.csdn.net/download/qq_33789001/33215369
如果打不开就是还没审核,最近审核很慢。

注意

这里特别注意的滚动页面的设置,我为了简便快速实现,这里就分成了横竖两个方向的设置做了固定适配。

横向

即从左到右或者从右到左的情况。Content如下设置:
在这里插入图片描述

竖向

即从上到下或者从下到上,Content:
在这里插入图片描述

如果不这样设置可能会有异常。

GridLayoutGroup的StartCorner设置也得是类似的设置,并且多行或者多列时需要固定值:

在这里插入图片描述

这个在移动节点时需要用到,不设置可能异常。也可尝试手动修改代码适配。