View->裁剪框View的绘制,手势处理

news2024/11/18 12:33:23

XML文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@color/black">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <com.yang.app.MyRootView
            android:id="@+id/my_root"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="vertical"
            android:layout_marginLeft="60dp"
            android:layout_marginTop="60dp"
            android:layout_marginRight="60dp"
            android:layout_marginBottom="60dp">
        </com.yang.app.MyRootView>
    </LinearLayout>
    <com.yang.app.MyCropView
        android:id="@+id/my_crop"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

Activity代码

const val TAG = "Yang"
class MainActivity : AppCompatActivity() {
    var tempBitmap: Bitmap? = null
    var mRootView: MyRootView? = null
    var mCropView: MyCropView? = null
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val tempRect = RectF(0f, 0f, resources.displayMetrics.widthPixels.toFloat(), resources.displayMetrics.heightPixels.toFloat())
        mCropView = findViewById(R.id.my_crop) as? MyCropView
        mRootView = findViewById<MyRootView?>(R.id.my_root).apply {
            mCropView?.let {
                setRectChangeListener(it)
            }
        }

        CoroutineScope(Dispatchers.IO).launch {
            tempBitmap = getBitmap(resources, tempRect, R.drawable.real)
            withContext(Dispatchers.Main) {
                tempBitmap?.let {
                    // 设置裁剪框的初始位置
                    mCropView?.setOriginBitmapRect(RectF(0f, 0f, it.width.toFloat(), it.height.toFloat()))
                    mRootView?.setOriginBitmap(it)
                }
            }
        }
    }
}

fun getBitmap(resources : Resources, destRect : RectF, imageId: Int): Bitmap? {
    var imageWidth = -1
    var imageHeight = -1
    val preOption = BitmapFactory.Options().apply {
        // 只获取图片的宽高
        inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, imageId, this)
    }
    imageWidth = preOption.outWidth
    imageHeight = preOption.outHeight
    // 计算缩放比例
    val scaleMatrix = Matrix()
    // 确定未缩放Bitmap的RectF
    var srcRect = RectF(0f, 0f, imageWidth.toFloat(), imageHeight.toFloat())
    // 通过目标RectF, 确定缩放数值,存储在scaleMatrix中
    scaleMatrix.setRectToRect(srcRect, destRect, Matrix.ScaleToFit.CENTER)
    // 缩放数值再映射到原始Bitmap上,得到缩放后的RectF
    scaleMatrix.mapRect(srcRect)

    val finalOption = BitmapFactory.Options().apply {
        if (imageHeight > 0 && imageWidth > 0) {
            inPreferredConfig = Bitmap.Config.RGB_565
            inSampleSize = calculateInSampleSize(
                imageWidth,
                imageHeight,
                srcRect.width().toInt(),
                srcRect.height().toInt()
            )
        }
    }
    return BitmapFactory.decodeResource(resources, imageId, finalOption)
}

fun calculateInSampleSize(fromWidth: Int, fromHeight: Int, toWidth: Int, toHeight: Int): Int {
    var bitmapWidth = fromWidth
    var bitmapHeight = fromHeight
    if (fromWidth > toWidth|| fromHeight > toHeight) {
        var inSampleSize = 2
        // 计算最大的inSampleSize值,该值是2的幂,并保持原始宽高大于目标宽高
        while (bitmapWidth >= toWidth && bitmapHeight >= toHeight) {
            bitmapWidth /= 2
            bitmapHeight /= 2
            inSampleSize *= 2
        }
        return inSampleSize
    }
    return 1
}

fun setRectChangeListener(listener: RectChangedListener) {
	mRectChangeListener = listener
}

fun dpToPx(context: Context, dp: Float): Float {
    val metrics = context.resources.displayMetrics
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics)
}

自定义View代码

  • 显示图片的View
class MyRootView constructor(context: Context, attrs: AttributeSet? ) : View(context, attrs) {
    private var lastX = 0f
    private var lastY = 0f
    private val scroller = OverScroller(context)
    private var tracker: VelocityTracker? = null
    private var initialLeft = 0
    private var initialTop = 0
    private var mDestRect: RectF? = null
    private val mScaleMatrix = Matrix()
    private var mRectChangeListener: RectChangedListener? = null
    private var mPaint = Paint().apply {
        isAntiAlias = true
        isFilterBitmap = true
    }

    private var mOriginBitmap: Bitmap? = null

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (initialLeft == 0) initialLeft = left
        if (initialTop == 0) initialTop = top
        mDestRect = RectF(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat())
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                tracker = VelocityTracker.obtain().apply {
                    addMovement(event)
                }
                lastX = event.rawX
                lastY = event.rawY
            }

            MotionEvent.ACTION_MOVE -> {
                if (tracker == null) {
                    tracker = VelocityTracker.obtain()
                    tracker?.addMovement(event)
                }
                val dx = event.rawX - lastX
                val dy = event.rawY - lastY
                val left = left + dx.toInt()
                val top = top + dy.toInt()
                val right = right + dx.toInt()
                val bottom = bottom + dy.toInt()
                layout(left, top, right, bottom)
                lastX = event.rawX
                lastY = event.rawY
            }

            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                // 手指抬起时,根据速度进行惯性滑动
                // (int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)
                // startX, startY:开始滑动的位置
                // velocityX, velocityY:滑动的速度
                // minX, maxX, minY, maxY:滑动的范围
                val parentView = (parent as? View)
                tracker?.computeCurrentVelocity(1000)
                scroller.fling(
                    initialLeft, initialTop,
                    -tracker?.xVelocity?.toInt()!!, -tracker?.yVelocity?.toInt()!!,
                    0, parentView?.width!! - width,
                    0, parentView?.height!! - height,
                    width, height
                )
                tracker?.recycle()
                tracker = null
                invalidate() // 请求重绘View,这会导致computeScroll()被调用
            }
        }
        return true
    }

    override fun computeScroll() {
        if (scroller.computeScrollOffset()) {
            // 更新View的位置
            val left = scroller.currX
            val top = scroller.currY
            val right = left + width
            val bottom = top + height
            layout(left, top, right, bottom)
            if (!scroller.isFinished) {
                invalidate()  // 继续请求重绘View,直到滑动结束
            }
        }
    }

    fun setOriginBitmap(bitmap: Bitmap) {
        mOriginBitmap = bitmap
        invalidate()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        mOriginBitmap?.let {
            setScaleMatrix(it)
            canvas?.drawBitmap(it, mScaleMatrix, mPaint)
            mScaleMatrix.postTranslate(left.toFloat(), top.toFloat())
            mRectChangeListener?.onRectChanged(mScaleMatrix)
        }
    }

    fun setScaleMatrix(bitmap: Bitmap) {
        val scaleX = mDestRect?.width()!! / bitmap.width
        val scaleY = mDestRect?.height()!! / bitmap.height
        val scale = Math.min(scaleX, scaleY)
        val dx = (mDestRect?.width()!! - bitmap.width!! * scale) / 2
        val dy = (mDestRect?.height()!! - bitmap.height!! * scale) / 2
        mScaleMatrix.reset()
        mScaleMatrix.postScale(scale, scale)
        mScaleMatrix.postTranslate(dx, dy)
    }

    fun setRectChangeListener(listener: RectChangedListener) {
        mRectChangeListener = listener
    }
}
  • 裁剪框View
class MyCropView(context: Context, attrs: AttributeSet) : View(context, attrs), RectChangedListener {
    private val mRectLinePaint = Paint().apply {
        isAntiAlias = true
        color = Color.WHITE
        strokeWidth = dpToPx(context, 1.5f)
        style = Paint.Style.STROKE
    }
    private val mCornerAndCenterLinePaint = Paint().apply {
        isAntiAlias = true
        color = Color.RED
        strokeWidth = dpToPx(context, 3f)
        style = Paint.Style.STROKE
    }
    private val mDividerLinePaint = Paint().apply {
        isAntiAlias = true
        color = Color.WHITE
        strokeWidth = dpToPx(context, 3f) / 2f
        alpha = (0.5 * 255).toInt()
        style = Paint.Style.STROKE
    }

    private val mLineOffset = dpToPx(context, 3f) / 2f
    private val mLineWidth = dpToPx(context, 15f)
    private val mCenterLineWidth = dpToPx(context, 18f)
    private val mCoverColor = context.getColor(com.tran.edit.R.color.crop_cover_color)

    // 处理手势
    private var downX = 0f
    private var downY = 0f

    enum class MoveType {
        LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTTOM, LEFT, TOP, RIGHT, BOTTOM
    }
    
    private var mMoveType : MoveType?= null
    private var mOriginBitmapRect = RectF()
    private var mOriginViewRect = RectF()
    private var mInitCropMatrix = Matrix()
    private var mCropMatrix = Matrix()
    private var mMinCropRect = RectF(0f, 0f, 200f , 200f)
    private var mActivePointerId = -1
    
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event?.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                // 记录第一根手指的位置和id
                val pointerIndex = event.actionIndex
                mActivePointerId = event.getPointerId(pointerIndex)
                downX = event.getX(pointerIndex)
                downY = event.getY(pointerIndex)

                val cropRect = getCropRect()
                // 计算初始拖动裁剪框的大致方向
                val leftTopRect = getStartCropCornerRect(cropRect.left, cropRect.top)
                if (leftTopRect.contains(event.x , event.y)) {
                    mMoveType = MoveType.LEFT_TOP
                    return true
                }

                val leftBottomRect = getStartCropCornerRect(cropRect.left, cropRect.bottom)
                if (leftBottomRect.contains(event.x , event.y)) {
                    mMoveType = MoveType.LEFT_BOTTOM
                    return true
                }

                val rightTopRect = getStartCropCornerRect(cropRect.right, cropRect.top)
                if (rightTopRect.contains(event.x , event.y)) {
                    mMoveType = MoveType.RIGHT_TOP
                    return true
                }

                val rightBottomRect = getStartCropCornerRect(cropRect.right, cropRect.bottom)
                if (rightBottomRect.contains(event.x , event.y)) {
                    mMoveType = MoveType.RIGHT_BOTTOM
                    return true
                }

                val leftCenterRect = getStartCropCenterRect(cropRect.left, cropRect.left, cropRect.top, cropRect.bottom)
                if (leftCenterRect.contains(event.x , event.y)) {
                    mMoveType = MoveType.LEFT
                    return true
                }

                val rightCenterRect = getStartCropCenterRect(cropRect.right, cropRect.right, cropRect.top, cropRect.bottom)
                if (rightCenterRect.contains(event.x , event.y)) {
                    mMoveType = MoveType.RIGHT
                    return true
                }

                val topCenterRect = getStartCropCenterRect(cropRect.left, cropRect.right, cropRect.top, cropRect.top)
                if (topCenterRect.contains(event.x , event.y)) {
                    mMoveType = MoveType.TOP
                    return true
                }

                val bottomCenterRect = getStartCropCenterRect(cropRect.left, cropRect.right, cropRect.bottom, cropRect.bottom)
                if (bottomCenterRect.contains(event.x , event.y)) {
                    mMoveType = MoveType.BOTTOM
                    return true
                }
                return true
            }
            
            MotionEvent.ACTION_POINTER_DOWN->{
                // 记录第二根手指的位置和id
                val pointerIndex = event.actionIndex
                mActivePointerId = event.getPointerId(pointerIndex)
                downX = event.getX(pointerIndex)
                downY = event.getY(pointerIndex)
            }
            
            MotionEvent.ACTION_MOVE -> {
                mMoveType ?: return false

                // 如果此时屏幕上有两根手指,这个时候mActivePointerId就是第二根手指的id,不支持多指更新位置
                val pointerIndex = event.findPointerIndex(mActivePointerId)
                if (pointerIndex < 0 || pointerIndex != 0) {
                    return false
                }

                var deltaX = event.getX(pointerIndex) - downX
                var deltaY = event.getY(pointerIndex) - downY

                downX = event.getX(pointerIndex)
                downY = event.getY(pointerIndex)
                val originalRect = getInitCropRect()
                val startCropRect = getCropRect()
                val endCropRect = RectF(startCropRect)

                when (mMoveType) {
                    MoveType.LEFT_TOP -> {
                        endCropRect.left += deltaX
                        endCropRect.top += deltaY
                    }

                    MoveType.LEFT_BOTTOM -> {
                        endCropRect.left += deltaX
                        endCropRect.bottom += deltaY
                    }

                    MoveType.RIGHT_TOP -> {
                        endCropRect.right += deltaX
                        endCropRect.top += deltaY
                    }

                    MoveType.RIGHT_BOTTOM -> {
                        endCropRect.right += deltaX
                        endCropRect.bottom += deltaY
                    }

                    MoveType.LEFT -> {
                        endCropRect.left += deltaX
                    }

                    MoveType.RIGHT -> {
                        endCropRect.right += deltaX
                    }

                    MoveType.TOP -> {
                        endCropRect.top += deltaY
                    }

                    MoveType.BOTTOM -> {
                        endCropRect.bottom += deltaY
                    }

                    else -> {
                        //
                    }

                }
                // 限制不超过初始裁剪框的大小
                endCropRect.left = max(endCropRect.left, originalRect.left)
                endCropRect.top = max(endCropRect.top, originalRect.top)
                endCropRect.right = min(endCropRect.right, originalRect.right)
                endCropRect.bottom = min(endCropRect.bottom, originalRect.bottom)
                if (endCropRect.width() < mMinCropRect.width() || endCropRect.height() < mMinCropRect.height()) {
                    // 将裁剪框的大小调整到最小范围
                    adjustCropRect(endCropRect, mMinCropRect, originalRect)
                    return true
                }
                mCropMatrix.setRectToRect(startCropRect, endCropRect, Matrix.ScaleToFit.FILL)
                invalidate()
                mOriginViewRect.set(getCropRect())
            }
            
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                // 所有手指抬起,重置状态
                downX = -1f
                downY = -1f
                mMoveType = null
                mActivePointerId = -1
            }

            MotionEvent.ACTION_POINTER_UP -> {
                // 假如屏幕上有两根手指
                // 按下第二根,抬起第二根,mActivePointerId == pointerId, 活动手指更新为第一根
                // 按下第二根,抬起第一根,mActivePointerId != pointerId, 活动手指为第二根,不变
                val pointerIndex = event.actionIndex
                val pointerId = event.getPointerId(pointerIndex)
                if (mActivePointerId == pointerId) {
                    // 选择一个新的活动手指
                    val newPointerIndex = if (pointerIndex == 0) 1 else 0
                    mActivePointerId = event.getPointerId(newPointerIndex)
                    downX = event.getX(newPointerIndex)
                    downY = event.getY(newPointerIndex)
                }
            }

        }
        return true
    }

    fun adjustCropRect(rect: RectF, minRect: RectF, maxRect: RectF) {
        if (rect.width() <= minRect.width()) {
            // 当前裁剪框的左右边界加上距离最小裁剪框的距离
            val xOffset = (minRect.width() - rect.width()) / 2
            rect.left -= xOffset
            rect.right += xOffset
            // 如果左边界小于最小裁剪框的左边界,那么左边界就等于最小裁剪框的左边界
            if (rect.left < maxRect.left) {
                rect.offset(maxRect.left - rect.left, 0f)
            }
            if (rect.right > maxRect.right) {
                rect.offset(maxRect.right - rect.right, 0f)
            }
        }
        if (rect.height() <= minRect.height()) {
            // 当前裁剪框的上下边界加上距离最小裁剪框的距离
            val yOffset = (minRect.height() - rect.height()) / 2
            rect.top -= yOffset
            rect.bottom += yOffset

            // 如果上边界小于最小裁剪框的上边界,那么上边界就等于最小裁剪框的上边界
            if (rect.top < maxRect.top) {
                rect.offset(0f, maxRect.top - rect.top)
            }
            if (rect.bottom > maxRect.bottom) {
                rect.offset(0f, maxRect.bottom - rect.bottom)
            }
        }
    }

    fun getStartCropCornerRect(startX : Float, startY : Float): RectF {
        return RectF(startX - mLineWidth, startY - mLineWidth, startX + mLineWidth, startY + mLineWidth)
    }

    fun getStartCropCenterRect(startX : Float, endX : Float, startY : Float, endY : Float): RectF {
        if (startX == endX){
            return RectF(startX - mLineWidth, startY, startX + mLineWidth, endY)
        }else{
            return RectF(startX, startY - mLineWidth, endX, startY + mLineWidth)
        }
    }

    override fun onRectChanged(changedMatrix: Matrix) {
        mInitCropMatrix.set(changedMatrix)
        val initCropRect = RectF(mOriginBitmapRect)
        mInitCropMatrix.mapRect(initCropRect)
        mOriginViewRect.set(initCropRect)
        invalidate()
    }

    fun getOriginViewRect(): RectF {
        return RectF(mOriginViewRect)
    }

    fun getInitCropRect(): RectF {
        val initCropRect = RectF(mOriginBitmapRect)
        mInitCropMatrix.mapRect(initCropRect)
        return initCropRect
    }

    fun getCropRect(): RectF {
        val cropRect = getOriginViewRect()
        mCropMatrix.mapRect(cropRect)
        mCropMatrix.reset()
        return cropRect
    }

    fun setOriginBitmapRect(rectF: RectF){
        mOriginBitmapRect = rectF
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val drawRect = getCropRect()

        drawRect?.let { rect->
            // 1. 绘制遮罩
            canvas.save()
            canvas.clipOutRect(rect)
            canvas.drawColor(Color.argb(mCoverColor.alpha, mCoverColor.red, mCoverColor.green, mCoverColor.blue))
            canvas.restore()

            // 2. 绘制边框
            canvas?.drawRect(rect, mRectLinePaint)

            // 3. 绘制分割线
            val x1 = rect.left + rect.width() / 3
            val x2 = rect.left + rect.width() * 2 / 3
            val y1 = rect.top + rect.height() / 3
            val y2 = rect.top + rect.height() * 2 / 3
            canvas.drawLine(x1, rect.top, x1, rect.bottom, mDividerLinePaint)
            canvas.drawLine(x2, rect.top, x2, rect.bottom, mDividerLinePaint)
            canvas.drawLine(rect.left, y1, rect.right, y1, mDividerLinePaint)
            canvas.drawLine(rect.left, y2, rect.right, y2, mDividerLinePaint)

            // 4. 绘制四个角的折线
            canvas.drawLine(
                rect.left - mLineOffset, rect.top - mLineOffset * 2, rect.left - mLineOffset, rect.top + mLineWidth, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.left - mLineOffset * 2, rect.top - mLineOffset, rect.left + mLineWidth, rect.top - mLineOffset, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.right + mLineOffset, rect.top - mLineOffset * 2, rect.right + mLineOffset, rect.top + mLineWidth, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.right + mLineOffset * 2, rect.top - mLineOffset, rect.right - mLineWidth, rect.top - mLineOffset, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.right + mLineOffset, rect.bottom + mLineOffset * 2, rect.right + mLineOffset, rect.bottom - mLineWidth, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.right + mLineOffset * 2, rect.bottom + mLineOffset, rect.right - mLineWidth, rect.bottom + mLineOffset, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.left - mLineOffset, rect.bottom + mLineOffset * 2, rect.left - mLineOffset, rect.bottom - mLineWidth, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.left - mLineOffset * 2, rect.bottom + mLineOffset, rect.left + mLineWidth, rect.bottom + mLineOffset, mCornerAndCenterLinePaint
            )

            // 5. 绘制四条边的中间线
            canvas.drawLine(
                rect.left - mLineOffset, rect.centerY() - mCenterLineWidth / 2, rect.left - mLineOffset, rect.centerY() + mCenterLineWidth / 2, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.right + mLineOffset, rect.centerY() - mCenterLineWidth / 2, rect.right + mLineOffset, rect.centerY() + mCenterLineWidth / 2, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.centerX() - mCenterLineWidth / 2, rect.top - mLineOffset, rect.centerX() + mCenterLineWidth / 2, rect.top - mLineOffset, mCornerAndCenterLinePaint
            )
            canvas.drawLine(
                rect.centerX() - mCenterLineWidth / 2, rect.bottom + mLineOffset, rect.centerX() + mCenterLineWidth / 2, rect.bottom + mLineOffset, mCornerAndCenterLinePaint
            )
        }
    }
}

效果图

在这里插入图片描述

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

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

相关文章

CFS三层内网渗透——第二层内网打点并拿下第三层内网(三)

目录 八哥cms的后台历史漏洞 配置socks代理 ​以我的kali为例,手动添加 socks配置好了&#xff0c;直接sqlmap跑 ​登录进后台 蚁剑配置socks代理 ​ 测试连接 ​编辑 成功上线 上传正向后门 生成正向后门 上传后门 ​内网信息收集 ​进入目标二内网机器&#xf…

【linux进程】进程地址空间(什么是进程地址空间?为什么要有进程地址空间?)

目录 一、前言 二、 程序的地址空间是真实的 --- 物理空间吗&#xff1f; 三、进程地址空间 &#x1f525; 操作系统是如何建立起进程与物理内存之间的联系的呢&#xff1f; &#x1f525;什么是进程地址空间&#xff1f; &#x1f525;为什么不能直接去访问物理内存&a…

Protobuf(三):理论学习,简单总结

1. Protocol Buffers概述 Protocol Buffers&#xff08;简称protobuf&#xff09;&#xff0c;是谷歌用于序列化结构化数据的一种语言独立、平台独立且可扩展的机制&#xff0c;类似XML&#xff0c;但比XML更小、更快、更简单protobuf的工作流程如图所示 1.1 protobuf的优点…

Python酷库之旅-第三方库Pandas(003)

目录 一、用法精讲 4、pandas.read_csv函数 4-1、语法 4-2、参数 4-3、功能 4-4、返回值 4-5、说明 4-6、用法 4-6-1、创建csv文件 4-6-2、代码示例 4-6-3、结果输出 二、推荐阅读 1、Python筑基之旅 2、Python函数之旅 3、Python算法之旅 4、Python魔法之旅 …

工业废水中镍超标怎么办?含镍废水处理方法有哪些?

镍是一种存在于自然界中的过渡金属。镍在土壤和岩石中的存量丰富&#xff0c;大部分镍已被氧化&#xff0c;或与其他元素结合成化合物。   含镍废水主要来源于电镀、合金制造、金属表面处理、电子等行业。这些行业在生产过程中&#xff0c;通常会使用含有镍离子的化学试剂&a…

kali改回官方源后更新失败

官方源&#xff1a; deb http://http.kali.org/kali kali-rolling main non-free contrib deb-src http://http.kali.org/kali kali-rolling main non-free contrib在文件 /etc/cat/sources.list中将官方源修改为&#xff1a; deb http://http.kali.org/kali kali-rolling ma…

等保测评需要什么SSL证书

在进行信息安全等级保护&#xff08;简称“等保”&#xff09;测评时&#xff0c;选择合适的HTTPS证书对于确保网站的安全性和合规性至关重要。以下是在等保测评中选择HTTPS证书时应考虑的因素&#xff1a; 国产证书&#xff1a; 等保测评倾向于使用国产品牌的SSL证书&#x…

科普文:一文搞懂jvm实战(二)Cleaner回收jvm资源

概叙 在JDK9中新增了Cleaner类&#xff0c;该类的作用是用于替代finalize方法&#xff0c;更有效地释放资源并避免内存泄漏。 在JEP260提案中&#xff0c;封装了大部分Sun包内部的API之余&#xff0c;还引入了一些新的API&#xff0c;其中就包含着Cleaner这个工具类。Cleaner承…

JAVA程序打包时报错,但是运行时正常。

报错&#xff1a;Could not transfer artifact com.alibaba:fastjson:pom:1.2.83 from/to clojars... 背景&#xff1a;需要将fastjson从1.2.70升级到1.2.83&#xff1b;并且编译环境是局域网不可以连接互联网&#xff1b;每个项目组都是独立的私有仓库。 操作&#xff1a;在本…

品牌推广的深层逻辑:自我提升与市场认同的和谐共生

品牌推广的深层逻辑&#xff1a;自我提升与市场认同的和谐共生 著名飞行员查尔斯林德伯格(Charles Lindbergh) 曾写道:“改善生活方式比传播生活方式更重要。如果我们自己的生活方式使别人感到满意&#xff0c;那么它将自动蔓延。如果不是这样&#xff0c;那么任何武力都不可能…

kotlin协程的理解

伴生对象&#xff1a;companion object 其实质等同于Java中的单例模式 协程&#xff1a;通常实现是用户态的任务协作式调度 一段可执行代码可挂起/可恢复执行概念上与语言无关&#xff0c;协程这个概念于1958年提出 依赖框架&#xff1a; 协程的启动&#xff1a; 1.协程体&a…

【SVN的使用-源代码管理工具-命令行的使用 Objective-C语言】

一、接下来,我们来说一个终端的命令行的使用, 1.我们说,你的电脑里边呢,有终端, 在Mac里边,你想新建一个txt,应该怎么写,对,打开文本编辑, 打开这个东西,写点儿东西,然后保存一下,保存的时候,你还要去选择格式, 现在,如果我们用命令行,可以更方便一些, 2.首…

SCT612404通道,高效高集成,摄像头模组电源集成芯片

集成三路降压变换器&#xff0c;1CH高压BUCK,2CH低压Buck >HVBuck1:输入电压4.0V-20V,输出电流1.2A,Voo300mV/500mV >LVBuck2:输入电压2.7V-5V,输出电流0.6A , 固定1.8V输出 ;LVBuck3:输λ2.7V-5V,输出电流1.2A,可设定固定输出&#xff1a; 1 . 1 V / 1 . 2 V / 1 . 3 …

vite项目配置svg图标(vite-plugin-svg-icons)

1.插件地址 网址 , 可以去里面查看中文文档,里面有详情的教程 2.使用, 如果你安装的有element-plus ,可以使用这样的方式来修改大小和颜色 <el-icon size"18" color"red"><SvgIcon name"xing"></SvgIcon></el-icon> …

MViT(ICCV 2021, Meta)论文解读

paper&#xff1a;Multiscale Vision Transformers official implementation&#xff1a;https://github.com/facebookresearch/SlowFast 背景和出发点 这篇文章提出了多尺度视觉Transformer&#xff08;Multiscale Vision Transformers, MViT&#xff09;的概念&#xff0c…

nftables(1)基本原理

简介 nftables 是 Linux 内核中用于数据包分类的现代框架&#xff0c;用来替代旧的 iptables&#xff08;包括 ip6tables, arptables, ebtables 等&#xff0c;统称为 xtables&#xff09;架构。nftables 提供了更强大、更灵活以及更易于管理的规则集配置方式&#xff0c;使得…

中国1km高分辨率高质量逐年近地表CO数据集(2013-2022年)

该数据为中国高分辨率高质量逐年CO数据集&#xff0c;该数据集主要的空间范围覆盖整个中国&#xff0c;其中内容包括中国1km高分辨率高质量逐年CO数据集(2013-2022年)。时间分辨率为年&#xff0c;单位为mg/m3&#xff0c;数据以(.nc/.tif)格式进行存储。

Vscode快捷键崩溃

Vscode快捷键崩溃 Linux虚拟机下使用vscode写代码【ctrlA&#xff0c;CtrlC&#xff0c;CtrlV】等快捷键都不能使用&#xff0c;还会出现“NO text insert“等抽象的指令&#xff0c;问题就是不知道什么时候装了一个VIM插件&#xff0c;让他滚出电脑》》》

快速傅里叶变换(Fast Fourier Transform)

快速算法&#xff08;FFT&#xff09;&#xff0c;即快速傅里叶变换&#xff08;Fast Fourier Transform&#xff09;&#xff0c;是一种用于计算离散傅里叶变换&#xff08;DFT&#xff09;及其逆变换的高效算法。FFT算法由J.W.库利和T.W.图基于1965年提出&#xff0c;显著减少…

T100-XG查询报表的开发

制作XG报表 1、注册程序 azzi900 首先现将程序注册一下,在内部构建基础代码档。 2、注册作业 azzi910 也是直接新增一个,作业跟程序绑定一下。 3、T100签出规格程序 这个时候应该是没签出的,首先将规格迁出。 4、T100画面产生器 规格迁出之后,这个时候还需要生成一个画…