Unity3D UI 嵌套滚动视图
Unity3D 解决 UI 嵌套滚动视图滑动问题。
嵌套滚动视图
滑动问题
在游戏开发中,我们常常会遇到一种情况,在一个滚动视图列表中,每个 item 还包含了一个内嵌的滚动视图。
这样,当我们在滑动外层的滚动视图时,如果点击位置在内嵌的滚动视图上,很可能滑不动,内外层滚动视图的滑动事件出现了冲突。
如下图所示,点击位置在奖励文本上时,是可以正常滑动的。但是,点击位置在奖励列表时,滑动方向变成了左右,而不是期望的上下滑动。

解决方案
通常的解决方案是,根据拖拽的增量,判断滑动的方向,如果方向与内层的方向相同,则优先滑动内层;如果方向不同,则传递滑动事件给外层的滚动视图。
为此,我们创建一个脚本 CustomScrollRect.cs,继承 ScrollRect,并重写它的一些方法。
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;public class CustomScrollRect : ScrollRect
{protected override void Awake(){base.Awake();}public override void OnBeginDrag(PointerEventData eventData){base.OnBeginDrag(eventData);}public override void OnDrag(PointerEventData eventData){base.OnDrag(eventData);}public override void OnEndDrag(PointerEventData eventData){base.OnEndDrag(eventData);}public override void OnScroll(PointerEventData eventData){base.OnScroll(eventData);}
}
首先,在 Awake 中,获取父节点的 CustomScrollRect 组件。
这里使用的 GetComponentInParent,会从当前节点开始查找,递归遍历其父节点。
所以要从 transform.parent 开始遍历,避免获取到自己身上的 CustomScrollRect 组件。
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;public class CustomScrollRect : ScrollRect
{CustomScrollRect parent;protected override void Awake(){base.Awake();if (parent == null){parent = transform.parent.GetComponentInParent<CustomScrollRect>();}}// ...
}
同时,在类内部定义一个方向枚举,在 Awake 时,记录当前的方向。
这里仅判断是水平还是垂直,通常不会有两个方向都能滑动的情况。
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;public class CustomScrollRect : ScrollRect
{CustomScrollRect parent;enum Direction{horizontal,vertical}Direction curDirection;Direction dragDirection;protected override void Awake(){base.Awake();if (parent == null){parent = transform.parent.GetComponentInParent<CustomScrollRect>();}curDirection = horizontal ? Direction.horizontal : Direction.vertical;}// ..
}
然后在开始拖拽时,根据 eventData.delta 的 x 和 y 变量增幅哪个较大,判断滑动的方向。
当拖拽的方向和当前方向不同,且有外层滚动视图时,把 beginDragHandler 传递给外层,如果不符合条件,则执行自身的 OnBeginDrag 事件。
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;public class CustomScrollRect : ScrollRect
{// ...public override void OnBeginDrag(PointerEventData eventData){// 判断拖拽的方向dragDirection = Mathf.Abs(eventData.delta.x) > Mathf.Abs(eventData.delta.y)? Direction.horizontal : Direction.vertical;// 拖拽的方向和当前方向不同,且有外层滚动视图if (dragDirection != curDirection && parent != null){// 把 beginDragHandler 传递给外层ExecuteEvents.Execute(parent.gameObject, eventData,ExecuteEvents.beginDragHandler);// 不执行自身的 OnBeginDrag 事件return;}// 执行自身的 OnBeginDrag 事件base.OnBeginDrag(eventData);}
}
依此类推,在其他方法中也加上这样的判断(dragDirection 可以仅在开始拖拽时赋值)。
需要注意的是,
OnBeginDrag方法传递的事件是beginDragHandlerOnDrag方法传递的事件是dragHandlerOnEndDrag方法传递的事件是endDragHandlerOnScroll方法传递的事件是scrollHandler
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;public class CustomScrollRect : ScrollRect
{// ...public override void OnDrag(PointerEventData eventData){if (dragDirection != curDirection && parent != null){ExecuteEvents.Execute(parent.gameObject, eventData,ExecuteEvents.dragHandler);return;}base.OnDrag(eventData);}public override void OnEndDrag(PointerEventData eventData){if (dragDirection != curDirection && parent != null){ExecuteEvents.Execute(parent.gameObject, eventData,ExecuteEvents.endDragHandler);return;}base.OnEndDrag(eventData);}public override void OnScroll(PointerEventData eventData){if (dragDirection != curDirection && parent != null){ExecuteEvents.Execute(parent.gameObject, eventData,ExecuteEvents.scrollHandler);return;}base.OnScroll(eventData);}
}
使用说明
移除掉原来的 ScrollRect 组件,换上 CustomScrollRect 组件。
记得要拖拽 Viewport 和 Content 节点。
内外层滚动视图都需要换上 CustomScrollRect 组件。

最终效果如图:

完整代码
CustomScrollRect.cs
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;public class CustomScrollRect : ScrollRect
{CustomScrollRect parent;enum Direction{horizontal,vertical}Direction curDirection;Direction dragDirection;protected override void Awake(){base.Awake();if (parent == null){parent = transform.parent.GetComponentInParent<CustomScrollRect>();}curDirection = horizontal ? Direction.horizontal : Direction.vertical;}public override void OnBeginDrag(PointerEventData eventData){// 判断拖拽的方向dragDirection = Mathf.Abs(eventData.delta.x) > Mathf.Abs(eventData.delta.y)? Direction.horizontal : Direction.vertical;// 拖拽的方向和当前方向不同,且有外层滚动视图if (dragDirection != curDirection && parent != null){// 把 beginDragHandler 传递给外层ExecuteEvents.Execute(parent.gameObject, eventData,ExecuteEvents.beginDragHandler);// 不执行自身的 OnBeginDrag 事件return;}// 执行自身的 OnBeginDrag 事件base.OnBeginDrag(eventData);}public override void OnDrag(PointerEventData eventData){if (dragDirection != curDirection && parent != null){ExecuteEvents.Execute(parent.gameObject, eventData,ExecuteEvents.dragHandler);return;}base.OnDrag(eventData);}public override void OnEndDrag(PointerEventData eventData){if (dragDirection != curDirection && parent != null){ExecuteEvents.Execute(parent.gameObject, eventData,ExecuteEvents.endDragHandler);return;}base.OnEndDrag(eventData);}public override void OnScroll(PointerEventData eventData){if (dragDirection != curDirection && parent != null){ExecuteEvents.Execute(parent.gameObject, eventData,ExecuteEvents.scrollHandler);return;}base.OnScroll(eventData);}
}
相关文章:
Unity3D UI 嵌套滚动视图
Unity3D 解决 UI 嵌套滚动视图滑动问题。 嵌套滚动视图 滑动问题 在游戏开发中,我们常常会遇到一种情况,在一个滚动视图列表中,每个 item 还包含了一个内嵌的滚动视图。 这样,当我们在滑动外层的滚动视图时,如果点…...
你还没有将 Siri 接入GPT对话功能吗?
由于各种原因,国内ios用户目前无缘自带 AI 功能,但是这并不代表国内 ios 无法接入 AI 功能,接下来手把手带你为iPhone siri 接入 gpt 对话功能。 siri 接入 chatGPT 暂时还无法下载 ChatGPT app,或者没有账号的读者可以直接跳到…...
_C#_串口助手_字符串拼接缺失问题(未知原理)
最近使用WPF开发串口助手时,遇到一个很奇怪的问题,无论是主线程、异步还是多线程,当串口接收速度达到0.016s一次以上,就会发生字符串缺失问题并且很卡。而0.016s就一切如常,仿佛0.015s与0.016s是天堑之隔。 同一份代码…...
浅析大数据时代下的网络安全
一、大数据时代下网络安全的现状 在全球化进程不断深入发展的情况下,互联网行业发展速度也更加迅猛,人们对网络信息的需求量不断增加,所以目前已经进入了大数据时代。 随着计算机技术的不断发展,我国互联网网络规模、网民数量、…...
Mysql数据库基础篇笔记
目录 sql语句 DDL——数据库定义语言(定义库,表,字段) 数据库操作: 表操作: DML 增删改语句 DQL 语法编写顺序: 条件查询 DCL 用户管理: 权限管理: 函数 常见字符串内置函…...
rabbitmq原理及命令
目录 一、RabbitMQ原理1、交换机(Exchange)fanoutdirecttopicheaders(很少用到) 2、队列Queue3、Virtual Hosts4、基础对象 二、RabbitMQ的一些基本操作:1、用户管理2、用户角色3、vhost4、开启web管理接口5、批量删除队列 一、Ra…...
React进阶面试题(四)
React 的 reconciliation(协调)算法 Reconciliation是React的diff算法,用于比较更新前后的虚拟DOM树差异,从而使用最小的代价将原始DOM按照新的状态、属性进行更新。其目的是找出两棵树的差异,原生方式直接比较复杂度…...
24/12/1 算法笔记<强化学习> 创建Maze交互
我们今天制作一个栅格的游戏。 我们直接上代码教学。 1.载入库和查找相应的函数版本 import numpy as np import time import sysif sys.version_info.major 2:import Tkinter as tk else:import tkinter as tk 2.设置长宽和单元格大小 UNIT 40 MAZE_H 4 MAZE_W 4 3.初始…...
Linux驱动开发(10):I2C子系统–mpu6050驱动实验
本章我们以板载MPU6050为例讲解i2c驱动程序的编写,本章主要分为五部分内容。 第一部分,i2c基本知识,回忆i2c物理总线和基本通信协议。 第二部分,linux下的i2c驱动框架。 第三部分,i2c总线驱动代码拆解。 第四部分&a…...
《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》
《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》 一、引言二、K-5S 电化学传感器模组概述(一)产品简介(二)产品特点(三)产品适用场景 三、电化学传感器原理及优点(一…...
如何将多个JS文件打包成一个JS文件?
文章目录 前言SDK 打包安装 webpack创建 webpack.config.js编译命令行遇到的坑点前言 上一篇已经记录了如何开发一个小游戏聚合SDK,既然是SDK,最终都是给外部人员使用的。调研了一下市面上的前端SDK,最终都是编译成一个 js 文件。我猜理由大概是 js 文件之间的调用都是需要…...
100个python经典面试题详解(新版)
应老粉要求,每晚加餐一个最新面试题 包括Python面试中常见的问题,涵盖列表、元组、字符串插值、比较操作符、装饰器、类与对象、函数调用方式、数据结构操作、序列化、数据处理函数等多个方面。 旨在帮助数据科学家和软件工程师准备面试或提升Python技能。 7、Python面试题…...
C#初阶概念理解
梳理了一些本人在学习C#时的一些生疏点,同时也加深自己的印象。 堆&栈 堆用来存储程序运行时产生的变量,当程序结束时释放; 栈用来存储程序运行时,调用方法产生的临时变量,方法运行完成后就会释放…...
node.js基础学习-url模块-url地址处理(二)
前言 前面我们创建了一个HTTP服务器,如果只是简单的http://localhost:3000/about这种链接我们是可以处理的,但是实际运用中一般链接都会带参数,这样的话如果我们只是简单的判断链接来分配数据,就会报404找不到链接。为了解决这个问…...
算法与数据结构(1)
一:数据结构概论 数据结构分为初阶数据结构(主要由C语言实现)和高阶数据结构(由C实现) 初阶数据结构当中,我们会学到顺序表、链表、栈和队列、二叉树、常见排序算法等内容。 高阶数据结构当中࿰…...
FTP介绍与配置
前言: FTP是用来传送文件的协议。使用FTP实现远程文件传输的同时,还可以保证数据传输的可靠性和高效性。 介绍 FTP的应用 在企业网络中部署一台FTP服务器,将网络设备配置为FTP客户端,则可以使用FTP来备份或更新VRP文件和配置文件…...
SQL面试题——抖音SQL面试题 最近一笔有效订单
最近一笔有效订单 题目背景如下,现有订单表order,包含订单ID,订单时间,下单用户,当前订单是否有效 +---------+----------------------+----------+-----------+ | ord_id | ord_time | user_id | is_valid | +---------+----------------------+--------…...
【线程】Java多线程代码案例(1)
【线程】Java多线程代码案例(1) 一、“单例模式” 的实现1.1“饿汉模式”1.2 “懒汉模式”1.3 线程安全问题 二、“阻塞队列”的实现2.1阻塞队列2.2生产者消费者模型2.3 阻塞队列的实现2.4 再谈生产者消费者模型 一、“单例模式” 的实现 “单例模式”即…...
go使用mysql实现增删改查操作
1、安装MySQL驱动 go get -u github.com/go-sql-driver/mysql2、go连接MySQL import ("database/sql""log"_ "github.com/go-sql-driver/mysql" // 导入 mysql 驱动 )type Users struct {ID intName stringEmail string }var db *sql.DBfu…...
【Rust】unsafe rust入门
这篇文章简单介绍下unsafe rust的几个要点 1. 解引用裸指针 裸指针其实就是C或者说C的指针,与C的指针不同的是,Rust的裸指针还是要分为可变和不可变,*const T 和 *mut T: 基于引用创建裸指针 let mut num 5;let r1 &num …...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
