疑问:多个mousearea或button重叠,结果响应异常
qml中,经常会遇到多个需要点击的组件重叠,点击重叠部分时,结果不符预期的问题。因为某个组件设置anchors.fill;parent,或者其他原因,不该触发的组件被触发,该触发的没反应。
作为qml的初学者,可能会很疑惑,调试半天,搞不明白哪里错了。也不知道在怎么设置,才能保证全部的需要点击的组件都可以正常运行。
我查了很多资料,只是谈mousearea用法,很少有人谈到这个问题。所以,我来简单解释一下,避免更多新人像我一样踩坑。
基本原理
首先,理解什么是渲染。
已知一段qml代码,如下:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("渲染一个带有颜色的矩形")
Rectangle{
id:rectangle1
color: "lightblue"
anchors.fill: parent
}
}
运行代码,看到一个蓝色的矩形铺满整个窗口。
然后,在此基础上再渲染一个矩形rectangle2,代码如下
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle{
id:rectangle1
color: "lightblue"
anchors.fill: parent
}
Rectangle{
id:rectangle2
color: "pink"
width: 100
height: 100
}
}
运行程序,查看效果。
原本全屏蓝色,现在多了一个粉色矩形,粉色矩形覆盖了一部分蓝色矩形,才显示成这样。
本来是两个矩形图像,现在被程序绘制到同一个显示区域,这个过程学名就叫渲染。
上图是粉色矩形覆盖蓝色矩形,有没有可能让蓝色矩形覆盖粉色矩形呢?
重点来了,仔细看
交换上面两个rectangle的代码顺序:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle{
id:rectangle2
color: "pink"
width: 100
height: 100
}
Rectangle{
id:rectangle1
color: "lightblue"
anchors.fill: parent
}
}
结果如图,先写rectangle2代码,后写rectangle1代码,与只有rectangle1的代码,渲染结果一致
只有蓝色矩形被显示,不代表程序运行的时候没有绘制粉色矩形。画过油画的都了解,后来的颜色会覆盖先前的颜色。道理一样的,这里粉色矩形被更大面积的蓝色矩形覆盖,导致看不到粉色矩形
结论:
qml组件定义顺序会影响到渲染顺序
渲染顺序影响点击时qml组件的响应顺序
这个结论适用于qml所有组件
补充知识点:qml组件层级及其渲染顺序
影响qml渲染顺序的,除了定义顺序以外,在可视组件里,还有z属性的值
qml的可视组件有xyz属性,用来确定渲染坐标,xy决定组件的在屏幕上的渲染位置
z轴垂直于屏幕,决定组件的渲染先后顺序,我称其为渲染层级
其值取值范围可以是正数负数,默认所有的组件z的值都是0,z值相同代表组件处同一个层级
z值更小者会处于更底层位置。
z值最大者会处于渲染结果的最表面,也就是用户看到的这一侧。
不同层级间,优先按照z值,顺序渲染
同一个层级,遵循默认规则,组件定义顺序为qml的渲染顺序。先定义的先被渲染,后被渲染的将处于窗口的表层
因此,手动设置z值,可以避免受代码定义顺序影响,调整组件在z轴上的位置。
形象理解,渲染就像向桌子上贴贴纸,后来者会盖住前者。把所有贴纸贴到桌子上后,就是最终的渲染结果。最后贴的贴纸会处于桌面的表面,
如果外层的贴纸大小大于内层贴纸,就会完全覆盖掉内层。就会导致看不到内层的贴纸图案。要想看到就得先撕下最外层,或者切换贴贴纸的顺序
mousearea和button,还有许多qml组件,遵循以上运行规则。
解决方法
因此,有多个重叠的可点击组件,想要正常被触发,就不能让大面积的组件完全叠在小面积组件上,否则点击小面积组件,响应的是大面积组件
所以解决方法很简单,就是调整渲染的先后顺序:
1.调整组件的定义顺序,先定义大面积组件
2.调整mousearea或button等多个组件的z值,让小面积组件裸露在重叠区域表层
两种方法根据情况和需求,选择使用即可
额外补充说明
这个方法,只能让点击位置最表面的那一个组件响应,内层不会响应
如果希望点击区域重叠的组件,被点击时都能响应。
就需要传递表层鼠标事件到内层,如果有多层重叠,需要一层一层传递鼠标事件才能保证全部响应
常用的设置方法我来简单介绍一下
把mousearea的属性propagateComposedEvents=true
并设置mouse.accepted=false
表示当前鼠标事件可以继续向内传递到下一层
示例代码如下:
Rectangle {
width: 100
height: 100
MouseArea {
anchors.fill: parent
onClicked: console.log("clicked yellow")
}
Rectangle {
color: "blue"
width: 50
height: 50
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
console.log("clicked blue");
mouse.accepted = false;
}
}
}
}
原来未设置时,blue在最顶层,所以点击重叠部分只能输出clicked blue,
在blue中添加设置后,点击重叠区域,log就能输出 clicked blue,clicked yellow了
此时,如果将yellow设置z值设为1,blue的z值不变,点击重叠区域
则log又只能输出clicked yellow
这时向yellow添加设置,传递yellow的鼠标事件,log会再次输出两者: clicked yellow,clicked blue
Handler
qml 中有一种更简便快速的方法,能够帮助我们处理各种鼠标点击事件,避免多个mousearea重叠。就是一些带有handler后缀名称的组件。
这些组件作为子组件添加到父组件中,就可以让父组件具备对应的功能。
常见handler组件如下:
1. TapHandler: 用于处理点击和双击事件。
2. LongPressHandler: 用于处理长按事件。
3. PanHandler: 用于处理平移(拖动)手势。
4. PinchHandler: 用于处理捏合手势,用于缩放操作。
5. SwipeHandler: 用于处理滑动手势。
6. WheelHandler: 用于处理鼠标滚轮事件。
7. KeyHandler: 用于处理键盘事件。
8. MouseHandler: 用于处理鼠标事件。
9. MultiPointTouchHandler: 用于处理多点触控手势。
10. GestureHandler: 这是一个抽象基类,用于创建自定义手势处理器的基类。
11. Grabber: 这是一个辅助类,用于控制手势处理器的抓取行为。
这些组件通常用于检测和响应用户的交互手势,如触摸、拖动、捏合等。它们可以添加到QML元素中,以提供对特定类型事件的响应。使用这些组件可以帮助开发者更轻松地实现复杂的手势识别和处理逻辑。