如何仿一个抖音极速版领现金的进度条动画?

news2024/11/17 2:53:16

效果演示

20230617_064552_edit.gif

不仅仅是实现效果,要封装,就封装好

看完了演示的效果,你是否在思考,代码应该怎么实现?先不着急写代码,先想想哪些地方是要可以动态配置的。首先第一个,进度条的形状是不是要可以换?然后进度条的背景色和填充的颜色,以及动画的时长是不是也要可以配置?没错,起始位置是不是也要可以换?最好还要让速度可以一会快一会慢对吧,画笔的笔帽是不是还可以选择平的或圆的?带着这些问题,我们再开始写代码。

代码实现

我们写一个自定义View,把可以动态配置的地方想好后,就可以定义自定义属性了。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DoraProgressView">
        <attr name="dview_progressType">
            <enum name="line" value="0"/>
            <enum name="semicircle" value="1"/>
            <enum name="semicircleReverse" value="2"/>
            <enum name="circle" value="3"/>
            <enum name="circleReverse" value="4"/>
        </attr>
        <attr name="dview_progressOrigin">
            <enum name="left" value="0"/>
            <enum name="top" value="1"/>
            <enum name="right" value="2"/>
            <enum name="bottom" value="3"/>
        </attr>
        <attr format="dimension|reference" name="dview_progressWidth"/>
        <attr format="color|reference" name="dview_progressBgColor"/>
        <attr format="color|reference" name="dview_progressHoverColor"/>
        <attr format="integer" name="dview_animationTime"/>
        <attr name="dview_paintCap">
            <enum name="flat" value="0"/>
            <enum name="round" value="1"/>
        </attr>
    </declare-styleable>
</resources>

然后我们不管三七二十一,先把自定义属性解析出来。

private fun initAttrs(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
    val a = context.obtainStyledAttributes(
        attrs,
        R.styleable.DoraProgressView,
        defStyleAttr,
        0
    )
    when (a.getInt(R.styleable.DoraProgressView_dview_progressType, PROGRESS_TYPE_LINE)) {
        0 -> progressType = PROGRESS_TYPE_LINE
        1 -> progressType = PROGRESS_TYPE_SEMICIRCLE
        2 -> progressType = PROGRESS_TYPE_SEMICIRCLE_REVERSE
        3 -> progressType = PROGRESS_TYPE_CIRCLE
        4 -> progressType = PROGRESS_TYPE_CIRCLE_REVERSE
    }
    when (a.getInt(R.styleable.DoraProgressView_dview_progressOrigin, PROGRESS_ORIGIN_LEFT)) {
        0 -> progressOrigin = PROGRESS_ORIGIN_LEFT
        1 -> progressOrigin = PROGRESS_ORIGIN_TOP
        2 -> progressOrigin = PROGRESS_ORIGIN_RIGHT
        3 -> progressOrigin = PROGRESS_ORIGIN_BOTTOM
    }
    when(a.getInt(R.styleable.DoraProgressView_dview_paintCap, 0)) {
        0 -> paintCap = Paint.Cap.SQUARE
        1 -> paintCap = Paint.Cap.ROUND
    }
    progressWidth = a.getDimension(R.styleable.DoraProgressView_dview_progressWidth, 30f)
    progressBgColor =
        a.getColor(R.styleable.DoraProgressView_dview_progressBgColor, Color.GRAY)
    progressHoverColor =
        a.getColor(R.styleable.DoraProgressView_dview_progressHoverColor, Color.BLUE)
    animationTime = a.getInt(R.styleable.DoraProgressView_dview_animationTime, 1000)
    a.recycle()
}

解析完自定义属性,切勿忘了释放TypedArray。接下来我们考虑下一步,测量。半圆是不是不要那么大的画板对吧,我们在测量的时候就要充分考虑进去。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    progressBgPaint.strokeWidth = progressWidth
    progressHoverPaint.strokeWidth = progressWidth
    if (progressType == PROGRESS_TYPE_LINE) {
        // 线
        var left = 0f
        var top = 0f
        var right = measuredWidth.toFloat()
        var bottom = measuredHeight.toFloat()
        val isHorizontal = when(progressOrigin) {
            PROGRESS_ORIGIN_LEFT, PROGRESS_ORIGIN_RIGHT -> true
            else -> false
        }
        if (isHorizontal) {
            top = (measuredHeight - progressWidth) / 2
            bottom = (measuredHeight + progressWidth) / 2
            progressBgRect[left + progressWidth / 2, top, right - progressWidth / 2] = bottom
        } else {
            left = (measuredWidth - progressWidth) / 2
            right = (measuredWidth + progressWidth) / 2
            progressBgRect[left, top + progressWidth / 2, right] = bottom - progressWidth / 2
        }
    } else if (progressType == PROGRESS_TYPE_CIRCLE || progressType == PROGRESS_TYPE_CIRCLE_REVERSE) {
        // 圆
        var left = 0f
        val top = 0f
        var right = measuredWidth
        var bottom = measuredHeight
        progressBgRect[left + progressWidth / 2, top + progressWidth / 2, right - progressWidth / 2] =
            bottom - progressWidth / 2
    } else {
        // 半圆
        val isHorizontal = when(progressOrigin) {
            PROGRESS_ORIGIN_LEFT, PROGRESS_ORIGIN_RIGHT -> true
            else -> false
        }
        val min = measuredWidth.coerceAtMost(measuredHeight)
        var left = 0f
        var top = 0f
        var right = 0f
        var bottom = 0f
        if (isHorizontal) {
            if (measuredWidth >= min) {
                left = ((measuredWidth - min) / 2).toFloat()
                right = left + min
            }
            if (measuredHeight >= min) {
                bottom = top + min
            }
            progressBgRect[left + progressWidth / 2, top + progressWidth / 2, right - progressWidth / 2] =
                bottom - progressWidth / 2
            setMeasuredDimension(
                MeasureSpec.makeMeasureSpec(
                    (right - left).toInt(),
                    MeasureSpec.EXACTLY
                ),
                MeasureSpec.makeMeasureSpec(
                    (bottom - top + progressWidth).toInt() / 2,
                    MeasureSpec.EXACTLY
                )
            )
        } else {
            if (measuredWidth >= min) {
                right = left + min
            }
            if (measuredHeight >= min) {
                top = ((measuredHeight - min) / 2).toFloat()
                bottom = top + min
            }
            progressBgRect[left + progressWidth / 2, top + progressWidth / 2, right - progressWidth / 2] =
                bottom - progressWidth / 2
            setMeasuredDimension(
                MeasureSpec.makeMeasureSpec(
                    (right - left + progressWidth).toInt() / 2,
                    MeasureSpec.EXACTLY
                ),
                MeasureSpec.makeMeasureSpec(
                    (bottom - top).toInt(),
                    MeasureSpec.EXACTLY
                )
            )
        }
    }
}

View的onMeasure()方法是不是默认调用了一个

super.onMeasure(widthMeasureSpec, heightMeasureSpec)

它最终会调用setMeasuredDimension()方法来确定最终测量的结果吧。如果我们对默认的测量不满意,我们可以自己改,最后也调用setMeasuredDimension()方法把测量结果确认。半圆,如果是水平的情况下,我们的宽度就只要一半,相反如果是垂直的半圆,我们高度就只要一半。最后我们画还是照常画,只不过在最后把画到外面的部分移动到画板上显示出来。接下来就是我们最重要的绘图环节了。

override fun onDraw(canvas: Canvas) {
    if (progressType == PROGRESS_TYPE_LINE) {
        val isHorizontal = when(progressOrigin) {
            PROGRESS_ORIGIN_LEFT, PROGRESS_ORIGIN_RIGHT -> true
            else -> false
        }
        if (isHorizontal) {
            canvas.drawLine(
                progressBgRect.left,
                measuredHeight / 2f,
                progressBgRect.right,
                measuredHeight / 2f,
                progressBgPaint)
        } else {
            canvas.drawLine(measuredWidth / 2f,
                progressBgRect.top,
                                    measuredWidth / 2f,
                progressBgRect.bottom, progressBgPaint)
        }
        if (percentRate > 0) {
            when (progressOrigin) {
                PROGRESS_ORIGIN_LEFT -> {
                    canvas.drawLine(
                        progressBgRect.left,
                        measuredHeight / 2f,
                        (progressBgRect.right) * percentRate,
                        measuredHeight / 2f,
                        progressHoverPaint
                    )
                }
                PROGRESS_ORIGIN_TOP -> {
                    canvas.drawLine(measuredWidth / 2f,
                        progressBgRect.top,
                        measuredWidth / 2f,
                        (progressBgRect.bottom) * percentRate,
                        progressHoverPaint)
                }
                PROGRESS_ORIGIN_RIGHT -> {
                    canvas.drawLine(
                        progressWidth / 2 + (progressBgRect.right) * (1 - percentRate),
                        measuredHeight / 2f,
                        progressBgRect.right,
                        measuredHeight / 2f,
                        progressHoverPaint
                    )
                }
                PROGRESS_ORIGIN_BOTTOM -> {
                    canvas.drawLine(measuredWidth / 2f,
                        progressWidth / 2 + (progressBgRect.bottom) * (1 - percentRate),
                    measuredWidth / 2f,
                        progressBgRect.bottom,
                    progressHoverPaint)
                }
            }
        }
    } else if (progressType == PROGRESS_TYPE_SEMICIRCLE) {
        if (progressOrigin == PROGRESS_ORIGIN_LEFT) {
            // PI ~ 2PI
            canvas.drawArc(progressBgRect, 180f, 180f, false, progressBgPaint)
            canvas.drawArc(
                progressBgRect,
                180f,
                angle.toFloat(),
                false,
                progressHoverPaint
            )
        } else if (progressOrigin == PROGRESS_ORIGIN_TOP) {
            canvas.translate(-progressBgRect.width() / 2, 0f)
            // 3/2PI ~ 2PI, 0 ~ PI/2
            canvas.drawArc(progressBgRect, 270f, 180f, false, progressBgPaint)
            canvas.drawArc(
                progressBgRect,
                270f,
                angle.toFloat(),
                false,
                progressHoverPaint
            )
        } else if (progressOrigin == PROGRESS_ORIGIN_RIGHT) {
            canvas.translate(0f, -progressBgRect.height() / 2)
            // 2PI ~ PI
            canvas.drawArc(progressBgRect, 0f, 180f, false, progressBgPaint)
            canvas.drawArc(
                progressBgRect,
                0f,
                angle.toFloat(),
                false,
                progressHoverPaint
            )
        } else if (progressOrigin == PROGRESS_ORIGIN_BOTTOM) {
            // PI/2 ~ 3/2PI
            canvas.drawArc(progressBgRect, 90f, 180f, false, progressBgPaint)
            canvas.drawArc(
                progressBgRect,
                90f,
                angle.toFloat(),
                false,
                progressHoverPaint
            )
        }
    } else if (progressType == PROGRESS_TYPE_SEMICIRCLE_REVERSE) {
        if (progressOrigin == PROGRESS_ORIGIN_LEFT) {
            canvas.translate(0f, -progressBgRect.height() / 2)
            // PI ~ 2PI
            canvas.drawArc(progressBgRect, 180f, -180f, false, progressBgPaint)
            canvas.drawArc(
                progressBgRect,
                180f,
                -angle.toFloat(),
                false,
                progressHoverPaint
            )
        } else if (progressOrigin == PROGRESS_ORIGIN_TOP) {
            // 3/2PI ~ PI/2
            canvas.drawArc(progressBgRect, 270f, -180f, false, progressBgPaint)
            canvas.drawArc(
                progressBgRect,
                270f,
                -angle.toFloat(),
                false,
                progressHoverPaint
            )
        } else if (progressOrigin == PROGRESS_ORIGIN_RIGHT) {
            // 2PI ~ PI
            canvas.drawArc(progressBgRect, 0f, -180f, false, progressBgPaint)
            canvas.drawArc(
                progressBgRect,
                0f,
                -angle.toFloat(),
                false,
                progressHoverPaint
            )
        } else if (progressOrigin == PROGRESS_ORIGIN_BOTTOM) {
            canvas.translate(-progressBgRect.width() / 2, 0f)
            // PI/2 ~ 2PI, 2PI ~ 3/2PI
            canvas.drawArc(progressBgRect, 90f, -180f, false, progressBgPaint)
            canvas.drawArc(
                progressBgRect,
                90f,
                -angle.toFloat(),
                false,
                progressHoverPaint
            )
        }
    } else if (progressType == PROGRESS_TYPE_CIRCLE) {
        val deltaAngle = if (progressOrigin == PROGRESS_ORIGIN_TOP) {
            90f
        } else if (progressOrigin == PROGRESS_ORIGIN_RIGHT) {
            180f
        } else if (progressOrigin == PROGRESS_ORIGIN_BOTTOM) {
            270f
        } else {
            0f
        }
        canvas.drawArc(progressBgRect, 0f, 360f, false, progressBgPaint)
        canvas.drawArc(
            progressBgRect,
            180f + deltaAngle,
            angle.toFloat(),
            false,
            progressHoverPaint
        )
    } else if (progressType == PROGRESS_TYPE_CIRCLE_REVERSE) {
        val deltaAngle = if (progressOrigin == PROGRESS_ORIGIN_TOP) {
            90f
        } else if (progressOrigin == PROGRESS_ORIGIN_RIGHT) {
            180f
        } else if (progressOrigin == PROGRESS_ORIGIN_BOTTOM) {
            270f
        } else {
            0f
        }
        canvas.drawArc(progressBgRect, 0f, 360f, false, progressBgPaint)
        canvas.drawArc(
            progressBgRect,
            180f + deltaAngle,
            -angle.toFloat(),
            false,
            progressHoverPaint
        )
    }
}

绘图除了需要Android的基础绘图知识外,还需要一定的数学计算的功底,比如基本的几何图形的点的计算你要清楚。怎么让绘制的角度变化起来呢?这个问题问的好。这个就牵扯出我们动画的一个关键类,TypeEvaluator,这个接口可以让我们只需要指定边界值,就可以根据动画执行的时长,来动态计算出当前的渐变值。

private inner class AnimationEvaluator : TypeEvaluator<Float> {
    override fun evaluate(fraction: Float, startValue: Float, endValue: Float): Float {
        return if (endValue > startValue) {
            startValue + fraction * (endValue - startValue)
        } else {
            startValue - fraction * (startValue - endValue)
        }
    }
}

百分比渐变的固定写法,是不是应该记个笔记,方便以后CP?那么现在我们条件都成熟了,只需要将初始角度的百分比改变一下,我们写一个改变角度百分比的方法。

fun setPercentRate(rate: Float) {
    if (animator == null) {
        animator = ValueAnimator.ofObject(
            AnimationEvaluator(),
            percentRate,
            rate
        )
    }
    animator?.addUpdateListener { animation: ValueAnimator ->
        val value = animation.animatedValue as Float
        angle =
            if (progressType == PROGRESS_TYPE_CIRCLE || progressType == PROGRESS_TYPE_CIRCLE_REVERSE) {
                (value * 360).toInt()
            } else if (progressType == PROGRESS_TYPE_SEMICIRCLE || progressType == PROGRESS_TYPE_SEMICIRCLE_REVERSE) {
                (value * 180).toInt()
            } else {
                0   // 线不需要求角度
            }
        percentRate = value
        invalidate()
    }
    animator?.interpolator = LinearInterpolator()
    animator?.setDuration(animationTime.toLong())?.start()
    animator?.addListener(object : Animator.AnimatorListener {
        override fun onAnimationStart(animation: Animator) {}
        override fun onAnimationEnd(animation: Animator) {
            percentRate = rate
            listener?.onComplete()
        }

        override fun onAnimationCancel(animation: Animator) {}
        override fun onAnimationRepeat(animation: Animator) {}
    })
}

这里牵扯到了Animator。有start就一定不要忘了异常中断的情况,我们可以写一个reset的方法来中断动画执行,恢复到初始状态。

fun reset() {
    percentRate = 0f
    animator?.cancel()
}

如果你不reset,想连续执行动画,则两次调用的时间间隔一定要大于动画时长,否则就应该先取消动画。

涉及到的Android绘图知识点

我们归纳一下完成这个自定义View需要具备的知识点。

  1. 基本图形的绘制,这里主要是扇形
  2. 测量和画板的平移变换
  3. 自定义属性的定义和解析
  4. Animator和动画估值器TypeEvaluator的使用

思路和灵感来自于系统化的基础知识

这个控件其实并不难,主要就是动态配置一些参数,然后在计算上稍微复杂一些,需要一些数学的功底。那么你为什么没有思路呢?你没有思路最可能的原因主要有以下几个可能。

  1. 自定义View的基础绘图API不熟悉
  2. 动画估值器使用不熟悉
  3. 对自定义View的基本流程不熟悉
  4. 看的自定义View的源码不够多
  5. 自定义View基础知识没有系统学习,导致是一些零零碎碎的知识片段
  6. 数学功底不扎实

我觉得往往不是你不会,这些基础知识点你可能都看到过很多次,但是一到自己写就没有思路了。思路和灵感来自于大量源码的阅读和大量的实践。大前提就是你得先把自定义View的这些知识点系统学习一下,先保证都见过,然后才是将它们融会贯通,用的时候信手拈来。

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

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

相关文章

五种主流数据库:常用数据类型

在设计数据库的表结构时&#xff0c;我们需要明确表中包含哪些字段以及字段的数据类型。字段的数据类型定义了该字段能够存储的数据种类以及支持的操作。 本文将会介绍五种主流数据库中常用的数据类型以及如何选择合适的数据类型&#xff0c;包括 MySQL、Oracle、SQL Server、…

零代码本地搭建AI大模型,详细教程!普通电脑也能流畅运行,中文回答速度快,回答质量高...

你好&#xff0c;我是郭震 这篇教程主要解决&#xff1a; 1). 有些读者朋友&#xff0c;电脑配置不高&#xff0c;比如电脑没有配置GPU显卡&#xff0c;还想在本地使用AI&#xff1b; 2). Llama3回答中文问题欠佳&#xff0c;想安装一个回答中文问题更强的AI大模型。 3). 想成为…

汽车IVI中控开发入门及进阶(二十四):杰发科技AC8015

前言: 在此之前的大部分时间,四维图新更多的是以图商的身份在业内出现,但现在四维图新图商之外的技术积累提现在了杰发科技身上,或者是从图商到汽车智能化一体解决方案供应商的角色转变。汽车智能化,可以简单的归为座舱智能化和智能驾驶两个板块。 随着汽车变得越来越智能…

显示器与电脑如何分屏显示?

1.点击电脑屏幕右键--显示设置 2、然后找到屏幕---找到多显示器---选择扩展显示器

重生奇迹MU召唤师如何学习狂暴术?

一、了解狂暴术的基本信息 狂暴术是一种非常强大的技能&#xff0c;可以让召唤师的攻击力和防御力大幅度提高&#xff0c;但同时也会增加自身的伤害。在使用狂暴术之前&#xff0c;召唤师需要仔细考虑自己的状态和对手的情况。 二、学习狂暴术的方法 1.通过任务学习 在游戏…

戴尔科技:一盆冷水浇醒了AIPC

这年头&#xff0c;只要沾上英伟达的公司&#xff0c;不论美股还是大A,都跟着鸡犬升天几轮过&#xff0c;但昨晚英伟达蒸发1064亿美元&#xff0c; 跟着遭罪的也不少&#xff0c;有没有一夜惊魂梦醒的感觉&#xff1f; 今天我们来说说——戴尔科技。 昨晚戴尔科技大跌5.18%&a…

查看VUE3代理后真正请求的URL

在vite.config.ts中添加如下配置&#xff1a; server: {host: "0.0.0.0", // 指定服务器应该监听哪个 IP 地址port: 8848, // 指定开发服务器端口open: true, // 开发服务器启动时&#xff0c;自动在浏览器中打开应用程序cors: true,// Load proxy configuration fr…

建议收藏-各类IT证书查验真伪链接

1、红帽认证证书核验链接&#xff1a; https://rhtapps.redhat.com/verify/ RHCSA认证、RHCE认证、RHCA认证 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 2、华为认证证书核验链接&#xff1a; https://e.huawei.com/cn/talent/#/cert/certificate…

Java进阶学习笔记31——日期时间

Date&#xff1a; 代表的是日期和时间。 分配Date对象并初始化它以表示自标准基准时间&#xff08;称为纪元&#xff09;以来的指定毫秒数&#xff0c;即1970年1月1日00:00:00。 有参构造器。 package cn.ensource.d3_time;import java.util.Date;public class Test1Date {pu…

YOLOv10涨点改进:卷积魔改 | 分布移位卷积(DSConv),提高卷积层的内存效率和速度

💡💡💡本文改进内容: YOLOv10如何魔改卷积进一步提升检测精度?提出了一种卷积的变体,称为DSConv(分布偏移卷积),其可以容易地替换进标准神经网络体系结构并且实现较低的存储器使用和较高的计算速度。 DSConv将传统的卷积内核分解为两个组件:可变量化内核(VQK)和…

打造你的专属Vue组件:超实用“高级筛选弹窗组件“实战

打造你的专属Vue组件&#xff1a;超实用“高级筛选弹窗组件“实战 在现代前端开发中&#xff0c;组件化思想是提高开发效率、维护性和代码复用性的关键。本文将通过一个实例——创建一个自定义的“高级筛选”弹窗组件&#xff0c;来展示如何在Vue框架下利用Composition API和E…

内网安全:横向传递攻击(PTH || PTK || PTT 哈希票据传递)

内网安全&#xff1a;横向传递攻击. 横向移动就是在拿下对方一台主机后&#xff0c;以拿下的那台主机作为跳板&#xff0c;对内网的其他主机再进行后面渗透&#xff0c;利用既有的资源尝试获取更多的凭据、更高的权限&#xff0c;一步一步拿下更多的主机&#xff0c;进而达到控…

Mac电脑pd虚拟机专用windows系统镜像(m1/intel)win10、11镜像文件

入手了Mac电脑后&#xff0c;由于需要用到Windows软件&#xff0c;又嫌安装双系统太复杂&#xff0c;这时候Mac就用到了安装虚拟机&#xff0c;目前最好用的虚拟机是Parallels Desktop&#xff0c;win镜像版本要根据自己的喜好选对&#xff0c;在此提供分别兼容M1和Intel的win1…

WSL2-Ubuntu22.04-配置

WSL2-Ubuntu22.04-配置 准备1. WSL相关命令[^1]2. WSL2-Ubuntu22.04可视化3. WSL2 设置 CUDA4. 设置OpenGL 本文介绍了WSL2的基本使用方法及可视化&#xff0c;着重介绍了GPU和OpenGL的设置。 准备 名称版本windows11wsl2CUDA12.5 1. WSL相关命令1 查看已安装的wsl distribut…

AMS 仿真 ERROR

ERROR (OSSHNL-514): Netlist generation failed because of the errors reported above. The netlist might not have been generated at all, or the generated netlist could be corrupt. Fix the reported errors and regenerate the netlist. 原因&#xff1a;用的incisi…

假设检验学习笔记

1. 假设检验的基本概念 1.1. 原假设&#xff08;零假设&#xff09; 对总体的分布所作的假设用表示&#xff0c;并称为原假设或零假设 在总体分布类型已知的情况下&#xff0c;仅仅涉及总体分布中未知参数的统计假设&#xff0c;称为参数假设 在总体分布类型未知的情况下&#…

Solidity学习-投票合约示例

以下的合约有一些复杂&#xff0c;但展示了很多Solidity的语言特性。它实现了一个投票合约。 当然&#xff0c;电子投票的主要问题是如何将投票权分配给正确的人员以及如何防止被操纵。 我们不会在这里解决所有的问题&#xff0c;但至少我们会展示如何进行委托投票&#xff0c;…

解决安装 WP Super Cache 插件提示 Advanced-Cache.Php 是另一个插件创建的

昨天晚上一个站长求助明月&#xff0c;说是安装 WP Super Cache 插件的时候提示 advanced-cache.php 被占用了&#xff0c;无法完成安装&#xff0c;收到截图看了才明白原来提示的是“advanced-cache.php 文件&#xff0c;由另一个插件或者系统管理员创建的”&#xff0c;如下图…

【AI界的狼人杀】人工智能“反图灵测试”实验

5月28日&#xff0c; AI Power Users、Unity 独立开发者&#xff1a;Tore Knabe 在其社交平台发布了一则名为《Reverse Turing Test Experiment with AIs》的视频&#xff0c;立马引发了热议。 视频中共出现了五位主要角色&#xff0c;“他们”分别是&#xff1a;亚里士多德&am…

Keil 5恢复默认布局,左边状态栏

第一步&#xff0c;点击windows&#xff1a; 第二步&#xff0c;点击reset view to default&#xff1a; 第三步&#xff0c;点击reset即可&#xff1a;