事件冒泡的原理?如何禁止事件冒泡 ?

本文介绍了事件流的概念,包括事件冒泡与事件捕获两种不同的处理方式,并详细讲解了如何使用stopPropagation()和preventDefault()方法来阻止事件的默认行为。

1.如何阻止事件冒泡?

function stopBubble(e){
    //如果提供了事件对象,则这是一个非 ie 浏览器
    if(e && e.stopPropagation){
        e.stopPropagation();
    }else{
        window.event.cancelBubble = true;
    }
    return false;
}

2.事件流

事件流描述的是从页面中接受事件的顺序,IE 的事件流是事件冒泡,而 Netscape Communicator 的事件流是事件捕获流。

事件冒泡:即事件开始时由最具体的元素,然后逐级向上传播到较为不具体的结点。(ieffchromesafari会将事件一直冒泡到 window 对象。)

事件捕获:事件捕获的思想是不太具体的结点应该更早接收到事件,而最具体的结点应该最后接受到事件。事件捕获的用意在于在事件达到预定目标之前捕获它。

要阻止特定事件的默认行为,可以使用 preventDefault()方法。例如,链接的默认行为就是在被单击时会导航到其 href 特性指定的 URL。如果你想阻止链接导航这一默认行为,那么通过链接的onclick 事件处理程序可以取消它:

var link = document.getElementById("myLink");
    link.onclick = function(event){
    event.preventDefault();
};

只有 cancelable 属性设置为 true 的事件,才可以使用 preventDefault()来取消其默认行为。

stopPropagation()方法用于立即停止事件在 DOM 层次中的传播,即取消进一步的事件捕获或冒泡。例如,直接添加一个按钮的事件处理给程序可以调用 stopPropagetion(),从而避免触发注册在 document.body 上面的事件处理程序:

var btn = document.getElementById("myBtn");
    btn.onclick = function(event){
    alert("Clicked");
    event.stopPropagation();
};
document.body.onclick = function(event){
    alert("Body clicked");
};

事件对象的 eventPhase 属性,可以用来确定事件当前正位于事件流的哪个阶段。如果是在捕获阶段调用的事件处理程序,其值为1;如果事件处理程序处于目标对象上,其值为2;如果是在冒泡阶段调用的事件处理程序,其值为3。这里要注意的是,尽管“处于目标”发生在冒泡阶段,但是 eventPhase 的值仍然一直等于2

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
    alert(event.eventPhase);//2
}

document.body.addEventListener("click",function(evnet){
    alert(event.eventPhase);//1
},true);

document.body.onclick = function(event){
    alert(event.eventPhase);//3
}

当单击这个例子中的按钮时,首先执行的事件处理程序是在捕获阶段触发的添加到 document.body 中的那一个,结果会弹出一个警告框表示 event.Phase 的值为1。接着,会触发在按钮上注册的事件处理程序,此时的 eventPhase 的值为2;最后一个被触发的事件处理程序,是在冒泡阶段执行的添加到 document.body 上的那一个,显示 eventPhase 的值为3。而当 eventPhase 的值为2时,this.targetcurrentTarget 的值始终都是相等的。

只有在事件处理程序执行期间,event 对象才会存在;一旦事件处理程序执行完成,event 对象就会被销毁。

<think>我们正在讨论Kivy内置触摸事件冒泡传递机制。Kivy的触摸事件处理遵循特定的传播规则,其冒泡过程是通过组件的树形结构和事件处理方法共同决定的。下面我将详细解释Kivy内置触摸事件如何实现冒泡传递。 ### Kivy内置触摸事件冒泡机制 Kivy的触摸事件(包括`on_touch_down`、`on_touch_move`、`on_touch_up`)的传播过程分为两个阶段: 1. **向下传播阶段(捕获阶段)**:从根部件开始,沿着部件树向下传递,直到目标部件(即触摸点所在的叶子部件)。 2. **向上传播阶段(冒泡阶段)**:从目标部件开始,沿着部件树向上传递,直到根部件。 #### 关键方法 在Kivy中,每个`Widget`类都有三个核心方法处理触摸事件: - `on_touch_down(touch)` - `on_touch_move(touch)` - `on_touch_up(touch)` 这些方法的默认行为决定了事件传播的方式。 #### 事件传播规则 1. **向下传播(捕获阶段)**: - 事件首先到达根窗口部件(通常是`Window`)。 - 然后从根部件开始,依次调用每个子部件的`on_touch_down`方法,直到到达目标部件(即触摸点所在的部件)。 - 在向下传播过程中,如果某个部件的`on_touch_down`方法返回`True`,表示事件已被处理,停止向下传播(但注意:这并不会停止向上传播)。 2. **向上传播(冒泡阶段)**: - 当事件到达目标部件后,如果目标部件没有处理事件(即返回`False`),则事件会沿着父链向上传播,调用每个父部件的`on_touch_down`方法(注意:这里仍然是`on_touch_down`,Kivy没有单独的冒泡阶段方法,而是通过返回值控制)。 - 在向上传播过程中,如果某个父部件的`on_touch_down`方法返回`True`,则停止向上传播。 #### 代码示例 下面是一个简单的示例,展示两个嵌套的按钮,演示触摸事件的传播: ```python from kivy.app import App from kivy.uix.button import Button from kivy.uix.boxlayout import BoxLayout class CustomButton(Button): def on_touch_down(self, touch): print(f"{self.text} - 触摸按下(向下传播)") # 如果触摸点在本按钮区域内,则处理事件并返回True if self.collide_point(*touch.pos): print(f"{self.text} 处理了触摸事件") return True # 停止向下传播,但会继续向上传播(实际上在Kivy中,返回True会停止向下传播,但向上传播仍然会进行?注意:这里需要澄清) return super().on_touch_down(touch) class CustomBoxLayout(BoxLayout): def on_touch_down(self, touch): print("BoxLayout - 触摸按下(向下传播)") # 调用子部件处理 if super().on_touch_down(touch): return True # 如果子部件处理了,则不再向上传播(实际上,在向下传播过程中,super()的调用已经完成了向下传播,并返回了是否被处理) # 如果没有子部件处理,则这里可以处理 if self.collide_point(*touch.pos): print("BoxLayout 处理了触摸事件") return True return False class TestApp(App): def build(self): root = CustomBoxLayout() btn1 = CustomButton(text='外部按钮') btn2 = CustomButton(text='内部按钮') root.add_widget(btn1) btn1.add_widget(btn2) return root if __name__ == '__main__': TestApp().run() ``` **注意**:上述代码中,我们重写了`on_touch_down`方法。在Kivy中,事件传播的实现细节如下: - 在父部件的`on_touch_down`方法中,调用`super().on_touch_down(touch)`会触发对子部件的`on_touch_down`的调用(即向下传播)。 - 如果子部件返回了`True`,则父部件的`super().on_touch_down(touch)`也会返回`True`,表示事件已被处理,父部件可以选择不再处理(即不再执行后续代码)或者继续处理。 #### 冒泡过程详解 1. 当触摸事件发生时,首先调用根部件的`on_touch_down`。 2. 根部件调用`super().on_touch_down(touch)`,该方法会遍历所有子部件(按照Z序从后往前,即先添加的在后),对每个子部件调用`on_touch_down`,直到有一个子部件返回`True`。 3. 如果某个子部件返回`True`,则停止向其他子部件传播(即不再调用其他子部件的`on_touch_down`),并且该父部件的`super().on_touch_down(touch)`返回`True`。 4. 如果父部件在`super().on_touch_down(touch)`后得到`True`,则父部件可以选择直接返回`True`(不再执行自己的处理逻辑)或者继续执行自己的处理逻辑(即使子部件已经处理了,父部件也可以再次处理,但这不符合常规,通常我们会避免这样做)。 5. 如果没有任何子部件处理事件(即所有子部件的`on_touch_down`都返回`False`),那么父部件可以自己处理(如果触摸点在其区域内)。 #### 阻止冒泡 在Kivy中,没有直接提供`stop_propagation`方法。但我们可以通过返回值来控制: - 在向下传播阶段,如果某个部件处理了事件(返回`True`),则停止继续向下传播(即不再向其他子部件传递)。 - 在向上传播阶段,由于Kivy的事件传播机制是:先向下传播,然后向上传播(通过函数调用栈),所以实际上没有显式的向上传播步骤。父部件在调用`super().on_touch_down(touch)`(即向下传播)后,根据返回值判断是否继续处理。因此,如果子部件返回了`True`,父部件可以选择不再处理(即不再执行自己的处理代码)来模拟阻止冒泡。 ### 总结 Kivy的触摸事件冒泡是通过以下方式实现的: 1. **向下传播**:父部件调用`super().on_touch_down(touch)`来将事件传递给子部件。如果某个子部件返回`True`,则停止继续向其他子部件传递。 2. **向上传播**:实际上,在向下传播的过程中,事件处理函数的调用栈已经形成了从根到叶子节点的路径。当子部件处理事件后(返回`True`),这个返回值会逐层返回给父部件,父部件可以选择不再处理(即不再执行子部件处理后的代码)来避免“冒泡”到更高层。但是,如果父部件在子部件处理之后仍然有代码要执行,那么它依然会执行(这可以视为在冒泡阶段执行)。 因此,我们可以这样理解:Kivy的触摸事件处理同时包含了捕获(向下)和冒泡(向上)两个阶段,但是它们是在同一个方法调用过程中完成的。 §§相关问题§§ 1. 如何在Kivy中实现自定义事件冒泡机制? 2. Kivy中如何阻止触摸事件的传播? 3. Kivy的触摸事件和鼠标事件传播机制有何不同? </think> ### Kivy内置触摸事件冒泡传递机制详解 Kivy的内置触摸事件天然支持冒泡传递机制,其实现基于**组件树结构**和**事件处理方法**的协作。以下是核心实现原理和过程: #### 1. 事件传播三阶段 Kivy的触摸事件传播分为三个阶段: ```mermaid graph TD A[捕获阶段] --> B[目标阶段] B --> C[冒泡阶段] ``` #### 2. 核心实现方法 在`Widget`类中,触摸事件通过以下方法实现冒泡: ```python def on_touch_down(self, touch): # 1. 捕获阶段处理 if self.collide_point(*touch.pos): touch.grab(self) # 标记事件抓取 # 2. 向子组件传递(冒泡开始) for child in self.children[:]: if child.dispatch('on_touch_down', touch): return True # 子组件已处理 # 3. 目标阶段处理 if self.collide_point(*touch.pos): return True # 当前组件处理 return False # 事件未处理 ``` #### 3. 冒泡传递流程 1. **向下传播(捕获阶段)**: - 从根组件开始,递归调用子组件的`on_touch_down` - 使用`collide_point()`检测触摸点是否在组件区域内 2. **目标处理阶段**: - 当触摸点命中组件时,触发组件的触摸处理逻辑 - 通过`touch.grab(self)`标记事件所有权 3. **向上传播(冒泡阶段)**: - 如果子组件未处理事件(`return False`) - 事件会向父组件冒泡,触发父组件的`on_touch_down` #### 4. 关键控制方法 - **停止传播**: ```python def on_touch_down(self, touch): if some_condition: return True # 停止向其他组件传播 ``` - **事件抓取**(阻止父组件处理): ```python def on_touch_down(self, touch): touch.grab(self) # 声明事件所有权 return True ``` #### 5. 完整传播示例 ```python from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button class BubblingButton(Button): def on_touch_down(self, touch): if self.collide_point(*touch.pos): print(f"按钮 {self.text} 处理触摸") return True # 阻止事件冒泡 return super().on_touch_down(touch) class BubblingLayout(BoxLayout): def on_touch_down(self, touch): if self.collide_point(*touch.pos): print("布局捕获触摸") # 继续向子组件传递 return super().on_touch_down(touch) class TestApp(App): def build(self): root = BubblingLayout() btn = BubblingButton(text="点我") root.add_widget(btn) return root ``` #### 6. 触摸事件生命周期 1. `on_touch_down`:触摸按下 2. `on_touch_move`:触摸移动 3. `on_touch_up`:触摸释放 每个阶段都遵循相同的冒泡规则: ``` [根组件] → [父组件] → [目标组件] → [父组件] → [根组件] ``` #### 7. 冒泡控制技巧 - **阻止父组件处理**: ```python def on_touch_down(self, touch): touch.ungrab(self) # 释放事件所有权 return False # 允许继续冒泡 ``` - **强制冒泡**: ```python def on_touch_down(self, touch): if self.collide_point(*touch.pos): # 当前组件处理... return False # 继续向上冒泡 ``` > Kivy的触摸事件系统借鉴了经典的事件冒泡模型,其实现细节可在`kivy/uix/widget.py`的`Widget`类源码中查看[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值