深入理解Flutter的GestureDetector组件

2023-11-06

引言

上一篇文章 深入理解Flutter的Listener组件 介绍了触控事件的监听原理,让我们对Flutter中触摸事件的传递过程有了进一步的认识。

今天我们学习一下手势识别组件GestureDetector的原理。GestureDetector的内部实现使用的是Listener组件,如果对Listener还不太熟悉,可以先了解一下Listener的原理。

源码解析

一、GestureDetector是Listener的封装

GestureDector是一个无状态组件,它的build方法如下所示。

class GestureDetector extends StatelessWidget {
    ...省略
    
    Widget build(BuildContext context) {
        ...省略return RawGestureDetector(
          gestures: gestures,
          behavior: behavior,
          excludeFromSemantics: excludeFromSemantics,
          child: child,
        );
    }
}

build方法直接返回了RawGestureDetector组件,说明GestureDetector是由子组件RawGestureDetector构成的。而RawGestureDetector是一个有状态组件,它的Statebuild方法如下所示。

class RawGestureDetector extends StatefulWidget {
  @override
  RawGestureDetectorState createState() => RawGestureDetectorState();
}

class RawGestureDetectorState extends State<RawGestureDetector> {
    ...省略
    
    @override
    Widget build(BuildContext context) {
        Widget result = Listener(
          onPointerDown: _handlePointerDown,
          behavior: widget.behavior ?? _defaultBehavior,
          child: widget.child,
        );if (!widget.excludeFromSemantics)
          result = _GestureSemantics(owner: this, child: result);
        return result;
    }
}

build方法里面返回了Listener组件,这也证明了上面的结论:

GestureDetector的内部实现使用的是Listener组件。

二、GestureDetector的实现原理

相比于ListenerGestureDetector有自己的属性,如onDoubleTaponLongPressonHorizontalDragStartonVerticalDragStart等。

其实说到底,这些属性也是由ListeneronPointerDownonPointerMoveonPointerUp这三个属性封装而成的。

重新看一下RawGestureDetectorStatebuild方法。

@override
Widget build(BuildContext context) {
    Widget result = Listener(
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child,
    );if (!widget.excludeFromSemantics)
      result = _GestureSemantics(owner: this, child: result);
    return result;
}

Listener组件的child属性是由GestureDector传递进来的,也就是说GestureDector自上而下的Widget构成如下图所示。

从之前对Listener组件的分析可知,Listener中的Child的触摸事件由onPointerDownonPointerMoveonPointerUp等属性值决定。

这里Listener属性值为_handlePointerDown,它是一个方法。

void _handlePointerDown(PointerDownEvent event) {
    assert(_recognizers != null);for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
}

该方法遍历了_recognizers里面的值(值类型为GestureRecognizer),_recognizers又是在_syncAll方法中赋值的。

void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
    _recognizers = <Type, GestureRecognizer>{};for (Type type in gestures.keys) {
      _recognizers[type] = oldRecognizers[type] ?? gestures[type].constructor(); //重要方法
      gestures[type].initializer(_recognizers[type]); //重要方法
    }
    for (Type type in oldRecognizers.keys) {
      if (!_recognizers.containsKey(type))
        oldRecognizers[type].dispose();
    }
}

_syncAll方法会将原有的_recognizers保存下来,然后遍历参数中的gestures,若原有的_recognizers有该手势类型对象,则使用,否则调用gestures[type]constructor方法。然后继续调用gestures[type]initializer方法。记住constructorinitializer这两个方法,后面的分析需要用到。

_syncAll方法在两处地方被调用,分别是initStatedidUpdateWidget方法。

@override
voidinitState() {
    super.initState();
    _syncAll(widget.gestures);
}

@override
void didUpdateWidget(RawGestureDetector oldWidget) {
    super.didUpdateWidget(oldWidget);
    _syncAll(widget.gestures);
}

组件初始化会调用initState方法,并传递了widgetgestures属性,这里的widget是指RawGestureDetector组件。

让我们再回过头来看GestureDectorbuild方法,如下所示。

@override
Widget build(BuildContext context) {
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};if (
      onTapDown != null ||
      onTapUp != null ||
      onTap != null ||
      onTapCancel != null ||
      onSecondaryTapDown != null ||
      onSecondaryTapUp != null ||
      onSecondaryTapCancel != null
    ) {
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
            ..onTapCancel = onTapCancel
            ..onSecondaryTapDown = onSecondaryTapDown
            ..onSecondaryTapUp = onSecondaryTapUp
            ..onSecondaryTapCancel = onSecondaryTapCancel;
        },
      );
    }

    if (onDoubleTap != null) {
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTap = onDoubleTap;
        },
      );
    }
    if (onLongPress != null ||
        onLongPressUp != null ||
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
        onLongPressEnd != null) {
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
        (LongPressGestureRecognizer instance) {
          instance
            ..onLongPress = onLongPress
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressEnd =onLongPressEnd
            ..onLongPressUp = onLongPressUp;
        },
      );
    }
    ...省略
    return RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
      child: child,
    );
}

首先初始化了gestures,并且对于每一种手势族都定义了一种类型。

1、TapGestureRecognizer手势族里面就包含了onTapDownonTapUponTaponTapCancelonSecondaryTapDownonSecondaryTapUponSecondaryTapCancel事件。

2、DoubleTapGestureRecognizer手势族里面就包含了onDoubleTap事件。

3、LongPressGestureRecognizer手势族里面就包含了onLongPressonLongPressStartonLongPressMoveUpdateonLongPressEndonLongPressUp事件。

gestures中的每一个值都是GestureRecognizerFactory类型。通过GestureRecognizerFactoryWithHandlers的构造方法,分别给GestureRecognizerFactoryconstructor、initializer方法进行初始化。

还记得RawGestureDetector组件的_syncAll中提到的constructor、initializer方法么?所以结合起来看,我们得出了如下结论:

GestureDetector的多种手势属性,都有其所属的手势族(GestureRecognizerFactory对象)。这些属性会通过手势族的initializer方法保存起来。

三、举个例子

GestureDetector(
    child: ConstrainedBox(
      constraints: BoxConstraints.tight(Size(300, 150)),
      child: Container(
        color: Colors.blue,
        child: Center(
          child: Text('click me'),
        ),
      ),
    ),
    onTapDown: (TapDownDetails details) {
      print("onTap down");
    },
    onTapUp: (TapUpDetails details) {
      print("onTap up");
    },
),

运行上面的代码后,展示如下。

GestureDector本质上也是 Listener,所以当我们点击了 click me文案后,需要执行命中测试,命中测试列表如下所示: RenderParagraph-> RenderPositionedBox-> RenderDecoratedBox-> RenderConstrainedBox-> RenderPointerListener

根据命中测试列表,从上而下执行每一个对象的handleEvent方法。Listener对应的RenderObject就是RenderPointerListener,而RenderPointerListenerhandleEvent方法如下。

@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
    assert(debugHandleEvent(event, entry));if (onPointerDown != null && event is PointerDownEvent)
      return onPointerDown(event);
    if (onPointerMove != null && event is PointerMoveEvent)
      return onPointerMove(event);
    if (onPointerUp != null && event is PointerUpEvent)
      return onPointerUp(event);
    if (onPointerCancel != null && event is PointerCancelEvent)
      return onPointerCancel(event);
    if (onPointerSignal != null && event is PointerSignalEvent)
      return onPointerSignal(event);
 }

这里的onPointerDownonPointerMoveonPointerUponPointerCancelonPointerSignal属性和Listener中是一一对应的。

当点击click me文案时,由于onPointerDown!=null && event is PointerDownEvent为true,从而执行了Listener中的onPointerDown方法,也就是RawGestureDetector组件的_handlePointerDown方法。

void _handlePointerDown(PointerDownEvent event) {
    assert(_recognizers != null);for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
}

_recognizers.values值遍历的结果我们上面分析过了,这里遍历的结果是每次都会去执行GestureRecognizer对象的addPointer方法。

void addPointer(PointerDownEvent event) {
    _pointerToKind[event.pointer] = event.kind;if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
}

首先通过了isPointerAllowed方法判断PointerDownEvent手势事件是否被GestureRecognizer对象所接受,一般每一个GestureRecognizer对象都会重写isPointerAllowed方法。

对于上面的例子,这里的GestureRecognizer对象就是TapGestureRecognizer,它的addAllowedPointer方法如下所示。

@override
void addAllowedPointer(PointerDownEvent event) {
    super.addAllowedPointer(event);
    _initialButtons = event.buttons;
}

这里直接调用了父类的addAllowedPointer方法。

@override
 void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer, event.transform);if (state == GestureRecognizerState.ready) {
      state = GestureRecognizerState.possible;
      primaryPointer = event.pointer;
      initialPosition = OffsetPair(local: event.localPosition, global: event.position);
      if (deadline != null)
        _timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));
    }
}

然后是startTrackingPointer方法。

@protected
void startTrackingPointer(int pointer, [Matrix4 transform]) {
    GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
    _trackedPointers.add(pointer);
    assert(!_entries.containsValue(pointer));
    _entries[pointer] = _addPointerToArena(pointer);
}

1、第一步通过GestureBinding.instance.pointerRouter调用了addRoute方法,参数为PointerDownEvent事件的唯一值(pointer)、handleEvent对象(由GestureRecognizer的子类实现)、PointerDownEvent事件坐标系(transform)。

注意:这里的GestureBinding.instance返回的是GestureBinding的对象,它是一个单例类,作用是管理手势事件生命周期与手势冲突。

void addRoute(int pointer, PointerRoute route, [Matrix4 transform]) {
    final LinkedHashSet<_RouteEntry> routes = _routeMap.putIfAbsent(pointer, () => LinkedHashSet<_RouteEntry>());
    assert(!routes.any(_RouteEntry.isRoutePredicate(route)));
    routes.add(_RouteEntry(route: route, transform: transform));
}

addRoute方法将在_routeMap中寻找pointer对应的LinkedHashSet,不存在则新建一个,然后创建一个_RouteEntry对象,并将routetransform传递过去。

2、第二步调用了_addPointerToArena方法。

GestureArenaEntry _addPointerToArena(int pointer) {if (_team != null)
      return _team.add(pointer, this);
    return GestureBinding.instance.gestureArena.add(pointer, this);
}

_addPointerToArena方法中,也通过GestureBinding.instance.gestureArena调用了add方法,参数为PointerDownEvent事件的唯一值(pointer)、GestureRecognizer对象(具体子类)。

还记得我们点击click me文案时,上面提到的命中测试列表么?其实上面只是列出了一部分,在RenderPointerListener的最后还有WidgetsFlutterBinding。所以应该是这样的: RenderParagraph->RenderPositionedBox->RenderDecoratedBox->RenderConstrainedBox->RenderPointerListener->...->WidgetsFlutterBinding

所以在命中测试列表最后一步,执行的是WidgetsFlutterBindinghandleEvent方法,这一步很重要,我们来看一下。

@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);if (event is PointerDownEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
}

方法中参数event是一个PointerEvent对象,由PointerDownEvent、一系列PointerMoveEventPointerUpEvent事件组成,对于每一个PointerEvent事件,都会执行pointerRouterroute方法。这里的pointerRouter对象就是GestureBinding.instance.pointerRouter对象。

void route(PointerEvent event) {
    final LinkedHashSet<_RouteEntry> routes = _routeMap[event.pointer];
    final List<_RouteEntry> globalRoutes = List<_RouteEntry>.from(_globalRoutes);if (routes != null) {
      for (_RouteEntry entry in List<_RouteEntry>.from(routes)) {
        if (routes.any(_RouteEntry.isRoutePredicate(entry.route)))
          _dispatch(event, entry);
      }
    }
    for (_RouteEntry entry in globalRoutes) {
      if (_globalRoutes.any(_RouteEntry.isRoutePredicate(entry.route)))
        _dispatch(event, entry);
    }
}

route方法会从_routeMap中取出该触摸事件,并执行_dispatch将该事件分发下去。这里_routeMap对应的数据,在上面的startTrackingPointer方法中已分析过。

void _dispatch(PointerEvent event, _RouteEntry entry) {
    try {
      event = event.transformed(entry.transform);
      entry.route(event);
    } catch (exception, stack) {
      ...省略
    }
}

event.transfromed方法会对当前触摸事件对象进行坐标系转换。一般来说,非特殊情况下,这里转换前和转换后是同一个触摸事件对象。然后调用了_RouteEntryroute方法,将该触摸事件对象传递过去。

这里_RouteEntryroute方法就是上面的startTrackingPointer方法中初始化的,并且它指向的是每一个GestureRecognizer子类的handleEvent方法。

拿上面的例子来说,就是TapGestureRecognizerhandleEvent方法,由于TapGestureRecognizer没有该方法,我们从它父类PrimaryPointerGestureRecognizer可以找到。

@override
  void handleEvent(PointerEvent event) {
    assert(state != GestureRecognizerState.ready);if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
      final bool isPreAcceptSlopPastTolerance =
          !_gestureAccepted &&
          preAcceptSlopTolerance != null &&
          _getGlobalDistance(event) > preAcceptSlopTolerance;
      final bool isPostAcceptSlopPastTolerance =
          _gestureAccepted &&
          postAcceptSlopTolerance != null &&
          _getGlobalDistance(event) > postAcceptSlopTolerance;

      if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
        resolve(GestureDisposition.rejected);
        stopTrackingPointer(primaryPointer);
      } else {
        handlePrimaryPointer(event);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
}

这里最重要的方法是执行了resolve方法。

void resolve(GestureDisposition disposition) {
    final List<GestureArenaEntry>localEntries = List<GestureArenaEntry>.from(_entries.values);
    _entries.clear();
    for (GestureArenaEntry entry in localEntries)
      entry.resolve(disposition);
}

这里的_entries也是在上面的startTrackingPointer方法分析过的,所以上述方法会遍历_entries的每一个GestureArenaEntry对象(对应着每一个GestureRecognizer对象),并执行它的resolved方法,然后再调用GestureArenaManager_resolve方法。

void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
    final _GestureArena state = _arenas[pointer];if (state == null)
      return; // This arena has already resolved.
    assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
    assert(state.members.contains(member));
    if (disposition == GestureDisposition.rejected) {
      state.members.remove(member);
      member.rejectGesture(pointer);
      if (!state.isOpen)
        _tryToResolveArena(pointer, state);
    } else {
      assert(disposition == GestureDisposition.accepted);
      if (state.isOpen) {
        state.eagerWinner ??= member;
      } else {
        assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
        _resolveInFavorOf(pointer, state, member);
      }
    }
}

该方法主要的作用是处理手势冲突,通过手势冲突处理后,能成功执行的手势事件会调用_resolveInFavorOf方法。

void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
    assert(state == _arenas[pointer]);
    assert(state != null);
    assert(state.eagerWinner == null || state.eagerWinner == member);
    assert(!state.isOpen);
    _arenas.remove(pointer);for (GestureArenaMember rejectedMember in state.members) {
      if (rejectedMember != member)
        rejectedMember.rejectGesture(pointer);
    }
    member.acceptGesture(pointer);
}

然后再执行GestureArenaMemberacceptGesture方法。该方法是抽象方法,具体的实现是在其子类中。我们看一下TapGestureRecognizer的实现。

@override
void acceptGesture(int pointer) {
    super.acceptGesture(pointer);if (pointer == primaryPointer) {
      _checkDown(pointer);
      _wonArenaForPrimaryPointer = true;
      _checkUp();
    }
}

这里_checkDown方法主要处理按下事件,_checkUp主要处理抬起事件。这也说明了TapGestureRecognizer手势族只处理手势的按下与抬起。其他事件由其他手势族进行处理。

_checkDown_checkUp方法后面还会调用诸多方法,最终会调用onTapDownonTapUp方法,这里的方法链路就不再分析了,有兴趣的同学可以去看看源码。

总结

本文以TapGestureRecognizer作为例子,分析了GestureDector组件的触摸事件的原理。GestureDector组件的底层是通过Listener实现的,并且与Listener一样也需要对触摸事件进行命中测试。GestureDector组件的各个属性方法在得到响应之前,会通过WidgetsFlutterBinding做事件分发,并通过手势冲突竞技场做手势冲突管理,最终通过的手势事件才会分发到各个GestureRecognizer对象的handleEvent方法进行处理,结果才会是各个GestureRecognizer对象的属性方法得到响应。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解Flutter的GestureDetector组件 的相关文章

  • 合并 dex 程序类型已存在时出错:android.support.v4.os.ResultReceiver$MyResultReceiver

    合并dex时出错 以下是依赖项 ext anko version 0 10 5 support lib 1 0 0 alpha1 room lib 1 1 0 dependencies implementation org jetbrain
  • java.net.ProtocolException:流意外结束

    我面临一个奇怪的问题 并且无法调试它 我已经实现了上传数据流的逻辑 并使用 Volley 来实现相同的功能 我在HurlStack addBodyIfExistsapi 以便可以处理 application octet stream 类型的
  • 从 HttpClient 3 转换为 4

    我已经成功地对所有内容进行了更改 但以下内容除外 HttpClient client HttpPost method client new DefaultHttpClient method new HttpPost url InputStr
  • 垂直从上到下线手势检测器

    我用的是 手势工具 注意到对于垂直从上到下的线无法检测 因为我在代码中使用生成的手势文件 如下所示 但无法检测垂直从上到下的线手势检测 import java util ArrayList import android app Activi
  • Android:如果任务管理器终止,则重新调用应用程序

    如果应用程序线程被任务管理器杀死 则应用程序线程将关闭 需要重新调用应用程序 就像它被其他应用程序或任务管理器杀死一样 任何想法 您必须使用 START STICKY 命令运行后台服务 只需扩展 Service 并重写 onCommand
  • 更改 JComboBox 中滚动条的大小

    有谁知道如何手动更改 jComboBox 中的滚动条大小 我已经尝试了一大堆东西 但没有任何效果 好吧 我明白了 您可以实现 PopUpMenuListener 并使用它 public void popupMenuWillBecomeVis
  • 是否有最新的 Facebook Java SDK? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 好像没找到最近更新的 如果没有 是否有一个好的 Java 库来执行与 Facebook 的 API 交
  • phonegap html5 android 同步文件系统 IO

    如何使用 PhoneGaps 文件系统 API 同步读写文件 有可用的同步包装器吗 无法通过提供的 api 同步访问文件 从phonegap的实现方式猜测 我怀疑您是否可以编写一个插件来同步执行此操作
  • 如何使用 MotionLayout 调整 TextView 的大小

    我正在尝试创建一个CollapsingToolbar动画使用MotionLayout 我已经成功地将所有内容设置为动画 使其表现得像CollapsingToolbar具有高度的灵活性 这意味着我可以轻松创建很棒的动画 而无需编写大量代码 我
  • Jetty Plugin 9启动不喜欢icu4j-2.6.1.jar

    我对 mortbay 的 Maven jetty 插件 6 有相同的配置
  • JAXB 编组器无参数默认构造函数

    我想从 java 库中编组一个 java 对象 当使用 JAXB marschaller 编组 java 对象时 我遇到了一个问题 A 类没有无参数默认构造函数 我使用Java Decompiler来检查类的实现 它是这样的 public
  • 如何向开关对象添加/更改波纹效果

    下面是我自定义的开关 红圈是默认的波纹效果 我发现设置一个波纹可绘制作为开关的背景 控制波纹的颜色
  • Java 中处理异步响应的设计模式

    我读过类似问答的答案 如何在 JAVA 中创建异步 HTTP 请求 https stackoverflow com questions 3142915 how do you create an asynchronous http reque
  • 从浏览器访问本地文件?

    您好 我想从浏览器访问系统的本地文件 由于涉及大量安全检查 是否可以通过某种方式实现这一目标 或使用 ActiveX 或 Java Applet 的任何其他工作环境 请帮帮我 要通过浏览器访问本地文件 您可以使用签名的 Java Apple
  • 如何检测日期选择器对话框的取消单击?

    我正在使用以下 日期选择器的示例 http developer android com guide tutorials views hello datepicker html http developer android com guide
  • Java 中的微分方程

    我正在尝试用java创建一个简单的SIR流行病模型模拟程序 基本上 SIR 由三个微分方程组定义 S t l t S t I t l t S t g t I t R t g t I t S 易感人群 I 感染人群 R 康复人群 l t c
  • Android Volley - 发布请求 - 无法在线工作

    我试图通过 Volley 发出 POST 请求 它在我的本地主机中工作得很好 但是当我将它移动到网络服务器时 响应为空 Java代码 RequestQueue queue Volley newRequestQueue this String
  • 如何减少 Android 中浮动 editText 提示和 editText 框之间的空间?

    我有一个带有浮动提示的 EditText 但我想知道如何减少浮动提示和 EditText 框之间的空间 现在我的用户界面看起来像https i stack imgur com ltfra jpg https i stack imgur co
  • 如何使用 Jest 从 ElasticSearch 获取索引列表

    我正在尝试使用 Jest 检索索引列表 但我只得到 Stats statistics new Stats Builder build result client execute statistics 如何从结果中检索索引列表 除了统计之外
  • 使用 AmazonSNSClient 发送短信时的授权

    aws 官方文档如何发送短信 http docs aws amazon com sns latest dg sms publish to phone html使用 java 中的 aws SDK 非常简单 但是 当发送如底部示例所示的消息时

随机推荐

  • C++报错不允许使用不完整的类型

    include pch h include
  • #define _CRT_SECURE_NO_WARNINGS 1问题

    文章目录 前言 一 为什么用了scanf strcyp strlen strcat等函数会报错 二 操作步骤 1 下载notepad 2 修改 总结 前言 当我们的代码出现这样的报错时 不要慌张 应该是你用了scanf strcyp str
  • Merkle Tree(默克尔树)算法解析

    Merkle Tree 默克尔树 算法解析 1 Merkle Tree概念 Merkle Tree 通常也被称作Hash Tree 顾名思义 就是存储hash值的一棵树 Merkle树的叶子是数据块 例如 文件或者文件的集合 的hash值
  • 记一次jvm项目调优

    问题描述 运维一直说 fy core 项目这个项目每天都会发生多次full gc full gc 会停顿600ms左右 理论上生产环境不允许发生full gc 所以决定把full gc都优化掉 以下是该grafana对应的资源表现 可以看出
  • react生命周期componentDidMount中设置setState在esIint报错

    react生命周期componentDidMount中设置setState在esIint报错 项目背景 由于需要在页面渲染出来的时 对state中的数据进行初始化并需要出发render重绘 componentDidMount let sel
  • 5G“邂逅”云计算,运营商云网融合大势所趋

    近几年 国内运营商纷纷发力布局云计算市场 力求在云领域打造出自己的地盘 近期 中国电信天翼云与中科曙光战略合作 双方将共同探索在 5G 云 领域的建设 去年8月中国移动在苏州成立中国移动云能力中心 中国联动也于去年年底发布沃云云计算战略 而
  • navicat使用触发器在插入记录修改某个字段的值

    最近项目遇到了一个问题 查用户的某些字段有NULL会报错 所以想在注册的时候用的触发器提前设置一个默认值 避免空指针 先在本地建一个测试表t 1 就给三个基本字段 然后右击表选择设计表找到 触发器 开始编辑 现在的写法是正确的 我之前是这样
  • B站马士兵python入门基础版详细笔记(4)

    前言 这篇文章是B站学习python入门基础班的视频的第四章内容 主要讲述的是if else语句 但是不包括循环结构 循环结构在下一章笔记中有记叙 一 顺序结构 什么是顺序结构呢 比如说 把大象装冰箱需要分几步 print 程序开始 pri
  • 数模比赛提分tips

    1 对于模型的建立和求解 这一部分是文章的重点 要特别突出你的创造性的工作 在这部分写作需要注意的事项有 一定要有分析 而且分析应在所建立模型的前面 一定要有明确的模型 不要让别人在你的文章中去找你的模型 关系式一定要明确 思路要清晰 易读
  • 指针进阶(三)

    指针进阶 三 指针习题组 01 int main int a 5 1 2 3 4 5 int ptr int a 1 printf d d a 1 ptr 1 return 0 运行结果 原因 这里a是数组名 存放的是数组的首地址 a是整个
  • pip install tensorflow报错ERROR: Could not find a version that satisfies the requirement tensorflow (f

    这里写目录标题 报错内容 解决方法 其他方法 原因分析 报错内容 pip3 install tensorflow 输入上述命令安装tensorflow后出现下面的报错 ERROR Could not find a version that
  • chrome浏览器被hao123劫持如何解决?

    那天上班 打开电脑点开chrome浏览器 首页出现的是我熟悉的谷歌搜索页面 可是很快右边又打开了一个页面 123 hao234 com 这个流氓网站就这个这么不请自来了 而且之后我用尽了网上各种办法都弄不走 各种无效方法 1 绑定自己的主页
  • 背包九讲--混合背包、分组背包、资源分配背包、背包方案总数

    混合背包 混合背包 问题描述 一个旅行者有一个最多能用V公斤的背包 现在有n件物品 它们的重量分别是W1 W2 Wn 它们的价值分别为C1 C2 Cn 有的物品只可以取一次 01背包 有的物品可以取无限次 完全背包 有的物品可以取的次数有一
  • hive基础(二) hive操作大全

    目录 注 只是为了以后忘了 好翻 做个总结 一 hive 操作 1 hive e 2 hive f 3 查看在hive中输入的所有历史命令 4 hive运行日志修改 二 hive参数配置 三 hive数据类型 基本数据类型 集合数据类型 1
  • PHP与MySQL程序设计 学习笔记 第八章 错误和异常处理

    error reporting函数确定报告的敏感级别 共有16个不同级别 这些级别的任何组合都是有效的 error reporting函数使用 字符表示逻辑操作符NOT error reporting E ALL E STRICT 希望报告
  • python两列表对应元素求和

    一种方法是循环 但是有简便方法 用numpy import numpy as np list1 1 2 3 4 list2 1 2 3 4 a array np array list1 b array np array list2 c ar
  • matlab安装好 启动总是闪退_win10系统启动matlab出现闪退的处理技巧

    电脑操作系统在使用的时候经常会被一些问题所困扰 例如很多用户都遇见过win10系统启动matlab出现闪退的问题 大部分用户如果第一次碰到win10系统启动matlab出现闪退的现象 因此大伙都会不知所措 怎么才可以完善的治理win10系统
  • 设计模式之状态模式(思想)

    设置模式之状态模式 上图学过网络的同学应该都比较清楚吧 这是一张TCP状态转换图 只要理解上图 那么对状态模式也就很容易理解啦 状态模式的意图 允许一个对象在其内部状态改变时改变它的行为 简单的说就是 一个人他的外表没有改变 但是他内在的心
  • batch-命令的学习

    batch命令的扩充变量语法 所谓扩充变量语法 是指对已有参数 环境变量或其他变量的引用 的再处理 扩展 对比与java语言 可以理解为 对传入参数的格式化 扩充变量只能针对于传入变量的自身属性 自身的引用 值 变量代表的文件位置信息 进行
  • 深入理解Flutter的GestureDetector组件

    引言 上一篇文章 深入理解Flutter的Listener组件 介绍了触控事件的监听原理 让我们对Flutter中触摸事件的传递过程有了进一步的认识 今天我们学习一下手势识别组件GestureDetector的原理 GestureDetec