文章目录
- 简介
- 变量说明
- 实现
- 光标移入移出
- 鼠标拖动距离
- Anchor 锚点
- 目标尺寸
- 扩展方向
简介
本文介绍如何在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
:拖动该拖动柄时,窗口会沿拖动柄锚点方向进行扩展缩放。
实现
光标移入移出
只有在光标进入拖动柄时,按住鼠标左键,窗口才根据拖动改变尺寸。继承IPointerEnterHandler
、IPointerExitHandler
接口,实现OnPointerEnter
、OnPointerEnter
方法:
//光标是否进入
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 Min
和Anchor 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);
}