自定义Android滑块拼图验证控件

news2024/9/27 15:32:07

自定义Android滑块拼图验证控件

      • 拼图认证视图
      • 默认策略
      • 工具类
      • 参考

1、继承自AppCompatImageView,兼容ImageView的scaleType设置,可设置离线/在线图片。
2、通过设置滑块模型(透明背景的图形块)设置滑块(和缺省块)样式,可修改缺省块颜色。
效果图

拼图认证视图

class PictureVerifyView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatImageView(
    context, attrs, defStyleAttr
) {

    private var mState = STATE_IDEL //当前状态

    // right bottom 禁用
    private var piercedPositionInfo: RectF? = null //拼图缺块阴影的位置

    // right bottom 禁用
    private var thumbPositionInfo: RectF? = null //拼图缺块的位置
    private var thumbBlock: Bitmap? = null //拼图缺块Bitmap
    private var piercedBlock: Bitmap? = null
    private var thumbPaint: Paint? = null//绘制拼图滑块的画笔
    private var piercedPaint: Paint? = null//绘制拼图缺块的画笔
    private var startTouchTime: Long = 0 //滑动/触动开始时间
    private var looseTime: Long = 0 //滑动/触动松开时间
    private var blockSize = DEF_BLOCK_SIZE
    private var mTouchEnable = true //是否可触动
    private var callback: Callback? = null
    private var mStrategy: CaptchaStrategy? = null
    private var mMode = Captcha.MODE_BAR //Captcha验证模式
    private val xModeDstIn = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
    private var isReversal = false
    private var middlewarePaint: Paint? = Paint()
    private val srcRect = Rect()
    private val dstRect = RectF()

    override fun onDetachedFromWindow() {
        mStrategy?.onDetachedFromWindow()
        thumbBlock?.recycle()
        piercedBlock?.recycle()
        thumbBlock = null
        thumbPaint = null
        piercedPositionInfo = null
        thumbPositionInfo = null
        callback = null
        piercedPaint = null
        middlewarePaint = null
        super.onDetachedFromWindow()
    }

    interface Callback {
        fun onSuccess(time: Long)
        fun onFailed()
    }

    private var tempX = 0f
    private var tempY = 0f
    private var downX = 0f
    private var downY = 0f

    init {
        setCaptchaStrategy(DefaultCaptchaStrategy(context))
    }

    private fun initDrawElements() {
        // 创建缺省镂空位置
        piercedPositionInfo ?: mStrategy?.getPiercedPosition(width, height, blockSize)
            ?.also {
                piercedPositionInfo = it
                thumbPositionInfo =
                    mStrategy?.getThumbStartPosition(width, height, blockSize, mMode, it)
            }

        // 创建滑块
        thumbBlock ?: createBlockBitmap().apply {
            thumbBlock = this
        }
    }

    private fun getBlockWidth() = if (isReversal) blockSize.height else blockSize.width
    private fun getBlockHeight() = if (isReversal) blockSize.width else blockSize.height

    private fun getRealBlockWidth() =
        getBlockWidth() + (mStrategy?.getThumbShadowInfo()?.size?.toFloat() ?: 0f)

    private fun getRealBlockHeight() =
        getBlockHeight() + (mStrategy?.getThumbShadowInfo()?.size?.toFloat() ?: 0f)

    /**
     * 生成拼图滑块和阴影图片
     */
    private fun createBlockBitmap(): Bitmap {
        // 获取背景图
        val origBitmap = getOrigBitmap()

        // 获取滑块模板
        val templateBitmap = getTempBitmap()

        if (blockSize.width != blockSize.height) {
            isReversal = templateBitmap.width == blockSize.height.toInt()
        }

        val resultBmp = Bitmap.createBitmap(
            getBlockWidth().toInt(),
            getBlockHeight().toInt(),
            Bitmap.Config.ARGB_8888
        )

        // 创建滑块画板
        middlewarePaint?.run {
            reset()
            isAntiAlias = true

            val canvas = Canvas(resultBmp)

            // 裁剪镂空位置
            val cropLeft = ((piercedPositionInfo?.left)?.toInt() ?: 0)
            val cropTop = ((piercedPositionInfo?.top)?.toInt() ?: 0)

            srcRect.set(
                cropLeft,
                cropTop,
                cropLeft + getBlockWidth().toInt(),
                cropTop + getBlockHeight().toInt()
            )
            dstRect.set(0f, 0f, getBlockWidth(), getBlockHeight())

            // 从原图上rect区间裁剪与画板上rectR区域重叠
            canvas.drawBitmap(
                origBitmap,
                srcRect,
                dstRect,
                this
            )
            srcRect.set(0, 0, getBlockWidth().toInt(), getBlockHeight().toInt())
            // 选择交集取上层图片
            xfermode = xModeDstIn
            // 绘制底层模板dst
            canvas.drawBitmap(
                templateBitmap,
                srcRect,
                dstRect,
                this
            )
        }

        return getRealThumbBitmap(resultBmp).apply {
            createPiercedBitmap(templateBitmap)
            origBitmap.recycle()
        }
    }

    // 获取缺省模板模型
    private fun getTempBitmap() = mStrategy?.getThumbBitmap(blockSize)
        ?: Utils.getBitmap(R.drawable.capt_def_puzzle, blockSize)

    private fun getOrigBitmap() =
        Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
            val canvasOrig = Canvas(this)
            // 复原ImageView中显示操作 防止缺省位置错位
            canvasOrig.concat(imageMatrix)
            drawable.draw(canvasOrig)
        }

    /**
     * 设置带阴影的滑块
     */
    private fun getRealThumbBitmap(resultBmp: Bitmap) =
        mStrategy?.getThumbShadowInfo()?.run {
            Utils.addShadow(resultBmp, this)
        } ?: resultBmp

    /**
     * 获取滑块图片
     */
    private fun createPiercedBitmap(templateBitmap: Bitmap) {
        piercedBlock = (mStrategy?.piercedColor() ?: Color.TRANSPARENT).let {
            if (it == Color.TRANSPARENT) {
                templateBitmap
            } else {
                createColorBitmap(templateBitmap, it)
            }
        }
    }

    /**
     * 获取滑块模型形状的纯色图片
     */
    private fun createColorBitmap(templateBitmap: Bitmap, color: Int, isRecycle: Boolean = true) =
        Bitmap.createBitmap(
            getBlockWidth().toInt(),
            getBlockHeight().toInt(),
            Bitmap.Config.ARGB_8888
        ).apply {
            val c = Canvas(this)
            c.drawColor(color)
            middlewarePaint?.run {
                reset()
                xfermode = xModeDstIn
                // 从原图上rect区间裁剪与画板上rectR区域重叠
                c.drawBitmap(
                    templateBitmap,
                    srcRect,
                    dstRect,
                    this
                )
                if (isRecycle) {
                    templateBitmap.recycle()
                }
            }
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        initDrawElements()
        if (mState != STATE_ACCESS) {
            // 绘制缺块位置
            piercedPaint?.runWith(piercedPositionInfo, piercedBlock) { p, i, b ->
                if (mStrategy?.drawPiercedBitmap(canvas, p, i, b) != true) {
                    canvas.drawBitmap(b, i.left, i.top, p)
                }
            }
        }
        if (mState == STATE_MOVE || mState == STATE_IDEL || mState == STATE_DOWN || mState == STATE_UNACCESS) {
            // 绘制滑块
            thumbPaint?.runWith(thumbPositionInfo, thumbBlock) { p, i, b ->
                if (mStrategy?.drawThumbBitmap(canvas, p, i, b) != true) {
                    val offset = (mStrategy?.getThumbShadowInfo()?.size?.toFloat()) ?: 0f
                    canvas.drawBitmap(
                        b,
                        (i.left - offset).coerceAtLeast(0f),
                        (i.top - offset).coerceAtLeast(0f),
                        p
                    )
                }
            }
        }
    }

    private fun Paint.runWith(
        t: RectF?,
        bm: Bitmap?,
        block: (Paint, RectF, Bitmap) -> Unit
    ): Paint {
        return this.also { p ->
            t?.let { rect ->
                bm?.let { b ->
                    if (!b.isRecycled) {
                        block(p, rect, b)
                    }
                }
            }
        }
    }

    /**
     * 按下滑动条(滑动条模式)
     */
    fun down(progress: Int) {
        if (isEnabled) {
            startTouchTime = System.currentTimeMillis()
            mState = STATE_DOWN
            thumbPositionInfo?.left = progress / 100f * (width - getRealBlockWidth())
            invalidate()
        }
    }

    /**
     * 触动拼图块(触动模式)
     */
    private fun downByTouch(x: Float, y: Float) {
        if (isEnabled) {
            mState = STATE_DOWN
            thumbPositionInfo?.run {
                left = x - getRealBlockWidth() / 2f
                top = y - getRealBlockHeight() / 2f
            }
            startTouchTime = System.currentTimeMillis()
            invalidate()
        }
    }

    /**
     * 移动拼图缺块(滑动条模式)
     */
    fun move(progress: Int) {
        if (isEnabled) {
            mState = STATE_MOVE
            thumbPositionInfo?.left = progress / 100f * (width - getRealBlockWidth())
            invalidate()
        }
    }

    /**
     * 触动拼图缺块(触动模式)
     */
    private fun moveByTouch(offsetX: Float, offsetY: Float) {
        if (isEnabled) {
            mState = STATE_MOVE
            thumbPositionInfo?.run {
                left = (left + offsetX.toInt()).coerceAtMost(width - getRealBlockWidth())
                top = (top + offsetY.toInt()).coerceAtMost(height - getRealBlockHeight())
            }
            invalidate()
        }
    }

    /**
     * 松开
     */
    fun loose() {
        if (isEnabled) {
            mState = STATE_LOOSEN
            looseTime = System.currentTimeMillis()
            checkAccess()
            invalidate()
        }
    }

    /**
     * 复位
     */
    fun reset() {
        mState = STATE_IDEL
        thumbPositionInfo = null
        thumbBlock?.recycle()
        thumbBlock = null
        piercedBlock?.recycle()
        piercedBlock = null
        isReversal = false
        piercedPositionInfo = null
        invalidate()
    }

    fun unAccess() {
        mState = STATE_UNACCESS
        invalidate()
    }

    fun access() {
        mState = STATE_ACCESS
        invalidate()
    }

    fun callback(callback: Callback?) {
        this.callback = callback
    }

    fun setCaptchaStrategy(strategy: CaptchaStrategy) {
        mStrategy = strategy
        thumbPaint = strategy.thumbPaint
        piercedPaint = strategy.piercedPaint
        setLayerType(LAYER_TYPE_SOFTWARE, thumbPaint)
        if (!isInLayout) {
            invalidate()
        }
    }

    fun setBlockSize(size: SizeF) {
        blockSize = size
        reset()
    }

    fun setBitmap(bitmap: Bitmap?) {
        setImageBitmap(bitmap)
    }

    override fun setImageBitmap(bm: Bitmap?) {
        super.setImageBitmap(bm)
        reset()
    }

    override fun setImageDrawable(drawable: Drawable?) {
        super.setImageDrawable(drawable)
        reset()
    }

    override fun setImageURI(uri: Uri?) {
        super.setImageURI(uri)
        reset()
    }

    override fun setImageResource(resId: Int) {
        super.setImageResource(resId)
        reset()
    }

    fun setMode(@Captcha.Mode mode: Int) {
        mMode = mode
        isEnabled = true
        reset()
    }

    fun setTouchEnable(enable: Boolean) {
        mTouchEnable = enable
    }

    private fun getFaultTolerant() = (mStrategy?.getFaultTolerant()) ?: DEF_TOLERANCE

    /**
     * 检测是否通过
     */
    private fun checkAccess() {
        thumbPositionInfo?.let { info ->
            piercedPositionInfo?.run {
                val faultTolerant = getFaultTolerant()
                if (abs(info.left - left) < faultTolerant && abs(
                        info.top - top
                    ) < faultTolerant
                ) {
                    access()
                    callback?.onSuccess(looseTime - startTouchTime)
                } else {
                    unAccess()
                    callback?.onFailed()
                }
            }
        }
    }

    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
        //触动模式下,点击超出拼图缺块的区域不进行处理
        thumbPositionInfo?.let {
            if (event.action == MotionEvent.ACTION_DOWN
                && mMode == Captcha.MODE_NONBAR
                && (event.x < it.left || event.x > it.left + getRealBlockWidth() || event.y < it.top || event.y > it.top + getRealBlockHeight())
            ) {
                return false
            }
        }
        return super.dispatchTouchEvent(event)
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (mMode == Captcha.MODE_NONBAR && mTouchEnable && isEnabled) {
            thumbBlock?.run {
                val x = event.x
                val y = event.y
                when (event.action) {
                    MotionEvent.ACTION_DOWN -> {
                        downX = x
                        downY = y
                        downByTouch(x, y)
                    }

                    MotionEvent.ACTION_UP -> loose()
                    MotionEvent.ACTION_MOVE -> {
                        val offsetX = x - tempX
                        val offsetY = y - tempY
                        moveByTouch(offsetX, offsetY)
                    }
                }
                tempX = x:
                tempY = y
            }
        }
        return true
    }

    companion object {
        //状态码
        private const val STATE_DOWN = 1
        private const val STATE_MOVE = 2
        private const val STATE_LOOSEN = 3
        private const val STATE_IDEL = 4
        private const val STATE_ACCESS = 5
        private const val STATE_UNACCESS = 6
        internal const val DEF_TOLERANCE = 10 //验证的最大容差
        internal val DEF_BLOCK_SIZE = SizeF(50f, 50f) //验证的最大容差
    }
}

默认策略

class DefaultCaptchaStrategy(ctx: Context) : CaptchaStrategy(ctx) {
    private val degreesList = arrayListOf(0, 90, 180, 270)
    private val defBound: ShadowInfo =
        ShadowInfo(SizeUtils.dp2px(3.0f), Color.BLACK,SizeUtils.dp2px(2.0f).toFloat())

    // 滑块模型
    override fun getThumbBitmap(blockSize: SizeF): Bitmap {
        return Utils.getBitmap(
            R.drawable.capt_def_puzzle,
            blockSize,
            getDegrees()
        )
    }

    override fun getThumbShadowInfo() = defBound // 滑块阴影信息

    // 缺省位置
    override fun getPiercedPosition(width: Int, height: Int, blockSize: SizeF): RectF {
        val random = Random()
        val size =
            blockSize.width.coerceAtLeast(blockSize.height).toInt() + getThumbShadowInfo().size
        val left = (random.nextInt(width - size)
            .coerceAtLeast(size)).toFloat()
        val top = (random.nextInt(height - size)
            .coerceAtLeast(getThumbShadowInfo().size)).toFloat()
        return RectF(left, top, 0f, 0f)
    }

    private fun getDegrees(): Int {
        val random = Random()
        return degreesList[random.nextInt(degreesList.size)]
    }

    // 滑块初始位置
    override fun getThumbStartPosition(
        width: Int,
        height: Int,
        blockSize: SizeF,
        mode: Int,
        thumbPosition: RectF
    ): RectF {
        var left = 0f
        val top: Float
        val maxSize = blockSize.width.coerceAtLeast(blockSize.height).toInt()
        if (mode == Captcha.MODE_BAR) {
            top = thumbPosition.top
        } else {
            val random = Random()
            val size = maxSize + getThumbShadowInfo().size
            left = (random.nextInt(width - size)
                .coerceAtLeast(getThumbShadowInfo().size)).toFloat()
            top = (random.nextInt(height - size)
                .coerceAtLeast(getThumbShadowInfo().size)).toFloat()
        }
        return RectF(left, top, 0f, 0f)
    }

    override val thumbPaint: Paint
        get() = Paint().apply {
            isAntiAlias = true
        }

    override val piercedPaint: Paint
        get() = Paint().apply {
            isAntiAlias = true
        }

    override fun drawThumbBitmap(canvas: Canvas, paint: Paint, info: RectF, src: Bitmap): Boolean {
        return false
    }

    override fun drawPiercedBitmap(
        canvas: Canvas,
        paint: Paint,
        info: RectF,
        src: Bitmap
    ): Boolean {
        return false
    }

    // 缺省块颜色
    override fun piercedColor(): Int {
        return ResourcesUtils.getColor(R.color.black_a6)
    }

   // 验证可冗余空间
    override fun getFaultTolerant(): Int {
        return SizeUtils.dp2px(10.0f)
    }
}

工具类

object Utils {

    /**
     * 获取指定大小、指定旋转角度的图片
     */
    @JvmStatic
    fun getBitmap(@DrawableRes resId: Int, size: SizeF, degrees: Int = 0): Bitmap {
        val options = BitmapFactory.Options()
        options.inMutable = true
        val newWidth = size.width.toInt()
        val newHeight = size.height.toInt()
        return ImageUtils.scale(
            BitmapFactory.decodeResource(
                ResourcesUtils.getResources(),
                resId,
                options
            ), newWidth, newHeight, true
        ).let {
            if (degrees > 0) ImageUtils.rotate(
                it,
                degrees, newWidth / 2f, newHeight / 2f, true
            ) else it
        }
    }

    /**
     * 给图片添加阴影
     */
    @JvmStatic
    fun addShadow(
        srcBitmap: Bitmap,
        info: ShadowInfo
    ): Bitmap? {
        val w = 2 * info.size + info.dx.toInt()
        val h = 2 * info.size + info.dy.toInt()
        val dstWidth = srcBitmap.width + w
        val dstHeight = srcBitmap.height + h
        val mask = Bitmap.createBitmap(dstWidth, dstHeight, Bitmap.Config.ALPHA_8)
        val scaleToFit = Matrix()
        val src = RectF(0f, 0f, srcBitmap.width.toFloat(), srcBitmap.height.toFloat())
        val dst = RectF(
            info.size.toFloat(),
            info.size.toFloat(),
            dstWidth - info.size - info.dx,
            dstHeight - info.size - info.dy
        )
        scaleToFit.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER)
        val dropShadow = Matrix(scaleToFit)
        dropShadow.postTranslate(info.dx, info.dy)
        val maskCanvas = Canvas(mask)
        val paint = Paint(Paint.ANTI_ALIAS_FLAG)
        maskCanvas.drawBitmap(srcBitmap, scaleToFit, paint)
        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
        maskCanvas.drawBitmap(srcBitmap, dropShadow, paint)
        //设置阴影
        val filter = BlurMaskFilter(info.size.toFloat(), BlurMaskFilter.Blur.NORMAL)
        paint.reset()
        paint.isAntiAlias = true
        paint.color = info.color
        paint.maskFilter = filter
        paint.isFilterBitmap = true
        val ret = Bitmap.createBitmap(dstWidth, dstHeight, Bitmap.Config.ARGB_8888)
        val retCanvas = Canvas(ret)
        //绘制阴影
        retCanvas.drawBitmap(mask, 0f, 0f, paint)
        retCanvas.drawBitmap(srcBitmap, scaleToFit, null)
        mask.recycle()
        return ret
    }
}

参考

Android拼图滑块验证码控件:http://blog.csdn.net/sdfsdfdfa/article/details/79120665
关于android:绘制图像时绘制外部阴影:https://www.codenong.com/17783467/
Paint API之—— Xfermode与PorterDuff详解:https://www.kancloud.cn/kancloud/android-tutorial/87249

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

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

相关文章

IDEA 运行Application时出错,命令行过长

解决方案&#xff1a; 第一步选择编辑配置&#xff1a; 第二步选择配置&#xff1a; 第三步&#xff1a;选择JAR清单

MySQL- sql语句基础

文章目录 1.select后对表进行修改&#xff08;delete&#xff09;2.函数GROUP_CONCAT()3.使用正则表达式 1.select后对表进行修改&#xff08;delete&#xff09; 报错&#xff1a;You can’t specify target table ‘Person’ for update in FROM clause 原因&#xff1a;mys…

vue3自定义样式-路由-axios拦截器

基于vue,vite和elementPlus 基于elementPlus自定义样式 history模式的路由 在根目录配置jsconfig.json&#xff0c;添加json的配置项。输入自动联想到src目录&#xff0c;是根路径的别名拦截器 如果存在多个接口地址&#xff0c;可以配置多个axios实例 数据持久化之后&#x…

网络安全--linux下Nginx安装以及docker验证标签漏洞

目录 一、Nginx安装 二、docker验证标签漏洞 一、Nginx安装 1.首先创建Nginx的目录并进入&#xff1a; mkdir /soft && mkdir /soft/nginx/cd /soft/nginx/ 2.下载Nginx的安装包&#xff0c;可以通过FTP工具上传离线环境包&#xff0c;也可通过wget命令在线获取安装包…

服务监控平台:SpringBoot Admin入门应用

前言 在日常工作中&#xff0c;我们需要有一款监控平台来帮助我们管理服务&#xff0c;监控服务是否宕机、服务运行指标&#xff08;内存、虚拟机、线程、请求等&#xff09;、监控日志、管理服务&#xff08;服务下线&#xff09;等&#xff0c;SpringBoot Admin作为一款开源…

从0搭建ECG深度学习网络

本篇博客介绍使用Python语言的深度学习网络&#xff0c;从零搭建一个ECG深度学习网络。 任务 本次入门的任务是&#xff0c;筛选出MIT-BIH数据集中注释为[‘N’, ‘A’, ‘V’, ‘L’, ‘R’]的数据作为本次数据集&#xff0c;然后按照8&#xff1a;2的比例划分为训练集&…

【QT+ffmpeg】QT+ffmpeg 环境搭建

1.qt下载地址 download.qt.io/archive/ 2. win10sdk 下载 https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ 安装 debug工具路径 qtcreater会自动识别 调试器选择

微服务-Fegin

在之前我们两服务之间调用的时候用的是restTemplate,但是这个方式调用存在很多的问题 String url "http://userservice/user/" order.getUserId(); 代码可读性差&#xff0c;编码体验不统一参数复杂的url难以维护 所以我们大力推出我们今天的主角--Fegin Feign是…

大数据时代下的GIS:数据驱动的地理智能

在当今信息时代&#xff0c;大数据技术的飞速发展正深刻影响着各个领域&#xff0c;地理信息系统&#xff08;GIS&#xff09;作为其中之一&#xff0c;也在大数据的浪潮下展现出了新的活力和潜力。 大数据为GIS注入了强大的数据能量 过去&#xff0c;地理信息数据的收集和存储…

B站一个月内涨粉57万,情景剧再迎黑马UP主

飞瓜数据&#xff08;B站版&#xff09;【达人排行榜】-【涨粉榜】显示&#xff0c;继上次的【成长榜】第三之后&#xff0c;UP主-显眼宝-在本周登上了涨粉榜第三的位置&#xff0c;单周涨粉就超过22万。 从UP主的粉丝趋势图来看&#xff0c;自7月起UP主账号就多次出现涨粉峰值…

Sealos 国内集群正式上线,可一键运行 LLama2 中文版大模型!

2023 年 7 月 19 日&#xff0c;MetaAI 宣布开源旗下的 LLama2 大模型&#xff0c;Meta 首席科学家、图灵奖得主 Yann LeCun 在推特上表示 Meta 此举可能将改变大模型行业的竞争格局。一夜之间&#xff0c;大模型格局再次发生巨变。 不同于 LLama&#xff0c;LLama2 免费可商用…

【仿写框架之仿写Tomact】三、使用socket监听配置文件中的端口接收HTTP请求并创建线程池处理请求

文章目录 1、自定义配置文件2、使用DOM解析XML文件3、创建Tomcat启动方法&#xff08;解析配置文件、创建线程池、socket循环监听端口&#xff09; 1、自定义配置文件 首先在main文件下创建资源目录resources&#xff1a; 在resource目录下创建server.xml文件&#xff0c;并写…

第二章-自动驾驶卡车-自动驾驶卡车前装量产的要求

1、自动驾驶卡车的特点与挑战 重卡主要运行在相对封闭的高速公路&#xff0c;相较城市道路场景看似更简单。但是&#xff0c;由于重卡特有的物理特性、运行环境和商业运营要求&#xff0c;相较于乘用车的自动驾驶系统&#xff0c;重卡的自动驾驶系统对车辆的感知距离和精度、系…

【操作系统】24王道考研笔记——第一章 计算机系统概述

第一章 计算机系统概述 一、操作系统基本概念 1.1 定义 1.2 特征 并发 &#xff08;并行&#xff1a;指两个或多个事件在同一时刻同时发生&#xff09; 共享 &#xff08;并发性指计算机系统中同时存在中多个运行着的程序&#xff0c;共享性指系统中的资源可供内存中多个并…

Vue实现动态可视化

1、可视化效果&#xff1a; 水晶球环绕地球旋转&#xff1b; 2、实现 <template><div class"container"><div class"header-body"><div class"header-left"></div><div class"header-title">网…

删除了很久的备忘录怎么恢复?快速恢复误删备忘录的方法

习惯于经常使用手机来记录事情的网友&#xff0c;应该都对备忘录软件不陌生&#xff0c;因为我们可以直接在备忘录中记录心情日记、读书笔记、生活琐事以及其他重要的事情&#xff0c;这样就不用担心自己会忘记了。不过也有一些备忘录用户表示自己在整理无效备忘录时&#xff0…

gtsam使用-Pose2 SLAM

Pose2 SLAM Pose2 SLAM是一种昂进行同时定位与地图构建&#xff08;SLAM&#xff09;的一种简单方法是仅仅融合连续机器人姿态之间的相对姿态测量。这种不涉及地标的SLAM变体通常被称为 "Pose SLAM"。 %pip -q install gtbook # also installs latest gtsam pre-re…

安防监控视频汇聚平台EasyCVR视频平台调用iframe地址无法播放的问题解决方案

安防监控视频汇聚平台EasyCVR基于云边端一体化架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;可提供视频监控直播、云端录像、视频云存储、视频集中存储、视频存储磁盘阵列、录像检索与回看、智能告警、平台级联、云台控制、语音对讲、AI算法中台智能分析无缝…

Docker容器:docker基础概述、docker安装、docker网络

文章目录 一.docker容器概述1.什么是容器2. docker与虚拟机的区别2.1 docker虚拟化产品有哪些及其对比2.2 Docker与虚拟机的区别 3.Docker容器的使用场景4.Docker容器的优点5.Docker 的底层运行原理6.namespace的六项隔离7.Docker核心概念 二.Docker安装 及管理1.安装 Docker1.…

[C++] string类的介绍与构造的模拟实现,进来看吧,里面有空调

文章目录 1、string类的出现1.1 C语言中的字符串 2、标准库中的string类2.1 string类 3、string类的常见接口说明及模拟实现3.1 string的常见构造3.2 string的构造函数3.3 string的拷贝构造3.4 string的赋值构造 4、完整代码 1、string类的出现 1.1 C语言中的字符串 C语言中&…