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

前言

如题的功能在项目中经常用到,滚动的信息内容,我们用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设置也得是类似的设置,并且多行或者多列时需要固定值:

在这里插入图片描述

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

  • 21
    点赞
  • 89
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
实现一个item列表可以通过 Unity3D 中的UGUI组件来完成,以下是一个简单的实现步骤: 1. 创建一个ScrollView对象,它会成为你的item列表的容器。 2. 在ScrollView对象下创建一个Content对象,用于放置所有的item。 3. 创建一个item的Prefab,包你需要显示的元素。 4. 在运行时,动态生成多个item对象,将它们放置在Content对象下,以此来构建item列表。 5. 根据需要,可以对item列表进行滚动、添加或删除item等操作。 具体实现可以参考以下步骤: 1. 创建ScrollView和Content对象 在场景中创建一个空对象,命名为ScrollView。将Canvas组件的Render Mode设置为Screen Space - Overlay,然后将ScrollView对象的RectTransform组件的Anchors和Pivot都设置为(0, 0)。这样,ScrollView对象的左下角就会位于屏幕左下角。在ScrollView对象下创建一个空对象,命名为Content。将Content对象的RectTransform组件的Anchors和Pivot也都设置为(0, 0),以便于它能够与ScrollView对象的位置重合。 2. 创建item的Prefab 在项目资源中创建一个新的Prefab,将你需要显示的元素放入其中。例如,可以在Prefab中添加一个Text对象,用于显示item的标题。确保这个Prefab的RectTransform组件的Anchors和Pivot都设置为(0, 0),以便于在生成item时它们能够正确地布局。 3. 动态生成item对象 在脚本中,使用Instantiate()方法动态生成多个item对象,并将它们作为Content对象的子对象。例如: ```csharp public GameObject itemPrefab; public int itemCount = 20; void Start() { for (int i = 0; i < itemCount; i++) { GameObject item = Instantiate(itemPrefab, content.transform); // 设置item的位置和大小 item.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -i * item.GetComponent<RectTransform>().rect.height); } } ``` 这段代码会生成20个item对象,将它们放置在Content对象下,并设置它们的位置和大小。这里假设item的高度是固定的。 4. 对item列表进行滚动 为了让item列表能够滚动,需要将ScrollView对象下的Scrollbar组件与Content对象的RectTransform组件相绑定。在ScrollView对象下添加一个Scrollbar组件,将它的Direction设置为Vertical,并将它的Size设置为0.2(或根据需要调整)。然后将Scrollbar组件的Value属性绑定到Content对象的RectTransform组件的anchoredPosition.y属性上。这样,当拖动Scrollbar时,Content对象就会相应地向上或向下滚动。 5. 添加或删除item 如果需要动态地添加或删除item,可以在脚本中使用Instantiate()和Destroy()方法来完成。例如: ```csharp public void AddItem() { GameObject item = Instantiate(itemPrefab, content.transform); // 设置新的item的位置和大小 item.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -itemCount * item.GetComponent<RectTransform>().rect.height); itemCount++; } public void RemoveItem() { if (itemCount > 0) { Destroy(content.transform.GetChild(itemCount - 1).gameObject); itemCount--; } } ``` 这样,就可以在运行时动态地添加或删除item了。当添加一个新的item时,只需生成一个新的GameObject,并将它放置在Content对象下;当删除一个item时,只需销毁Content对象下的最后一个子对象即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十幺卜入

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值