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

Posted CoderZ1010

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity RectTransform Scale Handler - 如何在Runtime运行时拖动缩放窗口尺寸相关的知识,希望对你有一定的参考价值。

文章目录


简介

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

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

变量说明

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

因此,在鼠标按下时,需要根据锚点的位置,反方向设置窗口的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);

Unity——RectTransform详解

参考技术A unity中的ui元素是有严格的父子关系的,子物体的位置是根据父物体的变化而变化的,而子物体和父物体联系的桥梁就是Anchor。在recttransform面板中可以调整锚点的值

想要清晰的理解Recttransform的各个属性,个人认为首先需要建立的第一个概念就是 绝对布局 以及 相对布局 这两个概念。

所谓的绝对布局,就是出现 锚点 的情况,此时的recttransform面板中的属性变成了

所谓相对布局,就是出现锚框的情况。在这种情况下UI元素的四个角,距离四个对应的锚点的距离是不变的,在这种情况下RectTransform的属性又变为了 Left , Top , Right , Bottom , PosZ ,其中的 PosZ 表征的是该元素到父物体在Z轴上的偏移,利用这个值可以调整UI元素的显示顺序,不过我用的不多,这里不作太多讨论。剩下的四个值应该很好理解了,就是UI元素的每一条边距离父物体的每一条边的距离。
在示意图的情况下,我设定了红色图片(子物体)距离灰色图片(父物体)的每一条边的距离都是200个单位。

Pivot中心点,就是该UI元素旋转缩放的中心点,左下角为(0,0)右上角为(1,1)

首先说说OffsetMax,其实OffsetMin也是同理。接下来会主要解释两个问题

以前对这个属性是真的一脸懵逼,网上很多教程说这个值可以设置UI元素的大小,但是真的有时候好用,有时候有不好用,真的一头雾水,官方文档说的也是很笼统,但是现在搞清楚了其中的联系以后,就觉得清晰了不少了。

所以就会出现有时候sizeDelta得到的是UI元素的大小,有时候又不是的情况,下面就复现一下这两种情况

那么我们在锚框的情况下要怎么样才能获得元素的大小呢?这个时候就可以用到rect属性了。

rect中的属性,不与UI元素所在的位置有关,只和其自身属性相关,所以其中的 rect.width 和 rect.height 属性就可以让我们在任何情况下取得元素的大小,而 rect.x 和 rect.y 如图所示,表示的是以Pivot为原点,UI元素左下角的坐标,可以看到图中Pivot是在UI元素的正中间,所以左下角的坐标就刚好是 (-100,-100)

但是有一个问题,rect属性是一个只读的属性,如果我们想要设置UI元素的大小的话,这好像又不适用了,所以RectTransform还提供了几个非常有用的方法。

通过直接设置anchoredPosition的值可以改变UI元素的位置,但也是要分 锚点 和 锚框 的情况

这个方法无论在 绝对布局 还是 相对布局 的情况下,都可以通过直接设置rect中的 width 和 height 值来改变UI元素的大小。

这个方法就比较冷门了可能,不过还是挺强大的。调用这个方法,可以根据父物体的Edge(某一边)去布局。其中第一个参数就是用于确定基准的边,第二个参数是UI元素的该边界与父物体该边界的距离,第三个元素是设定选定轴上UI元素的大小,可能说起来有点复杂,但是我上两张图相信各位就可以秒懂了。

首先以右边界为基准

然后以下边界为基准

在使用这个方法的时候要注意锚点也会改变,改变的规则为

使用这个方法,可以取得UI元素四个角的世界坐标,具体使用方法,先建立一个长度为4的vector3数组,然后传进这个方法,调用一次后,数组被赋值,里面的四个元素分别是UI的 左下角 , 左上角 , 右上角 , 右下角 。

以上是关于Unity RectTransform Scale Handler - 如何在Runtime运行时拖动缩放窗口尺寸的主要内容,如果未能解决你的问题,请参考以下文章

关于Unity中RectTransform和Transform

unity的ugui-9.搭建一个简单的背包

Unity——RectTransform详解

Unity UGUI 修改好了RectTransform的值但是重启工程后RectTransform会自己变化,如何解决呢

Unity UI的transform,recttransform,position的相互转换

Unity中实现限制RectTransform下某个属性不可被修改