矩形区域怎么计算和判断?RectUtils 帮你搞定各种矩形操作

在做图形开发的时候,你经常会遇到需要处理矩形区域的情况——判断两个矩形有没有重叠、计算它们的交集或并集、检测一个点是不是在矩形里面、把矩形平移到某个位置……这些操作看起来简单,但自己写起来还是挺麻烦的。

HarmonyOS 的 RectUtils 就是一个专门处理这些矩形操作的工具类。它提供了一整套静态方法,让你可以方便地创建、拷贝、计算、判断各种矩形。

创建矩形

RectUtils 提供了完整的矩形操作能力,下面是常用操作的分类:

RectUtils 矩形工具

创建矩形

获取信息

判断关系

几何运算

调整变换

makeEmpty 空矩形

makeLtrb 坐标创建

makeCopy 拷贝创建

getWidth / getHeight

centerX / centerY

contains 包含判断

isIntersect 相交判断

isEqual 相等判断

intersect 计算交集

union 计算并集

offset 相对平移

offsetTo 绝对平移

inset 调整边界

RectUtils 提供了好几种创建矩形的方式:

创建空矩形

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeEmpty();

makeEmpty() 创建一个上下左右都是 0 的"空矩形"。你可以把它当作一个占位符,后面再用其他方法来设置它的值。

用坐标创建矩形

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 10, 20, 20);

makeLtrb 的四个参数分别是 left(左)、top(上)、right(右)、bottom(下),也就是矩形左上角和右下角的坐标。这是最常用的创建方式。

拷贝矩形

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 10, 20, 20);
let rect2 = drawing.RectUtils.makeCopy(rect);
console.info('rect2.left:', rect2.left);
console.info('rect2.top: ', rect2.top);
console.info('rect2.right: ', rect2.right);
console.info('rect2.bottom: ', rect2.bottom);

makeCopy 创建一个新的矩形,是原矩形的副本。修改其中一个不会影响另一个。

获取矩形的基本信息

创建好矩形之后,你可以获取它的各种属性:

获取宽度和高度

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 10, 20, 20);
let width = drawing.RectUtils.getWidth(rect);
console.info('width:', width);
import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 10, 20, 20);
let height = drawing.RectUtils.getHeight(rect);

有个细节:如果矩形的左边界大于右边界(或者上边界大于下边界),返回的宽度/高度会是负值。这种情况通常意味着矩形是"反转"的。

获取中心点坐标

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(20, 30, 30, 40);
let x = drawing.RectUtils.centerX(rect);
import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(20, 30, 30, 40);
let x = drawing.RectUtils.centerY(rect);

centerX 返回中心点的横坐标,centerY 返回纵坐标。在需要把某个元素居中放置时,这两个方法非常好用。

判断包含关系

这是最常用的功能之一——判断一个矩形是否包含另一个矩形或点:

判断矩形是否包含另一个矩形

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 10, 20, 20);
let rect2 = drawing.RectUtils.makeLtrb(0, 0, 40, 40);
let isContains = drawing.RectUtils.contains(rect2, rect);
console.info('isContains: ', isContains);

这里 contains(rect2, rect) 判断的是 rect2 是否完全包含 rect。如果 rect 完全在 rect2 内部(或者两者相等),返回 true。注意:空矩形不包含任何矩形。

用坐标判断包含

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(0, 0, 100, 100);
let isContains = drawing.RectUtils.contains(rect, 10, 20, 30, 40);
console.info('isContains :', isContains);

这个重载版本直接用 left/top/right/bottom 坐标来表示要判断的矩形,不用先创建 Rect 对象。

判断矩形是否包含一个点

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(0, 0, 100, 100);
let isContains = drawing.RectUtils.contains(rect, 10, 20);
console.info('isContains: ', isContains);

contains(rect, x, y) 判断点 (x, y) 是否在矩形内部。这个在做点击检测时特别有用——用户点击了屏幕某个位置,你想知道他是不是点在了某个按钮上。

注意一个细节:左边界和上边界属于矩形内部,但右边界和下边界不属于。也就是说,对于一个 {left: 0, top: 0, right: 100, bottom: 100} 的矩形,点 (0, 0) 在内部,但点 (100, 100) 不在内部。

判断和计算交集

判断两个矩形是否相交

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(0, 0, 20, 20);
let rect2 = drawing.RectUtils.makeLtrb(10, 10, 40, 40);
let isIntersect = drawing.RectUtils.isIntersect(rect, rect2);
console.info('isIntersect :', isIntersect);

isIntersect 只判断两个矩形有没有重叠部分,返回 truefalse。注意:如果两个矩形只是边重叠或者只有角相交,返回的是 false,必须有面积上的重叠才返回 true

计算交集并更新矩形

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(0, 0, 20, 20);
let rect2 = drawing.RectUtils.makeLtrb(10, 10, 40, 40);
let isIntersect = drawing.RectUtils.intersect(rect, rect2);
console.info('isIntersect :', isIntersect);
console.info('rect.left:', rect.left);
console.info('rect.top: ', rect.top);
console.info('rect.right: ', rect.right);
console.info('rect.bottom: ', rect.bottom);

intersectisIntersect 的区别是:intersect 不仅告诉你有没有交集,还会把交集区域的结果更新到第一个参数 rect 里。也就是说,调用之后 rect 变成了两个矩形的重叠部分。

计算并集

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(0, 0, 20, 20);
let rect2 = drawing.RectUtils.makeLtrb(10, 10, 40, 40);
drawing.RectUtils.union(rect, rect2);
console.info('rect.left:', rect.left);
console.info('rect.top: ', rect.top);
console.info('rect.right: ', rect.right);
console.info('rect.bottom: ', rect.bottom);

union 计算两个矩形的"最小包围矩形"——就是刚好能包含两个矩形的最小矩形。结果会更新到第一个参数 rect 里。

如果第一个矩形是空的,结果会直接用第二个矩形。如果第二个矩形是空的,不做任何操作。

调整矩形边界

inset:向内/向外调整边界

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 10, 20, 20);
drawing.RectUtils.inset(rect, 10, -20, 30, 60);
console.info('rect.left:', rect.left);
console.info('rect.top: ', rect.top);
console.info('rect.right: ', rect.right);
console.info('rect.bottom: ', rect.bottom);

inset 把传入的值分别加到矩形的左、上、右、下边界上。传正数就是边界往外扩(矩形变大),传负数就是边界往里缩(矩形变小)。

这在做"内边距"或"外边距"计算时很有用。比如你想让一个矩形的四周都留出 10px 的间距:

drawing.RectUtils.inset(rect, -10, -10, 10, 10);

平移矩形

offset:相对平移

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(0, 0, 20, 20);
drawing.RectUtils.offset(rect, 10, 20);
console.info('rect.left:', rect.left);
console.info('rect.top: ', rect.top);
console.info('rect.right: ', rect.right);
console.info('rect.bottom: ', rect.bottom);

offset(rect, dx, dy) 让矩形向右移动 dx,向下移动 dy。传负数就是往反方向移动。

offsetTo:绝对平移

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(20, 20, 40, 40);
drawing.RectUtils.offsetTo(rect, 10, 20);
console.info('rect.left:', rect.left);
console.info('rect.top: ', rect.top);
console.info('rect.right: ', rect.right);
console.info('rect.bottom: ', rect.bottom);

offsetTooffset 的区别是:offset 是相对移动(移动多少),offsetTo 是绝对移动(移动到哪)。offsetTo(rect, 10, 20) 会把矩形的左上角移到 (10, 20) 的位置,矩形的大小不变。

其他实用方法

判断矩形是否为空

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeEmpty();
let isEmpty = drawing.RectUtils.isEmpty(rect);
console.info('isEmpty :', isEmpty);
let rect2 = drawing.RectUtils.makeLtrb(0, 0, 20, 20);
isEmpty = drawing.RectUtils.isEmpty(rect2);
console.info('isEmpty :', isEmpty);

当左边界大于等于右边界,或者上边界大于等于下边界时,矩形就被认为是"空"的。

判断两个矩形是否相等

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 20, 20, 30);
let rect2 = drawing.RectUtils.makeEmpty();
let isEqual = drawing.RectUtils.isEqual(rect, rect2);
console.info('isEqual :', isEqual);

isEqual 逐个比较两个矩形的 left、top、right、bottom,全部相等才返回 true

修正反转的矩形

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(20, 40, 30, 30);
drawing.RectUtils.sort(rect);
console.info('rect.left:', rect.left);
console.info('rect.top: ', rect.top);
console.info('rect.right: ', rect.right);
console.info('rect.bottom: ', rect.bottom);

如果矩形的边界是"反转"的(比如左 > 右,或者上 > 下),sort 会自动交换它们,让矩形变得"正常"。

重新设置矩形的值

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeEmpty();
drawing.RectUtils.setLtrb(rect, 10, 20, 30, 60);
console.info('rect.left:', rect.left);
console.info('rect.top: ', rect.top);
console.info('rect.right: ', rect.right);
console.info('rect.bottom: ', rect.bottom);
import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 20, 30, 40);
let rect2 = drawing.RectUtils.makeEmpty();
drawing.RectUtils.setRect(rect2, rect);
console.info('rect2.left:', rect2.left);
console.info('rect2.top: ', rect2.top);
console.info('rect2.right: ', rect2.right);
console.info('rect2.bottom: ', rect2.bottom);

setLtrb 用坐标值来更新矩形,setRect 用另一个矩形来更新。还有 setEmpty 可以把矩形清空:

import { drawing } from '@kit.ArkGraphics2D';

let rect = drawing.RectUtils.makeLtrb(10, 20, 20, 30);
drawing.RectUtils.setEmpty(rect)
console.info('rect.left:', rect.left);
console.info('rect.top: ', rect.top);
console.info('rect.right: ', rect.right);
console.info('rect.bottom: ', rect.bottom);

实际应用场景

下面是碰撞检测的典型流程:

获取用户触摸坐标 touchX touchY

构建目标矩形区域

调用 contains 判断点是否在矩形内

点在矩形内?

触发点击响应

忽略本次触摸

构建玩家矩形

构建敌人矩形

调用 isIntersect 判断相交

两矩形相交?

发生碰撞 处理碰撞逻辑

未碰撞 继续检测

场景一:点击检测

用户点击了屏幕,你想知道他是不是点在了某个按钮上:

let buttonRect = drawing.RectUtils.makeLtrb(100, 200, 300, 250);
let isHit = drawing.RectUtils.contains(buttonRect, touchX, touchY);
if (isHit) {
  // 用户点到了按钮
}

场景二:碰撞检测

在游戏开发中,检测两个物体有没有碰到:

let playerRect = drawing.RectUtils.makeLtrb(playerX, playerY, playerX + 50, playerY + 50);
let enemyRect = drawing.RectUtils.makeLtrb(enemyX, enemyY, enemyX + 40, enemyY + 40);
let isColliding = drawing.RectUtils.isIntersect(playerRect, enemyRect);

场景三:计算可见区域

列表滚动时,计算哪些项在可视区域内:

let viewport = drawing.RectUtils.makeLtrb(0, scrollY, screenWidth, scrollY + screenHeight);
for (let item of items) {
  let itemRect = drawing.RectUtils.makeLtrb(item.x, item.y, item.x + item.width, item.y + item.height);
  if (drawing.RectUtils.isIntersect(viewport, itemRect)) {
    // 这个项在可视区域内,需要渲染
  }
}

场景四:布局计算

计算元素的包围盒:

let boundingBox = drawing.RectUtils.makeCopy(firstElement.rect);
for (let i = 1; i < elements.length; i++) {
  drawing.RectUtils.union(boundingBox, elements[i].rect);
}
// boundingBox 现在是所有元素的最小包围矩形

注意事项

  1. API 版本:RectUtils 从 API version 20 开始支持。

  2. 物理像素:所有坐标和尺寸都是物理像素 px。

  3. 线程安全:和其他 drawing 对象一样,是单线程模型。

  4. 空矩形的特殊性:空矩形不包含任何矩形或点,和任何矩形的交集也是空的。

  5. 边界包含规则:左边界和上边界属于矩形内部,右边界和下边界不属于。这和 Android 的 Rect 类似。

小结

RectUtils 是一个非常实用的矩形工具类,提供了创建、拷贝、计算宽高、判断包含关系、计算交集并集、平移、调整边界等一系列操作。

在实际开发中,矩形操作无处不在——点击检测、碰撞检测、布局计算、可见区域判断等等。有了 RectUtils,这些操作都变得非常简单。记住它是从 API version 20 开始支持的,确保你的项目版本符合要求。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐