QML实现不同父层级元素与checker的相交碰撞检测
实现方案
核心逻辑分三步:递归找到所有嵌套层级下的Point实例、把所有待检测元素的坐标转到同一参考系、做矩形相交判断。
第一步:给Point组件加识别标记
因为Point本身继承自Rectangle,和界面里其他矩形元素是同个基类,先给Point.qml加个专属只读属性,避免遍历的时候把其他矩形误判成Point:
// Point.qml import QtQuick 2.0 Rectangle { width: 10 height: 10 color: "black" // 专属标记,用于识别Point实例 readonly property bool isPoint: true }
第二步:补全检测逻辑
直接替换MouseArea里的对应代码即可,递归遍历会自动扫完area下所有嵌套层级的子元素,不管Point藏在哪一层都能找到:
MouseArea { anchors.fill: parent drag.target: parent onReleased: { var point = getCoveredPoint() if (point) { console.log("Point ", point, " is covered") } } // 递归遍历父元素下所有子节点,收集全部Point实例 function findAllPoints(parentItem) { let pointList = [] for (let i = 0; i < parentItem.children.length; i++) { const child = parentItem.children[i] if (child.isPoint === true) { pointList.push(child) } // 有子节点就继续往下找 if (child.children.length) { pointList = pointList.concat(findAllPoints(child)) } } return pointList } function getCoveredPoint() { // 拿到所有Point实例 const allPoints = findAllPoints(area) // 把checker的坐标转到area的坐标系下,统一参考系 const checkerPos = checker.mapToItem(area, 0, 0) const checkerBounds = { left: checkerPos.x, top: checkerPos.y, right: checkerPos.x + checker.width, bottom: checkerPos.y + checker.height } // 逐个判断相交 for (let p of allPoints) { const pPos = p.mapToItem(area, 0, 0) const pBounds = { left: pPos.x, top: pPos.y, right: pPos.x + p.width, bottom: pPos.y + p.height } // 轴对齐矩形相交判断:不满足「完全在上下左右任意一侧」就是相交 const intersect = !( checkerBounds.right < pBounds.left || checkerBounds.left > pBounds.right || checkerBounds.bottom < pBounds.top || checkerBounds.top > pBounds.bottom ) if (intersect) return p } return null } }
关键说明
- 绝对不能直接拿元素的
x、y属性直接比较:这两个值是元素相对于自己直接父元素的局部坐标,嵌套层级不同时参考系完全不一样,算出来的结果是错的,必须用mapToItem()转到同一坐标系下再计算。 - 如果界面上元素很多,每次拖拽结束都递归遍历全量子元素会有性能损耗,可以在Point组件初始化的时候把自己注册到一个顶层的数组里,销毁的时候移除,检测的时候直接遍历这个数组就行,不用每次递归扫整棵元素树。
- 上面的逻辑是找到第一个相交的Point就返回,如果需要拿到所有被覆盖的Point,只要把相交的实例存到数组里,遍历完所有Point之后返回数组即可。
内容的提问来源于stack exchange,提问作者serheo
相关产品推荐
相关产品推荐

