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 …...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
