Flutter 事件传递简单概述、事件冒泡、事件穿透

news2024/9/26 5:18:06

前言

当前案例 Flutter SDK版本:3.13.2

本文对 事件传递只做 简单概述,主要讲解,事件传递过程中可能遇到的问题解决,比如 事件冒泡事件穿透;

不是我偷懒,是自认为没有这几位写的详细、仔细,非常建议先看完这几篇参考文档,不然下面讲解一些对象或者函数会不理解。

深入进阶-从一次点击探寻Flutter事件分发原理 - 掘金

Flutter分享:Flutter事件分发原理 - 掘金

8.3 Flutter事件机制 | 《Flutter实战·第二版》

8.4 手势原理与手势冲突 | 《Flutter实战·第二版》

Flutter事件传递简单概述

重要对象介绍

HitTestEntry:可以把它看成视图中的 手势监听组件,主要信息都在 target 属性中。

HitTestResult:翻译为 命中测试结果,重点是它的 _path 集合保存着 HitTestEntry 对象;

重要函数介绍

hitTest(result,position) 翻译为 命中测试手势监听组件 内部会调用的方法,如果返回true,会将当前 手势监听组件 也就是 HitTestEntry 加入 HitTestResult._path 集合中,这只是默认规则,可以手动添加

核心代码:result.add(BoxHitTestEntry(this, position)),将 HitTestEntry (手势监听组件) 加入 HitTestResult._path 集合中;

核心代码:`result.add(BoxHitTestEntry(this, position))`:将 `HitTestEntry` **(手势监听组件)** 加入 `HitTestResult._path` 集合中;

还有查找 监听组件的顺序,是由深到浅的查找,比如 父子结构查找顺序:子孙手势组件、子手势组件、父手势组件,其他传统布局查找顺序:兄弟手势组件03、兄弟手势组件02、兄弟手势组件01。

那这个 hitTest函数的 布尔值是不是没用了?当然有用,后面会讲解,先忽略

最开始执行的是 renderView.hitTest(result, position: position)renderView 表示 渲染树的根节点;

class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {

 
  bool hitTest(HitTestResult result, { required Offset position }) {
    
    // 这部分逻辑是父子结构的组件,才走的
    if (child != null) { 
      child!.hitTest(BoxHitTestResult.wrap(result), position: position);
    }

    // 你手指触摸位置的那个 手势监听组件,加入 HitTestResult._path 集合中
    result.add(HitTestEntry(this)); 
    return true;
  }

}


abstract class RenderBox extends RenderObject {

  // 父子结构的组件,走到这
  bool hitTest(BoxHitTestResult result, { required Offset position }) {   
    
    ... ...

    if (_size!.contains(position)) {
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
        result.add(BoxHitTestEntry(this, position));
        return true;
      }
    }
    return false;
  }

}

常用的手势监听组件

Listener组件

只监听最原始的几种事件,down ==> move ==> ... ==> move ==> up ==> cancel;

比如 第一次将手指放在屏幕上 触发 Down 事件,手指没有离开屏幕前,手指位置发生改变 触发 Move 事件,每次位置改变都会触发一次 Move 事件,手指离开屏幕时触发 Up事件,紧接着 触发 Cancel事件;

常用的一些手势,比如 单击、双击、长按 等等,它都识别不了,也不负责处理事件冲突

Listener(
    onPointerDown: (event) {
        debugPrint('onPointerDown');
    },
    child: Container(
        width: 100,
        height: 100,
        color: Colors.primaries[10],
    ),
)

GestureDetector

对Listener的封装后的产物,内部加了很多 GestureRecognizer (手势识别器),每个识别器都代表一种手势监听,比如监听 单击、双击、长按、缩放 等等手势,以及可以通过自定义手势识别器解决事件冲突,所以一般都用它

GestureDetector(
  onTap: () {
    debugPrint('onTap');
  },
  child: Container(
    width: 100,
    height: 100,
    color: Colors.primaries[10],
  ),
)
class GestureDetector extends StatelessWidget {

  ... ... 

  @override
  Widget build(BuildContext context) {

    ... ...

    // TapGestureRecognizer 单击手势识别器
    gestures[TapGestureRecognizer] = ... ...


    // DoubleTapGestureRecognizer 双击手势识别器
    gestures[DoubleTapGestureRecognizer] = ... ...

    ... ...

    return RawGestureDetector(
      ... ...
    );
  }
}

class RawGestureDetector extends StatefulWidget { 

  ... ...

  @override
  RawGestureDetectorState createState() => RawGestureDetectorState();
}

class RawGestureDetectorState extends State<RawGestureDetector> {

  ... ... 

  @override
  Widget build(BuildContext context) {

    Widget result = Listener( // 原始手势监听器
        ... ... 
    );
    
    ... ...

    return result;
  }

  ... ...

}

InkWell

对GestureDetector的封装,加了点击时出现水波纹效果,我项目里基本不用这东西。

注意:它这个水波纹效果,实现位置是在 Child 下面,所以Child 颜色要为透明,不然看不见;

一般是通过 Material 组件设置背景色,来解决这个问题。

Material(
  color: Colors.greenAccent, // 设置背景色
  child: InkWell(
    onTap: () {
      debugPrint('onTap');
    },
    child: Container(
      width: 100,
      height: 100,
    ),
  ),
),
class InkWell extends InkResponse {

  ... ...

}

class InkResponse extends StatelessWidget {

  ... ...

  @override
  Widget build(BuildContext context) {

    ... ...

    return _InkResponseStateWidget(
      ... ...
    );
  }

  ... ...

}

class _InkResponseStateWidget extends StatefulWidget {

  ... ... 

  @override
  _InkResponseState createState() => _InkResponseState();

  ... ...

}

class _InkResponseState extends State<_InkResponseStateWidget> with AutomaticKeepAliveClientMixin<_InkResponseStateWidget> implements _ParentInkResponseState {

  ... ... 

  @override
  Widget build(BuildContext context) {
    ... ...

    return _ParentInkResponseProvider(
        ... ...

        child: GestureDetector( // 手势监听器
             ... ...
        ),

      ),
    );
  }
  ... ...

}

事件传递过程

这个过程是我根据断点调试顺序构思的,如有错误,还请评论区留言,共勉。

默认传递过程

使用HitTestBehavior的传递过程

HitTestBehavior

翻译 命中测试行为,它不是一个对象,只是一个概念,让我们自己写 命中测试 逻辑,通过以下两个对象 实现。

RenderProxyBox:它是RenderObject的子类,可以重写 hitTest 命中测试函数,从而修改事件传递过程,RenderObject 属于 渲染树无法直接Widget树 中使用,需要包一层 SingleChildRenderObjectWidget。

SingleChildRenderObjectWidget:用来将 RenderObject 类型的组件,转换成 RenderObjectWidget,让其 可以在 Widget树中 使用;

会涉及到两个知识点:

  1. 事件中断机制;
  2. 还有 hitTest 命中测试函数 返回布尔值 有什么用;

我都写在代码注释里

如果你想自定义手势,建议去研究 GestureDetector 里的 GestureRecognizer (手势识别器),因为它用的最多,有的组件 甚至提供了 GestureRecognizer类型参数。

class MyListener extends SingleChildRenderObjectWidget {
  MyListener(
      {super.key,
        this.downEventListener,
        this.hitTestBehavior = MyHitTestBehavior.normal,
        super.child});

  PointerDownEventListener? downEventListener;
  MyHitTestBehavior hitTestBehavior;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return MyRenderListener(
        downEventListener: downEventListener, hitTestBehavior: hitTestBehavior);
  }

  @override
  void updateRenderObject(
      BuildContext context, covariant MyRenderListener renderObject) {
    renderObject.downEventListener = downEventListener;
    renderObject.hitTestBehavior = hitTestBehavior;
  }
}

class MyRenderListener extends MyRenderHitTestBehavior {
  MyRenderListener({this.downEventListener, super.hitTestBehavior});

  PointerDownEventListener? downEventListener;

  @override
  void handleEvent(PointerEvent event, covariant HitTestEntry<HitTestTarget> entry) {
    if (event is PointerDownEvent) {
      return downEventListener?.call(event);
    }
  }

}

abstract class MyRenderHitTestBehavior extends RenderProxyBox {
  MyRenderHitTestBehavior({this.hitTestBehavior = MyHitTestBehavior.normal});

  MyHitTestBehavior hitTestBehavior;

  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {

    if(hitTestBehavior == MyHitTestBehavior.normal) { // 默认
      return super.hitTest(result, position: position);
    }

    if(hitTestBehavior == MyHitTestBehavior.ignore) {
      return false; // 强制命中测试失败
    }

    // 下面两个判断,区别在于 返回布尔值不一样

    // 同一容器内的 兄弟级别事件监听组件,只要有一个返回true,
    // 其他的都会返回false,这叫 事件中断机制,触发了这个机制,
    // 这些返回false的,将不参与 事件命中测试,即使加入了 HitTestResult.path 集合 也没用
    // 因为这些 事件监听组件的 handleEvent 没有触发

    // 注意:是触发了 中断机制 之后,这些返回false的 事件监听组件 才不参与 事件命中测试
    // 不是因为返回值是false,就不参与 事件命中测试,跟 false 没啥关系

    // 不触发 中断机制 的方法
    // 全部返回 false,这样只要在 HitTestResult.path 里的事件监听组件,都会被 分发事件

    if(hitTestBehavior == MyHitTestBehavior.opaque) {
      if(size.contains(position)) { // 点击的坐标,是否在 事件监听组件 范围内
        result.add(BoxHitTestEntry(this, position));
        return true; // 强制命中测试成功,会触发中断机制
      }
    }

    if (hitTestBehavior == MyHitTestBehavior.avoidInterruptions) {

      // 注意:这里我没有使用这个 范围判断,触发范围会变成 它父级组件 范围
      // if(size.contains(position))
      result.add(BoxHitTestEntry(this, position));
      return false; // 强制命中测试失败,不会触发中断机制
    }

    return false;
  }

  @override
  bool hitTestSelf(Offset position) => super.hitTestSelf(position);

// hitTestSelf函数 是父子结构组件 的判断条件 之一,你点开 super.hitTest(result, position: position);源码

// 父子结构组件
// return Listener( // 父组件
//   ... ...
//   child: Container(
//    ... ...
//     child: Listener( // 子组件
//       ... ...
//       child: Container(
//         ... ...
//       ),
//     ),
//   ),
// );

// super.hitTest(result, position: position); 源码:

// 重点代码:如果子组件全都 命中测试失败,那就判断 hitTestSelf函数的 返回值
// if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
//    ... ...
// }

// bool hitTest(BoxHitTestResult result, { required Offset position }) {
//   ... ...
//   if (_size!.contains(position)) {
//     if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
//       result.add(BoxHitTestEntry(this, position));
//       return true;
//     }
//   }
//   return false;
// }

}

enum MyHitTestBehavior {
  ignore, // 不参与 命中测试
  opaque, // 强制命中测试成功
  avoidInterruptions, // 避免触发中断机制
  normal  // 默认
}

 使用 MyListener

  Widget box(int index, double size) {
    return MyListener(
      // hitTestBehavior: MyHitTestBehavior.ignore, // 事件拦截
      hitTestBehavior: MyHitTestBehavior.avoidInterruptions, // 所有兄弟节点都会被分发事件
      downEventListener: (event) {
        debugPrint('index:$index');
      },
      child: Container(
        width: size,
        height: size,
        color: Colors.primaries[index],
      ),
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SizedBox(
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.height,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [

              Container(
                color: Colors.greenAccent,
                width: 150,
                height: 400,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    box(1, 100),
                    box(2, 100),
                    box(3, 100),
                    box(4, 100),
                  ],
                ),
              ),

            ],
          )),
    );
  }

事件冒泡

事件冒泡的产生原因

在父子结构组件中,父组件会先调用 hitTestChildren 方法,最后调用自身的 hitTest方法;
父组件判断自身是否 命中测试 的条件:只要有一个子组件的 hitTest 方法 返回true,父组件 hitTest方法 也会返回true,导致它会执行handleEvent方法,递归这个过程,就会产生事件冒泡

hitTestChildren(result, position):执行子组件的 hitTest 方法;

// 事件冒泡代码
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Listener(
      onPointerDown: (event) {
        debugPrint('Parent --- onPointerDown');
      },
      child: Container(
        width: 300,
        height: 300,
        margin: const EdgeInsets.only(bottom: 12),
        color: Colors.primaries[10],
        alignment: Alignment.center,
        child: Listener(
            onPointerDown: (event) {
              debugPrint('Child01 --- onPointerDown');
            },
            child: Container(
              width: 200,
              height: 200,
              margin: const EdgeInsets.only(bottom: 12),
              color: Colors.primaries[8],
              alignment: Alignment.center,
              child: Listener(
                onPointerDown: (event) {
                  debugPrint('Child02 --- onPointerDown');
                },
                child: Container(
                  width: 100,
                  height: 100,
                  margin: const EdgeInsets.only(bottom: 12),
                  color: Colors.primaries[11],
                ),
              )
            )
        ),
      ),
    ),
  ],
)

解决方式一:通过变量判断

// 解决方式一:通过变量判断
Builder(
  builder: (context) {
    bool childEvent = false;
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Listener(
          onPointerDown: (event) {
            if(!childEvent) {
              debugPrint('Parent --- onPointerDown');
            }
            childEvent = false;
          },
          child: Container(
            width: 300,
            height: 300,
            margin: const EdgeInsets.only(bottom: 12),
            color: Colors.primaries[10],
            alignment: Alignment.center,
            child: Listener(
                onPointerDown: (event) {
                  if(!childEvent) {
                    debugPrint('Child01 --- onPointerDown');
                    childEvent = true;
                  }
                },
                child: Container(
                    width: 200,
                    height: 200,
                    margin: const EdgeInsets.only(bottom: 12),
                    color: Colors.primaries[8],
                    alignment: Alignment.center,
                    child: Listener(
                      onPointerDown: (event) {
                        debugPrint('Child02 --- onPointerDown');
                        childEvent = true;
                      },
                      child: Container(
                        width: 100,
                        height: 100,
                        margin: const EdgeInsets.only(bottom: 12),
                        color: Colors.primaries[11],
                      ),
                    )
                )
            ),
          ),
        ),
      ],
    );
  }
),

解决方式二:使用GestureDetector

// 使用GestureDetector解决
// 注意一:
// 有参数的事件回调,还是会触发冒泡,比如onTapDown(details),以此类推
// onTap():可以防止冒泡,onTapDown(details)不可以;
// onDoubleTap():可以防止冒泡,onDoubleTapDown(details)不可以;
//
// 注意二:而且它俩都是up事件,手指离开屏幕时才会触发
// ... ...
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    GestureDetector(
      onTap: () {
        debugPrint('Parent --- onPointerDown');
      },
      child: Container(
        width: 300,
        height: 300,
        margin: const EdgeInsets.only(bottom: 12),
        color: Colors.primaries[10],
        alignment: Alignment.center,
        child: GestureDetector(
            onTap: () {
              debugPrint('Child01 --- onPointerDown');
            },
            child: Container(
                width: 200,
                height: 200,
                margin: const EdgeInsets.only(bottom: 12),
                color: Colors.primaries[8],
                alignment: Alignment.center,
                child: GestureDetector(
                  onTap: () {
                    debugPrint('Child02 --- onPointerDown');
                  },
                  child: Container(
                    width: 100,
                    height: 100,
                    margin: const EdgeInsets.only(bottom: 12),
                    color: Colors.primaries[11],
                  ),
                )
            )
        ),
      ),
    ),
  ],
),

事件穿透

在叠加布局中,两个组件是位置相同相互覆盖,且两个都有事件,如何忽略盖在上面的组件事件,只触发底层的组件事件,这种场景出现的很少;

事件穿透应用场景:在叠加布局中,两个组件是位置相同相互覆盖,且两个都注册了事件监听器,如何忽略盖在上面的组件事件,只触发底层组件的事件;

这里介绍一下 IgnorePointer 和 AbsorbPointer 组件,它们的原理就是让这些组件不参与命中测试,从而做到事件拦截

  • IgnorePointer组件:包裹的组件,以及子组件、子孙后代组件,都不参与命中测试;
  • AbsorbPointer组件:包裹组件的 子组件、子孙后代组件 不参与命中测试,但不包括自身,点击子组件区域,还是会触发自身事件;

它俩都有一个是否启用的布尔值参数,默认为true,表示启用,可以通过变量动态操控;

使用IgnorePointer,包裹的组件事件被完全拦截,可以做到事件穿透的效果,反之AbsorbPointer不可以

// 在叠加布局中使用
Stack(
  alignment: Alignment.center,
  children: [
    Listener(
      onPointerDown: (event) {
        debugPrint('Child01 --- onPointerDown');
      },
      child: Container(
        width: 300,
        height: 300,
        margin: const EdgeInsets.only(bottom: 12),
        color: Colors.primaries[10],
      ),
    ),

    // Listener(
    //     onPointerDown: (event) {
    //       debugPrint('Child02 --- onPointerDown');
    //     },
    //     child: IgnorePointer(
    //       child: Container(
    //         width: 200,
    //         height: 200,
    //         margin: const EdgeInsets.only(bottom: 12),
    //         color: Colors.primaries[8],
    //       ),
    //     )
    // ),

    // 或者这样写 都可以

    // 拦截当前组件事件,但同一位置的底层组件,会被触发,相当于穿透了
    IgnorePointer(
      child: Listener(
          onPointerDown: (event) {
            debugPrint('Child02 --- onPointerDown');
          },
          child: Container(
            width: 200,
            height: 200,
            margin: const EdgeInsets.only(bottom: 12),
            color: Colors.primaries[8],
          )
      ),
    ),

    // 拦截当前组件事件,但同一位置的底层组件无法触发,无法穿透
    // AbsorbPointer(
    //   child: Listener(
    //       onPointerDown: (event) {
    //         debugPrint('Child02 --- onPointerDown');
    //       },
    //       child: Container(
    //         width: 200,
    //         height: 200,
    //         margin: const EdgeInsets.only(bottom: 12),
    //         color: Colors.primaries[8],
    //       )
    //   ),
    // ),

  ],
),

事件竞争

  • 当用户触摸屏幕时,可能同时触发好几种事件,这时候需要处理 事件冲突,确定哪一种 手势操作,Flutter提供了GestureArenaManager(手势竞技场)对象,将每一个手势当作一个竞选者,进行了筛选;

GestureArenaManager:官方视频:https://www.youtube.com/watch?v=Q85LBtBdi0U&t=469s


每个手势都有自己的判定条件,且每次竞争,只能有一个胜利者,举例:

  • 短按:手指按下 200毫秒
  • 长按:手指按下 500毫秒
  • ... ...

API 过时

以后要是找不到 hitTest 函数 就找 hitTestInView 函数

官方文档

gestures library - Dart API

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1538999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA学习_时序分析

文章目录 前言一、组合逻辑与时序逻辑二、建立时间和保持时间三、建立时间和保持时间 前言 心中有电路&#xff0c;下笔自然神&#xff01;&#xff01;&#xff01; 一、组合逻辑与时序逻辑 组合逻辑&#xff1a;没有时钟控制的数字电路&#xff0c;代码里的判断逻辑都是组…

颠覆传统:Web3如何塑造未来的数字经济

引言 近年来&#xff0c;随着数字化时代的到来&#xff0c;互联网已经成为人们生活中不可或缺的一部分。然而&#xff0c;随着技术的不断发展和社会的不断变迁&#xff0c;传统的Web2模式逐渐显露出一些弊端&#xff0c;如数据垄断、隐私泄露等问题&#xff0c;这促使人们寻求…

简历指导与模板获取

简历是应聘过程当中最重要的材料&#xff0c;是我们在求职市场的一张名片&#xff0c;一份好的简历能够吸引招聘者的注意&#xff0c;使你在竞争激烈的求职市场中脱颖而出。 1.简历指导 以下是一份典型简历的主要部分和常见内容&#xff1a; 联系信息&#xff1a; 包括你的全…

设计模式 适配器模式

1.背景 适配器模式&#xff0c;这个模式也很简单&#xff0c;你笔记本上的那个拖在外面的黑盒子就是个适配器&#xff0c;一般你在中国能用&#xff0c;在日本也能用&#xff0c;虽然两个国家的的电源电压不同&#xff0c;中国是 220V&#xff0c;日本是 110V&#xff0c;但是这…

操作系统面经-什么是操作系统?

通过以下四点可以概括操作系统到底是什么&#xff1a; 操作系统&#xff08;Operating System&#xff0c;简称 OS&#xff09;是管理计算机硬件与软件资源的程序&#xff0c;是计算机的基石。操作系统本质上是一个运行在计算机上的软件程序 &#xff0c;主要用于管理计算机硬…

DP:路径规划模型

创作不易&#xff0c;感谢三连支持&#xff01; 路径规划主要是让目标对象在规定范围内的区域内找到一条从起点到终点的无碰撞安全路径。大多需要用二维dp数组去实现 一、不同路径 . - 力扣&#xff08;LeetCode&#xff09;不同路径 class Solution { public:int uniquePath…

自动驾驶---Motion Planning之轨迹Path优化

1 背景 在之前的几篇文章中,不管是通过构建SL图《自动驾驶---Motion Planning之Path Boundary》,ST图《自动驾驶---Motion Planning之Speed Boundary》,又或者是构建SLT图《自动驾驶---Motion Planning之构建SLT Driving Corridor》,最终我们都是为了得到boundary的信息。 …

基于springboot的4S店车辆管理系统

基于springboot的4S店车辆管理系统 的设计和实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开…

算法-最短路径

图的最短路径问题是一个经典的计算机科学和运筹学问题&#xff0c;旨在找到图中两个顶点之间的最短路径。这种问题在多种场景中都有应用&#xff0c;如网络路由、地图导航等。 解决图的最短路径问题有多种算法&#xff0c;其中最著名的包括&#xff1a; 1.迪杰斯特拉算法 (1).…

抖音小店怎么定类目?分享几个爆单几率大,适合新手的细分类目!

大家好&#xff0c;我是电商糖果 做电商的应该经常听过这么一句话&#xff0c;类目大于一切&#xff01; 好的类目可以让商家减少很多竞争和难题。 糖果做电商有很多年了&#xff0c;我一直认为做店前期最难的定类目&#xff0c;中期是选品&#xff0c;后期是维护店铺。 如…

物联网数据报表分析

随着物联网技术的迅猛发展&#xff0c;越来越多的企业开始将物联网解决方案应用于各个领域&#xff0c;从提高生产效率到优化用户体验&#xff0c;物联网都发挥着至关重要的作用。然而&#xff0c;如何有效地分析和管理物联网产生的海量数据&#xff0c;成为企业面临的挑战之一…

【Java开发过程中的流程图】

流程图由一系列的图形符号和箭头组成&#xff0c;每个符号代表一个特定的操作或决策。下面是一些常见的流程图符号及其含义&#xff1a; 开始/结束符号&#xff08;圆形&#xff09;&#xff1a;表示程序的开始和结束点。 过程/操作符号&#xff08;矩形&#xff09;&#xff…

<Linux> 生产者消费者模型

目录 前言&#xff1a; 一、什么是生产者消费者模型 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;生产者消费者之间的关系 &#xff08;三&#xff09;生产者消费者模型特点 &#xff08;四&#xff09;生产者消费者模型的优点 二、基于阻塞队列实现生产…

《定时执行专家》:Nircmd 的超级搭档,解锁自动化新境界

目录 Nircmd 简介 《定时执行专家》与 Nircmd 的结合 示例&#xff1a; 自动清理电脑垃圾: 定时发送邮件: 定时关闭电脑: 《定时执行专家》的优势: 总结: 以下是一些其他使用示例&#xff1a; 立即下载《定时执行专家》&#xff1a; Nircmd 官方网站&#xff1a; 更…

代码随想录阅读笔记-栈与队列【用队列实现栈】

题目 使用队列实现栈的下列操作&#xff1a; push(x) -- 元素 x 入栈pop() -- 移除栈顶元素top() -- 获取栈顶元素empty() -- 返回栈是否为空 注意: 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。你所使用的语言…

普发Pfeiffer镀膜机Classic580-500SP Spider600 全套资料包含操作使用说明

普发Pfeiffer镀膜机Classic580-500SP Spider600 全套资料包含操作使用说明

LeetCode---126双周赛

题目列表 3079. 求出加密整数的和 3080. 执行操作标记数组中的元素 3081. 替换字符串中的问号使分数最小 3082. 求出所有子序列的能量和 一、求出加密整数的和 按照题目要求&#xff0c;直接模拟即可&#xff0c;代码如下 class Solution { public:int sumOfEncryptedInt…

瑞芯微RK3576|触觉智能:开启科技新篇章

更多产品详情可关注深圳触觉智能官网&#xff01; “瑞芯微&#xff0c;创新不止步&#xff01;”——全新芯片RK3576即将震撼登场。指引科技风潮&#xff0c;创造未来无限可能&#xff01;这款芯片在瑞芯微不断创新和突破的道路上&#xff0c;不仅是对过往成就的完美延续&…

ROS2从入门到精通0-3:VSCode 搭建 ROS2 工程环境

目录 0 专栏介绍1 Ubuntu下安装VSCode1.1 基本安装1.2 将VSCode添加到侧边栏 2 VSCode集成相关插件3 VSCode运行ROS2环境步骤3.1 安装编译依赖项3.2 创建工作空间和源码空间3.3 启动VSCode与配置 4 测试工程环境4.1 C版本4.2 Python版本 0 专栏介绍 本专栏旨在通过对ROS2的系统…

【机器学习入门 】逻辑斯蒂回归和分类

系列文章目录 第1章 专家系统 第2章 决策树 第3章 神经元和感知机 识别手写数字——感知机 第4章 线性回归 文章目录 系列文章目录前言一、分类问题的数学形式二、最大似然估计三、交叉熵损失函数四、多类别分类多类别逻辑斯蒂回归归一化指数函数交叉熵误差和均方误差的比较 五…