Flutter手势冲突与手势竞争

手势冲突的原因

当多个手势同时出现在同一控件上时,就会发生手势冲突。这可能会导致系统无法准确识别用户的意图,从而导致错误的操作。iOS 上的 UIScrollView 是手势冲突的主要来源,因为它可以响应滚动、缩放和点击等各种手势。当 Flutter 视图与 UIScrollView 同时存在时,很容易发生手势冲突。

常见的冲突场景

最常见的场景是将 Flutter 视图嵌套在 UIScrollView 中。在这种情况下,UIScrollView 的滚动手势和 Flutter 视图的手势很容易发生冲突。例如,当用户在 Flutter 视图上拖动时,UIScrollView 可能将其误认为是滚动操作,从而导致 Flutter 视图无法响应拖动手势。

解决方案:手势竞技场

解决手势冲突的最有效方法之一是使用 Flutter 的手势竞技场。这是一个负责协调手势的系统,可确保同一时间只有一个手势被识别。要使用手势竞技场,需要在 Flutter 视图的最顶层添加一个 GestureRecognizer Widget,并将其作为竞技场的子控件。

手势竞技场的使用

以下是使用手势竞技场的示例代码:

通过使用这种方法,Flutter 视图的手势和 UIScrollView 的手势可以同时响应,互不干扰,从而完美解决 iOS 上的 Flutter 页面手势冲突问题。

常见问题解答

1. 为什么手势冲突在 iOS 上很常见?

因为 iOS 上的 UIScrollView 可以响应多种手势,并且它经常与 Flutter 视图一起使用,这可能导致手势冲突。

2. 手势竞技场是如何工作的?

手势竞技场是一个负责协调手势的系统,可确保同一时间只有一个手势被识别。它通过让手势识别器注册自己并协调它们的活动来实现此目的。

3. 如何在 Flutter 中使用手势竞技场?

在 Flutter 视图的最顶层添加一个 GestureRecognizer Widget,并将其作为竞技场的子控件。

4. 除了手势竞技场,还有其他解决手势冲突的方法吗?

是的,还有其他方法,例如使用 GestureDetector Widget 或重写控件的手势识别逻辑。

5. 为什么解决手势冲突很重要?

解决手势冲突很重要,因为它可以防止错误的操作,并确保用户获得流畅、无缝的体验。

参考链接


Flutter:使用Overlay展示浮动的Widget

想象一下:你编写出的迷人表单页面

你把它发给产品经理,他看了一眼说:“我一定要完整的输入国家名称吗,当我输入文字时难道你就不能给我展示些建议吗?”,你想了想:“好吧,他是对的”,因此,你决定开发一个‘自动补全‘的’预先输入’功能,随便你怎么称呼它:一个文本展示框 TextField ,当用户输入文字的时候展示一些建议选项。开始工作了..你知道怎么拿到建议数据,你知道怎么写逻辑,你知道所有要做的事情..除了不知道怎么将建议选项浮动展示在 Widgets 之上。

你想了想:打算重新设计代码结构,为了实现悬浮效果,决定将整个页面包装进一个 Stack 组件中,你需要准确的计算每个 Widget 显示的位置,非常侵入性、必须要严谨、容易出错,并且直觉告诉你这么做可能是错误的,有其他的实现方式吗?

方案就是:你可以使用 Flutter 已经提供好的 StackOverlay

在这片文章中,我将会介绍如何使用 Overlay ,来创建悬浮在其他 Widget 之上的 Widgets,并且并不需要重构你的整个页面。

你可以使用 Overlay 来展示自动匹配的建议选项,小提示,或着基本上所有的浮动的东西。

Overlay是什么?

官方文档这样定义

Stack of entries that can be managed independently.    // 一个可以独立管理的Stack子类

Overlays let independent child widgets “float” visual elements on top of other widgets by inserting them into the overlay’s Stack.    // 通过将可独立管理的子节点widgets加入到overlay的栈中,Overlays可以将这些widgets浮动展示到显现的elements节点的顶部

这看起来就是我们正在寻找的内容,当我们创建 MaterialApp 的时候,它会自动创建一个NavigatorNavigator 则又会创建一个 Overlay : 一个 Navigator 用来管理所展示的 Views 视图的 Stack 组件。

接下来,让我们一起看看怎么使用 Overlay 来解决我们的问题吧。

注意:这篇文章的核心是介绍如何显示浮动 Widgets,因此不会涉及太多如何实现自动补全文本输入框 TextField 的细节,如果你对一个编写良好、可高度自定义的自动补全 Widget 感兴趣的话,可以参考 flutter_typeahead

初始代码

我们以最初的代码开始吧:

*一个简单页面,包含了三个文本输入框:country、city、address。

然后我们就以 country 文本输入框为例吧,将它封装成一个我们自己的 StatefulWidget,命名为 CountriesField

接下来我们将要做的是,每次当选中文本输入框获取焦点 Focus 的时候,将一个浮动的 List 列表展示出来。当失去焦点 Focus 的时候,再将它隐藏起来,当然你可以按照自己需求来决定如何实现,你可能需要在用户输入了一些文字后展示它,或者当用户点击 Enter 按钮的时候再隐藏。无论怎样,让我们先看看如何展示这个悬浮的 Widget吧:

  • 我们给 TextFormField 绑定了一个 FocusNode,并且在 initState 里面给 FocusNode 添加了一个监听事件,通过监听事件来获取什么时候获得/失去焦点。
  • 每次当我们获取焦点 (_focusNode.hasFocus == true) 的时候, 我们通过 _createOverlayEntry 创建一个OverlayEntry 实例对象,然后通过使用 Overlay.of(context).insert,将它插入到最邻近的 Overlay Widget 中去。
  • 每次当我们失去焦点 (_focusNode.hasFocus == false) 的时候,我们通过使用 _overlayEntry.remove 来移除刚才添加的 Overlay 实例。
  • _createOverlayEntry 通过使用 context.findRenderObject 来获取我们的 Widget 所在的渲染对象 RenderBox ,渲染对象里包含位置 Position、大小 Size、和一些其他关于渲染的信息,有了这些信息能够帮助我们计算在哪里展示我们的悬浮列表。
  • _createOverlayEntry 通过渲染信息来获取当前 Widget 的大小,也可以使用 renderBox.localToGlobal 来获取当前 Widget 在屏幕上的坐标。我们将 localToGlobal 设置为 Offset.zero 这意味着我们将在渲染对象中使用(0,0)坐标,并且将他们转换为屏幕上相对应的坐标。
  • 接着我们创建了 OverlayEntry,这时一个用来将 Widgets 展示到 Overlay 中的 Widget
  • 当前创建的 OverlayEntry 的是一个 Positioned Widget。请牢记 Positioned Widgets 只能被插入到 Stack 中,当然 Overlay 其实也是一个 Stack
  • 我们设置 Positioned Widget 的坐标,给它和 TextField 相同的 X 轴坐标,相同的宽度,相同的 Y 轴坐标,当然为了不遮挡到 TextField,在底部进行了一些偏移。
  • Positioned 内部,我们设置了一个展示建议选项的 ListView(里面默写了例子中的一些国家)。注意到我把所有的内容都包在了 Material 中,关于这样写有两个原因: Overlay 默认不包含 Material Widget,并且很多 Widgets 如果没有有 Material 祖先节点的话不能展示,除此之外 Material 还提供了 elevation 属性,可以让我们给 Widget 设置阴影效果,看起来就像真正浮在上面一样。

以上,我们的建议选择项可以浮在所有 Widget 之上了!

彩蛋:跟随 Widget 滑动!

在我们离开之前,让我们在多学喜一点吧!假如我们的页面是可以滚动,我们可能注意到如下现象:

建议选择列表固定在了屏幕上!在某些场景下你可能的确需要固定的,但是在当前场景中,我们不想要它固定,我们想要它跟随我们的 TextField 一起滚动!

关键词滚动,Flutter给我们提供了两个widget:CompositedTransformFollowerCompositedTransformTarget,简单介绍就是,如果我们关联起一个 follower 和一个 target ,那么无论 target 滚动到哪里,这个 follower 将跟随它一起滚动!为了关联起一个 follower 和一个target,我们需要给他们设置相同的LayerLink.

因此我们需要将建议选择列表用 CompositedTransformFollower 包起来,将 TextFieldCompositedTransformTarget 包起来。然后我们将他们使用想用的 LayerLink 关联起来,这样就可以是建议选择列表跟随 TextField 一起滑动了:

  • 我们将 Material WidgetCompositedTransformFollower 包裹进 OverlayEntry 中,将TextFormField 包裹进 CompositedTransformTarget 中。
  • 我们使用相同的 LayerLink 示例,来关联 followertarget,这样 followertarget 将会在相同的坐标系中,高效的跟随 target 而动。
  • Positioned Widget 中移除了 topleft 属性,因为默认 followertarget 有相同的坐标,因此不在需要。然而我们保留了 width 属性,因为如果不设置的话,follower 将会无限的延伸。
  • 为了不遮挡 TextFormField ,我们给 CompositedTransformFollower 设置了一个offset(和之前一样的原因)。
  • 最后,我们将 showWhenUnlinked 属性设置为 false,当 TextFormField 在屏幕上不可见时,用来隐藏OverlayEntry(比如我们滑动出屏幕底部很远的时候)。

经过这些操作,我们的 OverlayEntry 现在可以跟随我们的 TextField 一起滚动啦!

重要提示:CompositedTransformFollower 仍然有一点问题,当 target 不可见时,即使 follower 已经从屏幕上隐藏了,这个 follower 仍然会响应点击事件,我已经给 Flutter Team 提了 issue ,此 issue 已经关闭,标记为解决。

Overlay 是一个强大的 Widget ,它给我们提供了一个简单易用的的用来展示浮动 Widget 的的 Stack 组件。

参考链接


Flutter - 左右侧滑菜单:drawer和endDrawer

侧滑菜单可以从左面滑出,也可以从右面滑出。在Scaffold中有drawer和endDrawer两个参数,分别对应左边的菜单和右边的菜单。

参考链接


Android 15新特性,强制edge-to-edge全面屏体验

其实edge-to-edge全面屏体验并不是什么全新的功能,早在Android 15之前就已经支持了。

但是这个功能推出了很多年,仍然有大量的应用程序没有对全面屏体验进行适配。所以,在这次的Android 15更新中,Google终于下决心要强推这个功能,以让所有应用程序都能达到更好的体验。

需要说明的是,只有将App的targetSdkVersion指定到35或更高时,Android 15才会强制启用edge-to-edge功能。所以,如果你就是不想适配,那么只要不升级targetSdkVersion的版本就行了。

下面开始进入正题,首先跟大家介绍一下到底什么是edge-to-edge全面屏体验。

其实简单来讲,就是让App的界面延伸到手机屏幕的全部空间,这样可以带来更加沉浸式的用户体验。

事实上,绝大多数的App都没有将界面延伸到手机屏幕的全部空间,因此它们本可以提供更好的用户体验。

这里说的手机屏幕的全部空间具体指的是什么呢?我们看下面这张图就能快速了解了。

继续阅读Android 15新特性,强制edge-to-edge全面屏体验

Flutter监听软键盘的弹出和关闭获取键盘高度

通过 WidgetsBindingObserver 实现监听软键盘的弹出关闭


实现 WidgetsBindingObserver

注意:

如果使用 Scaffold 作为父组件,在使用 MediaQuery.viewInsetsOf(context).bottom 获取键盘高度如果无论如何都是 0

此时有两种方案:

1. 需设置父级 ScaffoldresizeToAvoidBottomInsetfalse

但是这样设置之后,会导致键盘弹出的时候,Scaffold 不会自动向上移动,导致输入范围被遮挡。

2. 使用 Scaffold 上层/同层的 BuildContext 作为参数传递给子 View ,然后传递给 MediaQuery 作为参数。

原始链接


[Powershell] 无需安装软件,通过命令行监控 CPU 温度

我们知道 Windows 电脑没有监控 CPU 温度的功能,如果想知道 CPU 温度需要安装软件,比如鲁大师。

那么是否可以不安装软件,就实现 CPU 的温度监控呢?

以管理员权限执行如下 PowerShell 脚本:

方法一:

方法二:

参考链接


macOS双频蓝牙鼠标失联问题

前置条件

  • MacBook Pro 2023-Apple M2 Pro (4能效核、8性能核、32GB内存、2TB磁盘)
  • macOS Sequoia 15.5 
  • Lenovo ThinkLife 静音鼠标无线蓝牙版 WLM210

问题现象

蓝牙鼠标正常配对使用,刚刚开始使用正常的。但是过一阵子不使用蓝牙鼠标,或者鼠标电源调整成关闭状态,或者拔掉电池,大概率连接不上。需要在电脑上手工删除蓝牙连接,然后重新配对。

问题定位

刚开始猜测是鼠标使用的 南孚 TENAVOLTS  锂电池 DC-DC 降压电路释放的信号干扰到了蓝牙通信协议或者电压纹波导致芯片工作异常,在更换为普通的 1.5V 非充电电池之后,问题依旧复现。

无意中点击蓝牙设备列表,发现重新配对之后 Lenovo ThinkLifeMAC 地址变化了。这说明两者进行配对的时候使用了动态协商出来的临时 MAC 地址,没有使用设备的真实 MAC 地址。这样诱发一个问题,那就是鼠标需要记住这个动态协商出来的 MAC 地址,然后用这个地址进行通信。这样就能解释为什么拔掉电池多等一会儿,让设备完全放电,再插上电池很容易复现这个问题。因为长时间断电之后,设备上记录的协商 MAC 地址丢失了。

继续阅读macOS双频蓝牙鼠标失联问题

在 .htaccess 中配置强制UTF-8字符集

网站配置了 robots.txt 来控制爬虫的行为,由于文件里面有中文注释,导致直接浏览器打开之后中文显示乱码。

对于 Apache 服务器,可以通过在 .htaccess 中配置文件的字符集格式解决显示乱码问题。

Set universal UTF-8 encoding:

Set UTF-8 encoding on select file types only:

Note: An .htaccess file located in a sub-directory overrides any duplicate rules from previous .htaccess files. For example, if you have a .htaccess file located in the root defining a 404 and 403 error page, and another .htaccess located in the “test” folder defining only a 404 error page, any files and folders in the “test” folder will use the 404 page defined in the "test" .htaccess file, and the 403 page defined in the root .htaccess file.

参考链接


Forcing UTF-8 encoding with .htaccess

ARG to Rescue: Reuse Variables in Multistage Dockerfile

概要

在参照使用 Ubuntu 22.04使用Podman部署OpenGrok的详细教程 进行 OpenGrok 部署的时候,其中的 Dockerfile 配置了两个独立的镜像来源。恰好这两个镜像来源都需要配置 PIP 国内镜像。于是思考如何在同一个 Dockerfile 存在两个镜像的场景下,如果共用一个变量。

研究了一阵,发现还是通过 ARG 变量实现上述想法。

具体说明参考下面的完整文章。

Introduction

The Docker ecosystem is rich with tools and best practices that streamline containerization. One of these practices is using multistage builds to create lean, efficient containers. However, as your Dockerfiles grow more complex, managing variables and maintaining readability can become a challenge. Enter the ARG instruction—your key to sharing variables across stages in a multistage Dockerfile. In this blog post, we’ll explore how ARG can simplify your Dockerfiles, enhance reusability, and maintain cleaner code.

Why Use Multistage Builds?

Multistage builds in Docker allow you to use multiple FROM statements in a single Dockerfile, creating separate stages that can be used to build a final image. This method is particularly useful for creating lightweight production images, as you can copy only the necessary artifacts from earlier stages. Here’s a quick example to illustrate:

In this simple example, the build stage compiles a Go application, and the production stage creates a minimal image containing only the compiled binary.

The Challenge: Sharing Variables Across Stages

While multistage builds are powerful, they can introduce a common challenge: variable reuse. Suppose you need to use a specific version of an application or a common path across multiple stages. Without a way to share variables, you might end up duplicating code, leading to maintenance headaches and potential errors.

Here’s where ARG (argument) comes into play. The ARG instruction allows you to define variables that can be used throughout your Dockerfile, even across different stages.

Introducing ARG: The Basics

The ARG instruction defines a variable that users can pass at build time to customize the build process. Unlike environment variables (ENV), which are persisted in the image, ARG variables are only available during the build process and do not become part of the final image.

Let’s start with a basic example:

In this example, BASE_IMAGE is an argument that can be overridden when building the Dockerfile. The default value is alpine:3.12, but you could specify a different base image at build time:

Sharing ARG Variables Across Stages

The real power of ARG comes into play with multistage builds. To share ARG variables between stages, you need to redefine the ARG in each stage. Let’s look at a more advanced example:

In this Dockerfile, we define GO_VERSION as an argument at the top. By repeating ARG GO_VERSION in each stage, we make the argument available for use. Notice how the build stage uses GO_VERSION to specify the Go image, and the production stage echoes the Go version used.

Advanced Usage: Combining ARG with Environment Variables

You might find it useful to combine ARG with ENV to set environment variables conditionally based on build arguments. This can further enhance your Dockerfile’s flexibility.

Following example demonstrates the use of ARG and ENV to have a generic Dockerfile for a Turborepo application:

Key Points on Environment Variables in Multistage Builds
  • Environment Variables (ENV):

    • Are specific to the stage where they are defined.
    • Do not persist across stages.
    • If you need an environment variable in multiple stages, you have to redefine it or pass it via ARG.
  • Build Arguments (ARG):

    • Are defined once and can be passed to any stage by redeclaring them.
    • Provide a way to share configuration details like versions or paths between stages.

Debugging ARG Variables

When working with ARG variables, you might run into issues where arguments aren’t passed correctly or variables aren’t set as expected. Here are some tips to help you debug:

  1. Check Build Logs: Use docker build with the --progress=plain flag to get more detailed logs that can help identify where arguments are being used or missed.

  2. Echo Variables: Add RUN echo statements to print the values of your ARG variables during the build process.

  3. Use Default Values: Define sensible default values for your ARG variables to ensure that your build doesn’t fail if arguments are not provided.

Best Practices for Using ARG

  • Define ARG Variables Early: Place ARG instructions at the top of your Dockerfile to make them accessible in all stages.
  • Use Descriptive Names: Choose meaningful names for your arguments to make the Dockerfile easier to understand and maintain.
  • Avoid Secrets in ARG: Never use ARG to pass sensitive data like passwords or API keys, as they can be exposed in the Docker image history.

Conclusion

Using ARG to share variables across stages in a multistage Dockerfile can significantly improve your Docker builds’ maintainability and flexibility. Whether you’re building lightweight production images or dynamically configuring your builds, ARG provides a powerful tool to streamline and enhance your Dockerfile.

参考链接


Android升级AGP、minSdkVersion 23后APK包体积变大问题

AGP 4.2 后,默认关闭了 so 文件的压缩。

AGP 7.1minSdkVersion>=23 后默认关闭了 dex 文件压缩,app bundle 打包不受影响

现象

升级到 AGP7 之后发现打包出来的 APK 变大不少,差不多增加了三分之一的大小。将 APK 拖入 Android Studio 中分析对比下之前的 APK 发现主要 so 文件增大、dex 文件未压缩。

so 文件压缩

AGP 3.6 之后默认关闭了 so 压缩,声明为 true 就行了。

minSdkVersion < 23 或 Android Gradle Plugin < 3.6.0 情况下,打包时 android:extractNativeLibs=true;

minSdkVersion >= 23 并且 Android Gradle Plugin >= 3.6.0 情况下,打包时 android:extractNativeLibs=false;

或者

DEX 文件压缩

参考链接