给 compose draw 绘制的非规则图形添加点击监听

news2024/11/24 5:56:44

前言

导言

在之前的两篇文章中,我们从实例出发,以实践的方式简单介绍了 compose 自定义绘制(如何自己绘制想要的控件)、为自定义绘制增加动画(让控件动起来)。

在这篇文章中,我们依然从实例出发,介绍怎么为自定义绘制图案添加触摸监听,特别是一些"奇形怪状"的异形图案应该怎么判断触摸坐标。

项目背景

在我的魔改车钥匙系列文章的最后一篇 《魔改车钥匙实现远程控车:(4)基于compose和经典蓝牙编写一个控制APP》 中,我们使用 compose 编写了一个简单的控制 APP,但是正如我在文章中说的,当前APP界面十分简陋,日后有时间我会再优化这个界面。

其实当时我对于界面已经有了想法,只是因为想要快速实现功能,所以没有去纠结 UI,现在正好闲下来了,可以去实现我预想的 UI 效果了。

我预想中的 UI 效果大致是一个"拟真"的遥控器界面,类似于这样:

s1.png

相信各位看到这种样式,第一反应就是使用基础 composable 实现不了吧?

没错,确实,所以我们又得使用自定义绘制了,但是问题来了,绘制简单,怎么监听点击事件呢?

这就是本文需要探讨的问题。

实现

基础知识

在开始探讨之前,我们需要知道一些基础知识。

在 compose 中是怎么监听触摸事件的(单击、双击、长按、滑动等等)?

注意:其实基于 声明式编程 的 compose 并不存在 监听 这一概念,常用的点击事件也是通过匿名函数回调的,但是这里为了方便叙述,我措辞使用的依然是 监听

在 compose 中,几乎所有 composable 的点击事件都是通过给 Modifier 添加 clickable 修饰来实现的。不过你也可以说,不对啊, Button 这一类 composable 不是给 Modifier 添加修饰啊,是它自己就提供了一个点击回调啊。

哈哈,其实如果你看过源码就会发现,即使是 Button 等等这些自带点击回调的 composable 最终都是靠给 Modifier 添加 clickable 来实现点击回调的。

例如:

Text(text = "我是一个可以点击的文字", Modifier.clickable { 
    // 被点击了
})

如果是需要更多的触摸事件,则可以使用:

Text(text = "我是一个可以点击的文字", Modifier.pointerInput(Unit) {
    // 这里可以接收更多事件
})

怎么给自定义绘制内容添加监听呢?

上一节简单介绍了怎么给 composable 添加监听,那么问题来了,怎么给我们自己绘制的内容添加呢?

答案是,一样的,但是也不完全一样。

为什么这么说呢,先看一个例子,假设有这么三个方格,每个方格都是一个按钮:

Canvas(
    modifier = Modifier
        .fillMaxSize()
) {
    drawRect(Color.Black, topLeft = Offset(0f, 0f), size = Size(50f, 50f))

    drawRect(Color.Red, topLeft = Offset(60f, 0f), size = Size(50f, 50f))

    drawRect(Color.Blue, topLeft = Offset(120f, 0f), size = Size(50f, 50f))
}

d1.png

怎么监听按钮点击?

要知道,绘制的作用域是 DrawScope 而不是 Compose 了。

并且,在 DrawScope 中也没有提供任何触摸相关的 API。如果想要监听触摸事件,我们只能在 compose 作用域中的 Modifier 中添加。

你可能会说,那还不简单,那我们直接给提供了 DrawScope 的 composable 加上监听不就得了?就像这样

Canvas(
    modifier = Modifier
        .fillMaxSize()
        .clickable {
            Log.i("el", "Test: click!")
        }
) {
    drawRect(Color.Black, topLeft = Offset(0f, 0f), size = Size(50f, 50f))

    drawRect(Color.Red, topLeft = Offset(60f, 0f), size = Size(50f, 50f))

    drawRect(Color.Blue, topLeft = Offset(120f, 0f), size = Size(50f, 50f))
}

你再看看,这代码对吗?如果你想监听的是这个 Canvas 整体,那确实没问题的。

但是,如果你想分别监听其中的不同部分,例如上面例子中的三个方格,显然这是不对的。

那么,我们要怎么分别监听呢?

监听方案

其实,这个也不难解决,只要我们能够拿到手指触摸的坐标点,再把这个坐标点和我们绘制的内容坐标点进行比对,如果点在按钮1区域内,则认为点击的是按钮1;如果坐标点在按钮2区域内则认为点击的是按钮2;以此类推。

所以,首先我们需要获取到点击的坐标点,接下来我们就只以单击这个触摸事件为例了,其他事件的处理方法也是一样的,只是换个 API 而已。

为了获取单击的坐标点,我们不能直接添加 clickable 修饰了,而是需要使用 pointerInput ,例如:

Modifier
    .pointerInput(Unit) {
        detectTapGestures(
            onTap = { offset: Offset ->
                Log.i("el", "click at $offset")
            }
        )
    }

这样,我们就拿到了点击的坐标点 offset

d2.png

有了坐标点,那么我们怎么去判断这个坐标点属于哪个按钮的呢?

直接计算

首当其冲的,我们能想到的当然是直接计算了。

毕竟这个图都是我们自己画,难道我们自己还不知道它的区域范围吗?

例如,上面的三个方块的例子,我们可以这样来判断点击的是哪个方块:

Canvas(
    modifier = Modifier
        .fillMaxSize()
        .pointerInput(Unit) {
            detectTapGestures(
                onTap = { offset: Offset ->
                    if (offset.x in 0f..50f && offset.y in 0f..50f) Log.i("el", "Test: click black block")
                    if (offset.x in 60f..110f && offset.y in 0f..50f) Log.i("el", "Test: click red block")
                    if (offset.x in 120f..170f && offset.y in 0f..50f) Log.i("el", "Test: click blue block")
                }
            )
        }
) {
    drawRect(Color.Black, topLeft = Offset(0f, 0f), size = Size(50f, 50f))

    drawRect(Color.Red, topLeft = Offset(60f, 0f), size = Size(50f, 50f))

    drawRect(Color.Blue, topLeft = Offset(120f, 0f), size = Size(50f, 50f))
}

d3.gif

如果是上面说的三个方块,那确实非常好算;那要是换成我开头说的那个模拟遥控器界面呢?好像也能算,不过是圆形和扇形嘛,也还有公式可以算;那,我换成一个完全无规则的图形呢?

比如一个地图,如果我现在绘制出了一个地图,然后需要监听地图中每一个区块的点击事件,该怎么算?地图的边界是完全没有任何规律的。

所以,直接计算只适合于简单的基础图形,并不适合我们这里的使用场景。

将可触摸范围缩小到方便计算的基础图形

既然图形太复杂,不好计算,那么我们就放弃一点用户体验,不要这么复杂的点击区域判断了。

我们只判断图形中的某个方便计算的区域不就行了?

例如:

d4.png

在这个例子中,我们只判断触摸点坐标是否在黄色矩形内,只有在黄色矩形内我们才认为它是点击了相应的按钮,否则我们就不予响应。

咋一看,好像挺不错的呢。

确实,如果不怕被测试追着你问为什么这个按键有时候点击会无响应的话,你可以试试这样干,哈哈哈。

使用前端方案

关于这个问题,我咨询了前端大佬 kirainmoe (什么?我为什么问搞前端的不问搞安卓的?因为我不认识搞安卓的啊……),前端大佬给出的答复是,对于这种情况,前端通常会多绘制一个离屏 canvas ,然后给需要监听的地方按照 id 给涂上相应的颜色,在点击显示的 canvas 时,用这个坐标在上色后的离屏 canvas 中获取颜色值,然后对比颜色和 id 确定点击的是哪个区域。

直接说可能不好理解,我画个图你们就好理解了。

d5.png

如图所示,左边是我们实际显示的 canvas ,右边是和左边一模一样的离屏 canvas ,离屏 canvas 仅用于计算,实际不会绘制,也不会显示出来。

在这个界面中,我们认为这个大猩猩是一个按键,我们需要分别监听不同的大猩猩,如果直接计算显然是无法计算的,因为这是个不规则的图形。

但是我们可以给离屏 canvas 上的不同大猩猩填充不同的颜色,并且建立对应关系,即: 红色-按钮1、绿色-按钮2、蓝色-按钮3。

当我们点击实际显示的大猩猩,即左边 canvas 上的大猩猩时,会拿到一个坐标值,我们使用这个坐标值从离屏 canvas ,即右边的 canvas 中相应的坐标点获取颜色,如果颜色是红色则认为点击的是按钮1;绿色认为点击的是按钮2;蓝色认为点击的是按钮3;其他颜色则认为点击的不是有效区域。

怎么一说,是不是觉得这方法真的太巧妙了,完全免去了我们自己计算点是否在相应区域内的问题。

然而……不幸的是,在 compose 中,我们无法拿到坐标点的颜色值,除非我们把这个 canvas 转成 bitmap……显然,这样做的性能代价会很高。

所以这个方法虽然十分巧妙,但是却不适合我们使用。

如果你执意想试试的话,这里有一篇文章教你怎么将 composable 转为 bitmap :

Jetpack Composable 🚀 to Bitmap Image 🌆

使用 Region

虽然上一节中的前端常用方法在安卓中无法使用,但是它的思路十分值得学习。

那么,在安卓中有没有类似的东西可以实现呢?

你还真别说,确实有,那就是 Region 。

Region,直译过来就是区域的意思,它是一个用单个或多个矩形(Rect)通过一定方式组合在一起形成任意图案的区域范围的类。

我们可以通过 Path 创建 Region ,这就意味着,我们可以完全复制一份我们绘制的内容生成 Region。

虽然 Region 不是 compose 工具包的内容之一,用来创建 Region 的 Path 也不是 compose 中的 Path,但是 compose 的 Path 可以通过扩展函数 path.asAndroidPath() 简单的转换为安卓绘制中的 Path。

并且,最最重要的是,Region 有一个方法 contains(int x, int y) 可以用来判断参数中指定的坐标值是否在该 Region 范围内!

因为这里我们的使用场景仅需要通过我们绘制的 path 生成 Region ,所以我们就不过多介绍关于 Region 的内容,如果有感兴趣的可以阅读文末附上的参考资料中其他大佬的文章学习。

我们先举个简单的例子:

val region = Region()

Column(Modifier.fillMaxSize()) {
    Canvas(
        modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectTapGestures(
                    onTap = { offset: Offset ->
                        Log.i(
                            "test",
                            "点击坐标 $offset, 是否在图形区域内=${
                                region.contains(
                                    offset.x.toInt(),
                                    offset.y.toInt()
                                )
                            }"
                        )
                    }
                )
            }
    ) {
        val path = Path()
        path.addOval(Rect(10f, 10f, 160f, 320f))
        region.setPath(path.asAndroidPath(), Region(10, 10, 160, 320))
        drawPath(path, color = Color.Black)
    }
}

在这个例子中,我们使用 path 简单的创建了一个椭圆形,然后将这个 path 添加到 region 中。最后绘制出这个 path 。

并且在点击回调中,通过把点击的坐标传递给 region.contains() 用于判断是否点击的是这个图形的范围。

需要注意的是,region.setPath(path.asAndroidPath(), Region(10, 10, 160, 320)) 中,setPath 必须设置一个新的 Region ,此时添加进 region 中的 path 实际上只是新创建的这个 Region 与 path 的交集。

这里因为我们想要将整个 path 全部添加进去,所以我创建了一个一定是比 path 区域大的 Region 。

对了,Region(10, 10, 160, 320) 这个初始化 Region 的四个参数分别是矩形的四个点的坐标值,还记得吗?前面我们说过,Region 实际就是由一个或多个矩形组成的任意图形,这里的初始化就是用一个矩形来初始化。

上面的代码运行效果如下:

d6.gif

经过上面的测试,证明该方法是可行的!

不过上面只示范了一个图形的情况,可能会有人说,那很多个图形呢?

行,那我写一个多个图形的例子:

@Composable
fun Test() {

    var text by remember { mutableStateOf("") }

    val regionList = listOf(
        Region(),
        Region(),
        Region()
    )

    val path = Path()

    Box(Modifier.fillMaxSize()) {
        Canvas(
            modifier = Modifier
                .fillMaxSize()
                .pointerInput(Unit) {
                    detectTapGestures(
                        onTap = { offset: Offset ->
                            if (regionList[0].contains(offset.x.toInt(), offset.y.toInt())) {
                                Log.i("el", "Test: click Red")
                                text = "click Red Button"
                            }
                            if (regionList[1].contains(offset.x.toInt(), offset.y.toInt())) {
                                Log.i("el", "Test: click Green")
                                text = "click Green Button"
                            }
                            if (regionList[2].contains(offset.x.toInt(), offset.y.toInt())) {
                                Log.i("el", "Test: click Blue")
                                text = "click Blue Button"
                            }
                        }
                    )
                }
        ) {
            // draw button red
            path.addOval(Rect(10f, 10f, 160f, 320f))
            regionList[0].setPath(path.asAndroidPath(), Region(0, 0, size.width.toInt(), size.height.toInt()))  // 这里偷懒了,直接创建了一个布满整个画布的 Region ,其实无所谓,因为是和 path 取交集,但是各位在使用的时候一定要根据自己的需求写,不要学我偷懒,不然出错了我可不负责
            drawPath(path, color = Color.Red)

            // draw button green
            path.reset()
            path.addOval(Rect(10f, 330f, 160f, 650f))
            regionList[1].setPath(path.asAndroidPath(), Region(0, 0, size.width.toInt(), size.height.toInt()))
            drawPath(path, color = Color.Green)

            // draw button blue
            path.reset()
            path.addOval(Rect(10f, 660f, 160f, 980f))
            regionList[2].setPath(path.asAndroidPath(), Region(0, 0, size.width.toInt(), size.height.toInt()))
            drawPath(path, color = Color.Blue)
        }

        Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Bottom, horizontalAlignment = Alignment.CenterHorizontally) {
            Text(text = text, fontSize = 22.sp)
        }
    }
}

运行效果如下:

d7.gif

最后,我们来实现文章开头提到的模拟遥控器效果(本来想偷懒不画了的,但是评论区有人想看,那我就补上吧)

@Composable
fun Test() {

    val okColor = Color(0xFF49A0F8)

    var text by remember { mutableStateOf("") }

    val regionList = listOf(
        Region(),
        Region(),
        Region(),
        Region(),
        Region()
    )

    val path = Path()
    val okPath = Path()  // 这个路径需要用来做运算,所以单独声明一个

    Box(Modifier.fillMaxSize()) {
        Canvas(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.Black)
                .pointerInput(Unit) {
                    detectTapGestures(
                        onTap = { offset: Offset ->
                            if (regionList[0].contains(offset.x.toInt(), offset.y.toInt())) {
                                Log.i("el", "Test: click OK")
                                text = "click OK Button"
                            }
                            if (regionList[1].contains(offset.x.toInt(), offset.y.toInt())) {
                                Log.i("el", "Test: click Button1")
                                text = "click Button1 Button"
                            }
                            if (regionList[2].contains(offset.x.toInt(), offset.y.toInt())) {
                                Log.i("el", "Test: click Button2")
                                text = "click Button2 Button"
                            }
                            if (regionList[3].contains(offset.x.toInt(), offset.y.toInt())) {
                                Log.i("el", "Test: click Button3")
                                text = "click Button3 Button"
                            }
                            if (regionList[4].contains(offset.x.toInt(), offset.y.toInt())) {
                                Log.i("el", "Test: click Button4")
                                text = "click Button4 Button"
                            }
                        }
                    )
                }
        ) {
            // 绘制外廓圆(这个园不用点击事件,只是一个线段)
            path.reset()
            path.addOval(Rect(Offset(size.width/2, size.height/2), 500f))
            drawPath(path, Color.White)

            // 绘制最中间的 OK 按钮
            okPath.addOval(Rect(Offset(size.width/2, size.height/2), 200f))
            regionList[0].setPath(okPath.asAndroidPath(), Region(0, 0, size.width.toInt(), size.height.toInt()))  // 这里偷懒了,直接创建了一个不满整个画布的 Region ,其实无所谓,因为是和 path 取交集,但是各位在使用的时候一定要根据自己的需求写,不要学我偷懒,不然出错了我可不负责
            drawPath(okPath, color = okColor)

            // 绘制按钮1
            path.reset()
            path.addArc(Rect(Offset(size.width/2, size.height/2), 500f), 225f, 90f)
            path.lineTo(size.width/2, size.height/2)
            path.op(path, okPath, PathOperation.Difference)
            regionList[1].setPath(path.asAndroidPath(), Region(0, 0, size.width.toInt(), size.height.toInt()))
            drawPath(path, color = okColor, style = Stroke(width = 2f))

            // 绘制按钮2
            path.reset()
            path.addArc(Rect(Offset(size.width/2, size.height/2), 500f), 315f, 90f)
            path.lineTo(size.width/2, size.height/2)
            path.op(path, okPath, PathOperation.Difference)
            regionList[2].setPath(path.asAndroidPath(), Region(0, 0, size.width.toInt(), size.height.toInt()))
            drawPath(path, color = okColor, style = Stroke(width = 2f))

            // 绘制按钮3
            path.reset()
            path.addArc(Rect(Offset(size.width/2, size.height/2), 500f), 45f, 90f)
            path.lineTo(size.width/2, size.height/2)
            path.op(path, okPath, PathOperation.Difference)
            regionList[3].setPath(path.asAndroidPath(), Region(0, 0, size.width.toInt(), size.height.toInt()))
            drawPath(path, color = okColor, style = Stroke(width = 2f))

            // 绘制按钮4
            path.reset()
            path.addArc(Rect(Offset(size.width/2, size.height/2), 500f), 135f, 90f)
            path.lineTo(size.width/2, size.height/2)
            path.op(path, okPath, PathOperation.Difference)
            regionList[4].setPath(path.asAndroidPath(), Region(0, 0, size.width.toInt(), size.height.toInt()))
            drawPath(path, color = okColor, style = Stroke(width = 2f))

            // 绘制文字
            drawIntoCanvas {
                val paint = android.graphics.Paint().apply {
                    color = android.graphics.Color.WHITE
                    textSize = 24.sp.toPx()
                }

                // OK
                it.nativeCanvas.drawText(
                    "OK",
                    size.width/2-50,
                    size.height/2+25,
                    paint
                )

                paint.color = android.graphics.Color.BLACK

                // Button 1
                it.nativeCanvas.drawText(
                    "Button1",
                    size.width/2-110,
                    size.height/2-250,
                    paint
                )

                // Button 2
                it.nativeCanvas.drawText(
                    "Button2",
                    size.width/2+250,
                    size.height/2+20,
                    paint
                )

                // Button 3
                it.nativeCanvas.drawText(
                    "Button3",
                    size.width/2-110,
                    size.height/2+300,
                    paint
                )

                // Button 4
                it.nativeCanvas.drawText(
                    "Button4",
                    size.width/2-450,
                    size.height/2+20,
                    paint
                )
            }

        }

        Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Bottom, horizontalAlignment = Alignment.CenterHorizontally) {
            Text(text = text, fontSize = 22.sp, color = Color.White)
        }
    }
}

运行效果:

d8.gif

总结

通过上面的分析探讨,我们可以知道,在 compose 中如果想要监听绘制内容的触摸事件,最好的方法还是使用 path 绘制,配合生成 Region 用于检测触摸的坐标点是否在指定范围之内。

当然,这只是我个人的想法,如果各位大佬还有更好的方案,希望能不吝赐教!

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

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

相关文章

Linux 权限-+完整思维导图+实图例子+深入细节+通俗易懂建议收藏

绪论 当时间的主人,命运的主宰,灵魂的舵手。上一回已将基础权限全部学习完了,本章开始我们将进入到权限的学习。 话不多说安全带系好,发车啦(建议电脑观看)。 附:红色,部分为重点部分…

基于Gitee的webhook编写hugo的自动构建实现博客自动更新

前言 差不多半年前趁着某云优惠,我买了5年的轻量级应用服务器。 拿着这个服务器原本打算做我的某个APP的服务端的,后来又觉得迁移数据好麻烦,所以随便搞了个博客上去。 选来选去,使用了 hugo 作为构建引擎。 正好,…

跟我一起使用 compose 做一个跨平台的黑白棋游戏(1)整体实现思路

前言 为什么写这系列文章 虽然 compose 正式版已经出来很久了,也有很多大佬写了很多教程文章和实例 demo ,但是对于 compose 其实我也还是一知半解的。 特别是对于 compose 的状态管理,由于 compose 声明式的特性,如果不对状态…

chatgpt赋能Python-pythonfor怎么用

PythonFor SEO:如何利用Python提高SEO效果 SEO(搜索引擎优化)是现代数字营销中至关重要的一环。随着搜索引擎算法不断发展,优化网站以提高排名已经成为了一门复杂的艺术。幸运的是,Python提供了一些强大的工具来简化这…

chatgpt赋能Python-pythonelem

PythonELEM - 简易的Python学习工具 作为一名有10年Python编程经验的工程师,我可以深刻地体会到新手们学习Python的难处。PythonELEM是一个以Python为主题的学习工具,它可以帮助初学者更容易地掌握Python编程。 PythonELEM的功能 PythonELEM是一个简易…

餐饮油烟排放监测管理系统的设计与应用

安科瑞虞佳豪 连日来,河东区生态环境保护综合行政执法支队组织开展餐饮行业油烟净化专项检查工作,有效应对即将到来的夏季餐饮油烟对环境的污染,着力解决群众身边的环境问题。 执法人员对辖区餐饮商户集中区域开展常态化巡查,重…

Metal入门学习:绘制渲染三角形

一、编程指南PDF下载链接(中英文档) 1、Metal编程指南PDF链接 https://github.com/dennie-lee/ios_tech_record/raw/main/Metal学习PDF/Metal 编程指南.pdf 2、Metal着色语言(Metal Shader Language:简称MSL)编程指南PDF链接 https://github.com/dennie-lee/ios_te…

chatgpt赋能Python-pythoncumsum

Python中的cumsum-累积求和函数 在数据处理中,经常需要对一个序列的元素进行累加。Python中提供了累积求和函数cumsum(),用于对一个序列的元素进行累加求和操作。 什么是cumsum()函数 cumsum()函数是Python中numpy模块中的一个函数,用于对…

通过小米万兆路由器将小米SoundMove 无缝接入 ChatGPT

通过小米万兆路由器将小米SoundMove 无缝接入 ChatGPT 本教程内容参考 Github 地址(可选)部署查看小米 SoundMove 信息的环境(可选)查看小米 SoundMove 的信息以容器方式部署程序到小米万兆路由器实际效果有待改善点 本教程内容 1 是记录了将小米 SoundMove 接入 ChatGPT 的操…

面向《海贼王》领域数据的知识图谱项目

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 本次任务试图为《海贼王》中出现的各个实体,包括人物、地点、组织等,构建一个知识图谱,帮助我们更好的理解这部作品。 项目内容包括数据采集、知识存储、知识抽取、知识计算、知识应用五大部…

【运动规划算法项目实战】如何使用Pure Pursuit算法进行路径跟踪(附ROS C++代码)

文章目录 前言一、简介二、Pure Pursuit算法优缺点三、 代码实现3.1 算法实现步骤3.2 pure_pursuit.h3.3 pure_pursuit.cpp3.4 cubic_spline_path.py3.5 节点连接关系3.6 RVIZ显示四、总结前言 在自动驾驶和机器人导航领域,路径跟踪是一项关键技术,它使车辆或机器人能够按照…

多线程排序法

多线程排序法 chatGPT给我改的多线程排序法 using System.Collections.Concurrent; using System.Threading; ConcurrentBag<int> sortedList new ConcurrentBag<int>(); void Sort() { int[] arr {2, 6, 12, 8}; List<Thread> threads new List<Threa…

chatgpt赋能Python-pythondone

PythonDone&#xff1a;将Python编程变得更加简单 介绍 Python是一种有着广泛应用的高级编程语言&#xff0c;由于其简洁易学、开发效率高、可移植性好等特点&#xff0c;成为业内最热门的技术之一。但是&#xff0c;对于一些初学者来说&#xff0c;Python的学习过程可能还是…

【论文分享|SIGMOD‘22】WeTune 自动发现和验证重写规则

作者&#xff1a;谢其骏 北京航空航天大学在读硕士&#xff0c; Databend 研发工程师实习生 https://github.com/jun0315 论文原文&#xff1a; Zhaoguo Wang, Zhou Zhou, Yicun Yang, Haoran Ding, Gansen Hu, Ding Ding, Chuzhe Tang, Haibo Chen, Jinyang Li. WeTune: Auto…

【AIGC】11、MDETR | LeCun 团队于 2021 年推出的端到端多模态理解模型

文章目录 一、背景二、方法2.1 DETR2.2 MDETR 三、效果3.1 预训练调整后的检测器3.2 下游任务 论文&#xff1a;MDETR - Modulated Detection for End-to-End Multi-Modal Understanding 代码&#xff1a;https://github.com/ashkamath/mdetr 出处&#xff1a;ICCV 2021 Oral…

chatgpt赋能Python-pythonctrl快捷键

PythonCtrl快捷键使用指南 作为一名有10年Python编程经验的工程师&#xff0c;我深知PythonCtrl快捷键的重要性。PythonCtrl作为一个Python的开源编辑器&#xff0c;在每一个版本中都加入了更多的功能和快捷键&#xff0c;使得Python编程更加高效和易用。在本篇文章中&#xf…

卡方分布分析与应用

卡方检验(chi-square&#xff0c;记为χ2检验)是统计学中常用来计数数据分析的方法&#xff0c;对于总体的分布不作任何假设&#xff0c;因此它属于非参数检验法中的一种。本博文从理论到实际应用去阐述卡方检验&#xff0c;最后用python语言去实现卡方分布的代码。 1. 卡方分…

Spring Security的基本组件

一.简介 Spring Security通过一些列的过滤器完成了用户身份认证及其授权工作&#xff0c;每个过滤器都有不同分工&#xff0c;当然这些过滤器并不是全部都一起工作&#xff0c;而是根据我们需要什么功能&#xff0c;才会选取对应的过滤器加入。 当然这些过滤器并不是直接加入…

Linux 终端特殊符号含义大全

Linux特殊符号使用及含义 总结 Linux 终端中有许多特殊符号&#xff0c;本文对常用的进行了总结&#xff1a; $ 表示变量/普通终端用户&#xff1a;用于引用变量的值/表示终端中的普通用户。# 表示注释/超级用户&#xff1a;用于在脚本中注释代码/表示终端中的超级用户。/ &…

Doxygen 源码分析: QCString类

2023-05-20 23:41:56 ChrisZZ imzhuofoxmailcom Hompage https://github.com/zchrissirhcz 文章目录 1. Doxygen 版本2. QCString 类概览3. QCString 特殊成员函数3.1 default 方式的构造函数3.2 单个参数和两个参数的构造函数 4. inline方式实现的成员函数4.1 operator 函数4.…