Unity RectTransform Scale Handler - 如何在Runtime运行时拖动缩放窗口尺寸

news2025/1/18 20:08:33

文章目录

  • 简介
    • 变量说明
  • 实现
    • 光标移入移出
    • 鼠标拖动距离
    • Anchor 锚点
    • 目标尺寸
    • 扩展方向


简介

本文介绍如何在Runtime运行时拖动缩放UI窗口的尺寸,如图所示,在示例窗口的左上、上方、右上、左方、右方、左下、下方、右下,分别放置了一个拖动柄,按下进行拖动时,将改变窗口的尺寸:

示例效果

该工具源码已上传SKFramework框架Package Manager中:

SKFramework PackageManager

变量说明

RectTransform Scale Handler

  • Target:目标,即拖动该拖动柄时要改变尺寸的RectTransform;
  • Min Size Limit:最小尺寸限制值;
  • Max Size Limit:最大尺寸限制值;
  • Handler Anchor:拖动柄的锚点位置:
    • UpperLeft:左上;
    • UpperCenter:上方;
    • UpperRight:右上;
    • MiddleLeft:左方;
    • MiddleRight:右方;
    • LowerLeft:左下;
    • LowerCenter:下方;
    • LowerRight:右下;
  • Expand Mode:扩展模式,包含两种类型:
    • Whole:拖动该拖动柄时,窗口会整个(居中)进行扩展缩放;
    • Along Anchor:拖动该拖动柄时,窗口会沿拖动柄锚点方向进行扩展缩放。

Expand Mode:Whole

Expand Mode:Along Anchor

实现

光标移入移出

只有在光标进入拖动柄时,按住鼠标左键,窗口才根据拖动改变尺寸。继承IPointerEnterHandlerIPointerExitHandler接口,实现OnPointerEnterOnPointerEnter方法:

//光标是否进入
private bool isMouseEntered;

//光标移入
public void OnPointerEnter(PointerEventData eventData)
{
    isMouseEntered = true;
}
//光标移除
public void OnPointerExit(PointerEventData eventData)
{
    isMouseEntered = false;
}

鼠标拖动距离

光标进入并按下鼠标左键时,标识正在拖动,并缓存按下时鼠标的坐标位置,在拖动过程中,通过当前的鼠标坐标位置减去按下时缓存的鼠标坐标位置,就可以得到拖动产生的偏差,即距离:

//是否正在拖动
private bool isDragging;
//缓存鼠标坐标
private Vector3 cacheMousePosition;
//拖动的偏差
private Vector3 dragDelta;

private void Update()
{
    //光标已进入
    if (isMouseEntered)
    {
        //鼠标左键按下
        if (Input.GetMouseButtonDown(0))
        {
            //正在拖动标识置为true
            isDragging = true;
            //缓存鼠标位置
            cacheMousePosition = Input.mousePosition;
        }
    }
    //正在拖动
    if (isDragging)
    {
        //拖动的偏差
        dragDelta = Input.mousePosition - cacheMousePosition;
        //TODO:
    }
    //鼠标左键抬起
    if (Input.GetMouseButtonUp(0))
    {
        //恢复正在拖动标识
        isDragging = false;
        //重置拖动偏差
        dragDelta = Vector3.zero;
    }
}

Anchor 锚点

初始化时根据Handler Anchor来设置拖动柄的RectTransform组件的Anchor MinAnchor Max,并将其Anchor Position锚点坐标归零,使其定位到指定的位置:

private void Start()
{
    switch (handlerAnchor)
    {
        case Anchor.UpperLeft: rt.anchorMin = rt.anchorMax = new Vector2(0f, 1f); break;
        case Anchor.UpperCenter: rt.anchorMin = rt.anchorMax = new Vector2(0.5f, 1f); break;
        case Anchor.UpperRight: rt.anchorMin = rt.anchorMax = new Vector2(1f, 1f); break;
        case Anchor.MiddleLeft: rt.anchorMin = rt.anchorMax = new Vector2(0f, 0.5f); break;
        case Anchor.MiddleRight: rt.anchorMin = rt.anchorMax = new Vector2(1f, 0.5f); break;
        case Anchor.LowerLeft: rt.anchorMin = rt.anchorMax = new Vector2(0f, 0f); break;
        case Anchor.LowerCenter: rt.anchorMin = rt.anchorMax = new Vector2(0.5f, 0f); break;
        case Anchor.LowerRight: rt.anchorMin = rt.anchorMax = new Vector2(1f, 0f); break;
    }
    rt.anchoredPosition = Vector2.zero;
}

目标尺寸

如何计算目标尺寸?需要在鼠标按下时,缓存窗口的尺寸,在拖动过程中,根据按下时缓存的窗口尺寸和拖动产生的偏差来计算目标尺寸。另外,拖动导致窗口尺寸变化的影响元素还包括:

  • Handler Anchor,当其类型为UpperCenter和LowerCenter时,拖动只改变窗口的高度;当其类型为MiddleLeft和MiddleRight时,拖动只改变窗口的宽度;为其他类型时,同时改变窗口宽和高,当然方向不同决定缓存窗口尺寸是加上还是减去拖动产生的偏差;
  • Expand Mode,当扩展类型为Whole,即窗口整体进行缩放时,缩放的增量是拖动产生偏差的2倍,即系数为2;
  • Min、Max Size Limit:在最终赋值之前会通过最大最小限制值进行钳制:
//正在拖动
if (isDragging)
{
    //拖动的偏差
    dragDelta = Input.mousePosition - cacheMousePosition;
    //水平方向数学符号
    float horizontalSign = rt.anchorMin.x == 0f ? -1f : rt.anchorMin.x == .5f ? 0f : 1f;
    //垂直方向数学符号
    float verticalSign = rt.anchorMin.y == 0f ? -1f : rt.anchorMin.y == .5f ? 0f : 1f;
    //系数 扩展模式为整个时系数=2 沿锚点扩展时系数=1
    float factor = expandMode == ExpandMode.AlongAnchor ? 1f : 2f;
    //目标宽高
    float width = cacheTargetSize.x + dragDelta.x * horizontalSign * factor;
    float height = cacheTargetSize.y + dragDelta.y * verticalSign * factor;
    //最大最小值限制
    width = Mathf.Clamp(width, minSizeLimit.x, maxSizeLimit.x);
    height = Mathf.Clamp(height, minSizeLimit.y, maxSizeLimit.y);
    //设置宽高
    target.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
    target.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
}

扩展方向

当扩展模式为Along Anchor时,窗口沿锚点方向进行缩放,例如锚点为右下时,窗口轴心点为左上,窗口会往右下方向进行缩放,如图所示:

Expand Along Anchor Direction

因此,在鼠标按下时,需要根据锚点的位置,反方向设置窗口的Pivot轴心点,在此需要注意一个问题,因为锚点坐标是不变的,因此更改轴心点会导致窗口的位置发生变化,因此在设置轴心点之前先进行缓存,然后在设置之后,根据轴心点的偏差修正窗口的位置:

//光标已进入
if (isMouseEntered)
{
    //鼠标左键按下
    if (Input.GetMouseButtonDown(0))
    {
        //正在拖动标识置为true
        isDragging = true;
        //缓存鼠标位置
        cacheMousePosition = Input.mousePosition;
        //缓存目标的大小
        cacheTargetSize.x = target.rect.width;
        cacheTargetSize.y = target.rect.height;
        //缓存轴心点
        cachePivot = target.pivot;
        //根据扩展模式修改目标的锚点
        if (expandMode == ExpandMode.Whole)
            target.pivot = Vector2.one * .5f;
        else
        {
            //根据处理柄锚点位置反方位设置目标的锚点
            switch (handlerAnchor)
            {
                case Anchor.UpperLeft: target.pivot = new Vector2(1f, 0f); break;
                case Anchor.UpperCenter: target.pivot = new Vector2(0.5f, 0f); break;
                case Anchor.UpperRight: target.pivot = new Vector2(0f, 0f); break;
                case Anchor.MiddleLeft: target.pivot = new Vector2(1f, 0.5f); break;
                case Anchor.MiddleRight: target.pivot = new Vector2(0f, 0.5f); break;
                case Anchor.LowerLeft: target.pivot = new Vector2(1f, 1f); break;
                case Anchor.LowerCenter: target.pivot = new Vector2(0.5f, 1f); break;
                case Anchor.LowerRight: target.pivot = new Vector2(0f, 1f); break;
            }
        }
        //获取轴心点的偏差 
        Vector2 pivotDelta = target.pivot - cachePivot;
        //由于锚点坐标不变 更改轴心点会导致位置变更 通过偏差来修正位置
        target.anchoredPosition += new Vector2(pivotDelta.x * target.rect.width, pivotDelta.y * target.rect.height);
    }
}

鼠标抬起结束拖动时,根据缓存恢复窗口的轴心点:

//鼠标左键抬起
if (Input.GetMouseButtonUp(0))
{
    //恢复正在拖动标识
    isDragging = false;
    //重置拖动偏差
    dragDelta = Vector3.zero;
    //鼠标抬起后 根据缓存恢复目标的轴心点
    Vector2 pivot = target.pivot;
    target.pivot = cachePivot;
    Vector2 pivotDelta = target.pivot - pivot;
    target.anchoredPosition += new Vector2(pivotDelta.x * target.rect.width, pivotDelta.y * target.rect.height);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/386993.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring之基于注解方式实例化BeanDefinition(1)

最近开始读Spring源码,读着读着发现里面还是有很多很好玩的东西在里面的,里面涉及到了大量的设计模式以及各种PostProcessor注入的过程,很好玩,也很复杂,本文就是记录一下我学习过程中的主干流程。 在开始我们源码解读…

2023年湖北武汉中级工程师怎么申请?申报渠道有哪些?启程别

2023年湖北武汉中级工程师怎么申请?申报渠道有哪些?启程别 武汉市中级工程师怎么报名?很多人不知道中级职称怎么申请,在哪里申请,那么启程别来告诉大家,启程别是谁,进入百度搜索启程别就知道啦 武汉中级工…

【学习Docker(七)】详细讲解Jenkins部署SpringCloud微服务项目,Docker-compose启动

Jenkins部署SpringCloud微服务项目,Docker-compose启动 座右铭:《坚持有效输出,创造价值无限》 本文介绍使用Jenkins部署SpringCloud微服务项目,Docker-compose启动。 之前写过安装Jenkins的过程,这里就不写安装细节了…

[oeasy]python0099_雅达利大崩溃_IBM的开放架构_兼容机_oem

雅达利大崩溃 回忆上次内容 个人计算机浪潮已经来临 苹果公司迅速发展微软公司脱离mits准备做纯软件公司IBM用大型机思路制作的5100惨败 Commodore 64 既做计算机又做游戏机 计算机行业和游戏行业 跟随着底层技术不断迭代已经进入了战乱纷纷的年代最终又会如何呢&#xff1f…

31 openEuler使用LVM管理硬盘-管理物理卷

文章目录31 openEuler使用LVM管理硬盘-管理物理卷31.1 创建物理卷31.2 查看物理卷31.3 修改物理卷属性31.4 删除物理卷31 openEuler使用LVM管理硬盘-管理物理卷 31.1 创建物理卷 可在root权限下通过pvcreate命令创建物理卷。 # pvcreate [option] devname ...其中&#xff1…

【linux工具】Tmux简明教程

A Quick and Easy Guide to tmux (hamvocke.com) 一、说明 我们常用ubuntu,用altctlT实现终端窗口弹出。然而当需要多个终端一起工作,切换的效率就成了问题。 tmux是ubuntu下,终端窗口工具。该工具能实现多窗口分屏显示、多个会话在一个终端…

【13】linux命令每日分享——groupadd建立组

大家好,这里是sdust-vrlab,Linux是一种免费使用和自由传播的类UNIX操作系统,Linux的基本思想有两点:一切都是文件;每个文件都有确定的用途;linux涉及到IT行业的方方面面,在我们日常的学习中&…

时间复杂度的计算

个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【数据结构初阶(C实现)】 文章目录123456789时间复杂度(就是一个函数)的计算,…

Flutter开发圆形计时进度条RingProgressBar

演示 先看效果图: 由于无法截取动态图,我就截过程中的两张图片表达了,我想应该能看得懂。 功能1.设置进度条半径 2.设置进度条宽度 3.设置进度条最大值 4.设置进度条背景色以及前景色 5.是否显示进度条文字 6.文字样式设置 7.点击进度条和进…

机器学习100天(三十七):037 朴素贝叶斯-挑个好西瓜!

《机器学习100天》完整目录:目录 机器学习100天,今天讲的是:朴素贝叶斯-挑个好西瓜! 红色石头已经了解了贝叶斯定理和朴素贝叶斯法,接下来已经可以很自信地去买瓜了。买瓜之前,还有一件事情要做,就是搜集样本数据。红色石头通过网上资料和查阅,获得了一组包含 10 组样…

打印名片-课后程序(Python程序开发案例教程-黑马程序员编著-第二章-课后作业)

实例3:文本进度条 进度条以动态方式实时显示计算机处理任务时的进度,它一般由已完成任务量与剩余未完成任务量的大小组成。本实例要求编写程序,实现图1所示的进度条动态显示的效果。 下载中下载完成图1文本进度条 实例分析 在本实例中可以将…

【java】alibaba Fastjson --全解史上最快的JSON解析库

文章目录前序Fastjson 简介Fastjson 的优点速度快使用广泛测试完备使用简单功能完备下载和使用将 Java 对象转换为 JSON 格式JSONField创建 JSON 对象JSON 字符串转换为 Java 对象使用 ContextValueFilter 配置 JSON 转换使用 NameFilter 和 SerializeConfigFastjson 处理日期F…

如何使用SaleSmartly进行Facebook Messenger 营销、销售和支持

如何使用SaleSmartly(ss客服)进行Facebook Messenger 营销、销售和支持上篇文章我们讲了什么是Facebook Messenger CRM以及获得Facebook Messenger CRM的注意事项,现在你有更多时间与客户聊天,让我们看看你如何使用SaleSmartly&am…

缓存穿透和缓存击穿、缓存雪崩

一、Redis作为一个缓存中间件是如何工作的?架构图如下过程如下 客户端发起一个查询请求的时候,首先去缓存中查询,如果数据在缓存中存在,则直接将缓存中数据返回给客户端;如果数据在缓存中不存在,则继续查询…

嵌入式知识点-SPI通讯

该文原自 : 正点原子 01 SPI概述 SPI (Serial Peripheralinterface),顾名思义就是串行外围设备接口。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同…

网络营销培训完能达到什么水平?学完能创业吗?

网络营销本身就是一门创业的技术,很多人学习网络营销,往往担心学完以后技术达不到,再工作几年才可以创业,实际这是错误的理解,那么,网络营销培训完能达到什么水平?新手学员参加网络营销培训&…

MAX 10 10M50 FPGA(10M50DDF256I7G)10M50DDF484C8G/10M50DDF484I7G

MAX 10器件是单芯片、非易失性低成本可编程逻辑器件(pld),用于集成最优的系统组件集。MAX 10设备的亮点包括:内部存储双配置闪存用户闪存即时支持集成模数转换器(adc)支持Nios II单芯片软核处理器该器件设备是系统管理、I/O扩展、通信控制平面、工业、汽…

Spark BlockManager数据存储与管理机制

BlockManager是整个Spark底层负责数据存储与管理的一个组件,Driver和Executor的所有数据都由对应的BlockManager进行管理。 Driver上有BlockManagerMaster,负责对各个节点上的BlockManager内部管理的数据的元数据进行维护,比如block的增删改等…

SpringBoot如何自定义一个starter

SpringBoot starter,大家应该在平常写项目中应该非常熟悉,很多依赖都会提供集成SpringBoot的依赖,这样我们用起来就非常顺手,开箱就能用,那如何自定义一个starter呢? SpringBoot starter SpringBoot中的一…

【C++初阶】2. 类和对象_1

1. 面向过程和面向对象的初步认识 2. 类的引入 C语言结构体中只能定义变量,在C中,结构体内不仅可以定义变量,也可以定义函数。比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量&#…