(UI)Android自定义图片裁剪

具体UI效果如下:

f146e9f44c754834ab319c75e4d51e2e.jpeg

思路

绘制5个rect,其中四个为半透明深色背景,一个为透明背景的裁剪内容框

之前也考虑过用region,但是自测的时候,发现两个region之间颜色会相互影响,可能是我代码问题(有了解的小伙伴可以指导一下哈),就用了5个Rect来绘制开发效率会更高一些。

2023年8月24号更新:

考虑到图片裁剪定位思路比较复杂,这里把最新的定位代码也贴上来!

具体代码如下:

package com.xingzhi.customview

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View


/**
 *
 *Date:2023/5/13
 *Time:20:08
 *author:wushengqi
 *
 * https://blog.csdn.net/qq_39312146/article/details/129053307
 *
 * https://blog.csdn.net/yihonglvyu1/article/details/122089901
 *
 * Region:
 * https://blog.csdn.net/qq_27061049/article/details/104534867
 *
 * 多点触控:
 * https://blog.51cto.com/u_15749390/5570836
 */
class ImageCropView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
    View(context, attrs, defStyleAttr, defStyleRes) {

    companion object{
        val TAG = ImageCropView::class.java.simpleName
    }

    init {
        init()
    }

    constructor(context: Context?):this(context, attributeSet = null){

    }

    constructor(context: Context?, attributeSet: AttributeSet?) : this(context, attributeSet, defStyleAttr = 0) {

    }

    constructor(context: Context?, attributeSet: AttributeSet?, defStyleAttr:Int) : this(context, attributeSet, defStyleAttr, defStyleRes = 0){

    }

    private lateinit var mBitmap: Bitmap//图片
    private var mBitmapRect:Rect? = null//图片绘制大小
    private var mCropRect:Rect? = null//裁剪框
    private var mPaint:Paint? = null //画笔
    private var mBgPaint:Paint? = null //裁剪框之外的颜色
    private var mCenterPaint:Paint? = null //裁剪框中心的颜色
    private var mCropX:Float = 0.0f//裁剪框的X坐标
    private var mCropY:Float = 0.0f//裁剪框的Y坐标
    private var mCropWidth:Float = 0F//裁剪框的宽度
    private var mCropHeight:Float = 0F//裁剪框的高度
    private var mOldX:Float = 0.0f//上一次触摸的X坐标
    private var mOldY:Float = 0.0f//上一次触摸的Y坐标
    private var mOldCropX:Float = 0.0f//上一次裁剪框的X坐标
    private var mOldCropY:Float = 0.0f//上一次裁剪框的Y坐标

    private var drawBitmapHeight = 0F
    private var drawBitmapWidth = 0F
    private var drawBitmapLeft = 0F
    private var drawBitmapTop = 0F
    private var drawBitmapRight = 0F
    private var drawBitmapBottom = 0F

    private var firstDraw = true
    private var initCropWidth = 0
    private var initCropHeight = 0

    private fun init() {
        mPaint = Paint()
        mPaint!!.style = Paint.Style.STROKE
        mPaint!!.strokeWidth = 4f
        mPaint!!.color = Color.WHITE

        mBgPaint = Paint()
        mBgPaint!!.style = Paint.Style.FILL
        mBgPaint!!.color = Color.parseColor("#AA000000")

        mCenterPaint = Paint()
        mCenterPaint!!.style = Paint.Style.FILL
        mCenterPaint!!.color = Color.parseColor("#00000000")

    }

    //设置图片
    fun setBitmap(bitmap: Bitmap) {
        mBitmap = bitmap
        invalidate()
    }

    /**
     * 绘制bitmap的基准
     */
    enum class FillBitmapStand{
        WIDTH_FILL, //填充满控件宽度
        HEIGHT_FILL //填充满控件高度
    }

    var mFillStand = FillBitmapStand.WIDTH_FILL

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (mBitmap != null) {

            Log.d(TAG, "onDraw")
            val bitmapWidth = mBitmap.width
            val bitmapHeight = mBitmap.height

            // 如果图片的高度和控件实际高度比大于宽度比,则高度填充满整个屏幕,宽度按比例缩放
            if (bitmapHeight.toFloat()/ measuredHeight > bitmapWidth / measuredWidth){
                drawBitmapHeight = measuredHeight.toFloat()
                drawBitmapWidth = ((bitmapWidth * ( measuredHeight.toFloat()/bitmapHeight)))

                drawBitmapLeft = (measuredWidth - drawBitmapWidth) / 2
                drawBitmapTop = 0F

                mFillStand = FillBitmapStand.HEIGHT_FILL

            }else{
                drawBitmapWidth = measuredWidth.toFloat()
                drawBitmapHeight = (bitmapHeight * (measuredWidth.toFloat()/bitmapWidth))

                drawBitmapLeft = 0F
                drawBitmapTop = (measuredHeight - drawBitmapHeight)/ 2
                mFillStand = FillBitmapStand.WIDTH_FILL
            }

            //绘制图片
            this.drawBitmapRight = drawBitmapLeft + drawBitmapWidth
            this.drawBitmapBottom =  drawBitmapTop + drawBitmapHeight
            mBitmapRect = Rect(
                drawBitmapLeft.toInt(),
                drawBitmapTop.toInt(),
                drawBitmapRight.toInt(),
                drawBitmapBottom.toInt(),
            )

            //绘制图片
            canvas.drawBitmap(mBitmap!!, null, mBitmapRect!!, mPaint)

            //如果裁剪框位置没有初始化,则将裁剪框位置置于中心
            if (firstDraw){
                Log.e(TAG, "图片宽:$drawBitmapWidth  高:$drawBitmapHeight")
                if(drawBitmapWidth/mCropWidth > drawBitmapHeight/mCropHeight){
                    mCropWidth = mCropWidth * (drawBitmapHeight - 100)/ mCropHeight
                    mCropHeight = drawBitmapHeight - 100
                }else{
                    mCropHeight = mCropHeight * (drawBitmapWidth - 100) / mCropWidth
                    mCropWidth = drawBitmapWidth - 100
                }

                Log.e(TAG, "当前裁剪框宽:$mCropWidth  高:$mCropHeight")
                mCropY = (measuredHeight - mCropHeight)/2
                mCropX = (measuredWidth - mCropWidth)/ 2
                firstDraw = false
            }

            //绘制裁剪框
            mCropRect = Rect(mCropX.toInt(),
                mCropY.toInt(),
                (mCropX + mCropWidth).toInt(),
                (mCropY + mCropHeight).toInt()
            )

            //绘制裁剪框部分
            canvas.drawRect(mCropRect!!, mCenterPaint!!)

            //绘制非裁剪框部分
            var topRect = Rect(0, 0, measuredWidth, mCropY.toInt())
            var leftRect = Rect(0, mCropY.toInt(), mCropX.toInt(), (mCropHeight + mCropY).toInt())
            var RightRect = Rect((mCropX + mCropWidth).toInt(), mCropY.toInt(), measuredWidth, (mCropHeight + mCropY).toInt())
            var bottomRect = Rect(0, (mCropHeight + mCropY).toInt(), measuredWidth, measuredHeight)
            canvas.drawRect(topRect, mBgPaint!!)
            canvas.drawRect(leftRect, mBgPaint!!)
            canvas.drawRect(RightRect, mBgPaint!!)
            canvas.drawRect(bottomRect, mBgPaint!!)

        }
    }

    private fun drawRegion(canvas: Canvas, rgn: Region, paint: Paint) {
        val iter = RegionIterator(rgn)
        val r = Rect()
        while (iter.next(r)) {
            canvas.drawRect(r, paint)
        }
    }

    /**
     * 设置裁剪框宽高
     */
    fun setCropSize(width:Int, height: Int){
        Log.e(TAG, "原始宽:$width 高:$height")
        initCropWidth = width
        initCropHeight = height

        mCropHeight = height.toFloat()
        mCropWidth = width.toFloat()
        firstDraw = true
        invalidate()

    }

    fun getViewSize():Double{
        return Math.sqrt((drawBitmapWidth * drawBitmapWidth + drawBitmapHeight * drawBitmapHeight).toDouble())
    }

    /**
     * 绘制缩放裁剪框
     */
    fun drawScaleStateCropRect(event: MotionEvent){

        //计算距离
        val nowDistance = distance(event)
        val diffDistance = nowDistance - mOldDistance
        mOldDistance = nowDistance

        //控件的对角线长度
        val viewSize = getViewSize()

        //计算缩放率 乘以2提高缩放效率
        var scale = diffDistance / viewSize * 2
        scale += 1
        var oldCropHeight = mCropHeight
        var oldCropWidth = mCropWidth

        mCropHeight = (mCropHeight * scale).toFloat()
        mCropWidth = (mCropWidth * scale).toFloat()

        mOldCropY = mCropY
        mOldCropX = mCropX

        mCropX = mCropX - (mCropWidth - oldCropWidth)/2
        mCropY = mCropY - (mCropHeight - oldCropHeight)/ 2


        //如果超出边界则不重绘,傻逼了,这个是最简单的算法了,搞了半天
        if (mCropX <= drawBitmapLeft || mCropX + mCropWidth > drawBitmapRight || mCropY <= drawBitmapTop || mCropY + mCropHeight> drawBitmapBottom){
            mCropX = mOldCropX
            mCropY = mOldCropY
            mCropHeight = oldCropHeight
            mCropWidth = oldCropWidth
            return
        }

        /**
         控制缩放边界失败,这个是个傻逼算法,留着以做教训

        if (mCropX <= drawBitmapLeft){
            mCropX = drawBitmapLeft

            var nowWidth = Math.abs(mCropX - mOldCropX) * 2 + oldCropWidth;
            Log.d(TAG, "当前宽度:${nowWidth}")

            var nowScale = nowWidth/oldCropWidth;

            resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
            mCropX = drawBitmapLeft
            return

        }
        if (mCropX + mCropWidth > drawBitmapRight){
            mCropX = drawBitmapRight - mCropWidth

            var nowWidth = Math.abs(-mCropX + mOldCropX) * 2 + oldCropWidth;
            Log.d(TAG, "当前宽度:${nowWidth}")

            var nowScale = nowWidth/oldCropWidth;

            resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
            mCropX = drawBitmapRight - mCropWidth
            return
        }

        if (mCropY <= drawBitmapTop){
            mCropY = drawBitmapTop

            var nowHeight = Math.abs(mCropY - mOldCropY) * 2 + oldCropHeight;
            Log.d(TAG, "当前高度:${nowHeight}")

            var nowScale = nowHeight/oldCropHeight;

            resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
            mCropY = drawBitmapTop
            return
        }
        if (mCropY + mCropHeight> drawBitmapBottom){
            mCropY = drawBitmapBottom - mCropHeight

            var nowHeight = (mCropY - mOldCropY) * 2 + oldCropHeight;
            Log.d(TAG, "当前高度:${nowHeight}")

            var nowScale = nowHeight/oldCropHeight;

            resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
            mCropY = drawBitmapBottom - mCropHeight
            return
        }*/
        invalidate()
    }

    fun resetScaleRect(scale: Float, oldCropWidth: Float, oldCropHeight:Float){
        Log.e(TAG, "重置初始值 scale:${scale}  oldCropWidth:${oldCropWidth} oldCropHeight:${oldCropHeight}" )
        mCropHeight = (oldCropHeight * scale)
        mCropWidth = (oldCropWidth * scale)
        Log.e(TAG, "重置前位置  mCropX:${mOldCropX} mCropY:${mOldCropX}" )
        Log.e(TAG, "重置后宽度:${mCropWidth} 高度:${mCropHeight}")
        mCropX = mOldCropX
        mCropY = mOldCropY

        mCropX = mCropX - (mCropWidth - oldCropWidth)/2
        mCropY = mCropY - (mCropHeight - oldCropHeight)/ 2
        Log.e(TAG, "重置后位置  mCropX:${mCropX} mCropY:${mCropY}" )

        if (mCropX < drawBitmapLeft){
            mCropX = drawBitmapLeft
        }

        mOldCropY = mCropY
        mOldCropX = mCropX
        invalidate()
    }

    enum class ActionState{
        MOVE_STATE, SCALE_STATE
    }

    var canMoveAction = false
    var mActionState = ActionState.MOVE_STATE
    var mOldDistance: Double = 0.0 //上次两点之间的距离

    override fun onTouchEvent(event: MotionEvent): Boolean {

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                mActionState = ActionState.MOVE_STATE
                mOldX = event.x
                mOldY = event.y
                mOldCropX = mCropX
                mOldCropY = mCropY

                canMoveAction = mOldX > mCropX && mOldX < mCropX + mCropWidth  && mOldY > mCropY && mOldY < mCropY + mCropHeight
            }
            MotionEvent.ACTION_MOVE -> {
                //裁剪框处于移动状态
                if (canMoveAction && mActionState == ActionState.MOVE_STATE){
                    val dx = event.getX(0) - mOldX
                    val dy = event.getY(0) - mOldY

                    //保存裁剪框的位置
                    mCropX = mOldCropX + dx
                    mCropY = mOldCropY + dy

                    if (mCropX <= drawBitmapLeft){
                        mCropX = drawBitmapLeft
                    }
                    if (mCropX + mCropWidth > drawBitmapRight){
                        mCropX = drawBitmapRight - mCropWidth
                    }

                    if (mCropY <= drawBitmapTop){
                        mCropY = drawBitmapTop
                    }
                    if (mCropY + mCropHeight> drawBitmapBottom){
                        mCropY = drawBitmapBottom - mCropHeight
                    }

                    invalidate()
                }

                if (canMoveAction && mActionState == ActionState.SCALE_STATE){
                    drawScaleStateCropRect(event)
                }

            }
            MotionEvent.ACTION_UP ->{
                canMoveAction = false
                mActionState = ActionState.MOVE_STATE
            }
            MotionEvent.ACTION_POINTER_DOWN ->{
                canMoveAction = true
                mActionState = ActionState.SCALE_STATE
                mOldDistance = distance(event)
            }
            MotionEvent.ACTION_POINTER_UP ->{
                mActionState = ActionState.MOVE_STATE
                canMoveAction = false
            }
        }
        return true
    }

    //裁剪图片
    fun crop(): Bitmap? {
        val scale = mBitmap.height / drawBitmapHeight
        val width = (mCropWidth * scale).toInt()
        val height = (mCropHeight * scale).toInt()

        /// 裁剪点相对于View中图片的位置,跟实际图片位置是两回事
        val relativeX = mCropX - drawBitmapLeft
        val relativeY = mCropY - drawBitmapTop

        return Bitmap.createBitmap(mBitmap!!, (relativeX * scale).toInt(), (relativeY * scale).toInt(), width, height)
    }

    // 计算两个触摸点之间的距离
    private fun distance(event:MotionEvent ): Double {
        var x = event.getX(0) - event.getX(1);
        var y = event.getY(0) - event.getY(1);
        return Math.sqrt((x * x + y * y).toDouble());
    }

}

 

 

 

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 系统界面的一部分是 SystemUI。它是 Android 操作系统的一个系统应用程序,提供了通知栏、状态栏、快捷设置等重要的用户界面元素。如果您想要自定义 SystemUI,可以按照以下步骤进行操作: 1. 首先,您需要了解 SystemUI 的架构和工作原理。它是一个基于 Java 的应用程序,使用 Android 的 Binder 机制与系统服务通信。您可以通过下载 Android 源代码并查看源代码来深入了解其工作原理。 2. 然后,您需要创建一个新的 Android 应用程序项目,并将其配置为 SystemUI 扩展。这可以通过在 manifest 文件中声明一个文件提供者来实现。例如: ```xml <provider android:name=".SystemUIProvider" android:authorities="com.example.systemui.provider" android:exported="true" android:grantUriPermissions="true"> <meta-data android:name="com.android.systemui" android:resource="@xml/systemui_extensions" /> </provider> ``` 其中,`SystemUIProvider` 是您创建的文件提供者类的名称,`com.example.systemui.provider` 是您自己的包名,`@xml/systemui_extensions` 是您的扩展配置文件的资源 ID。 3. 接下来,您需要创建一个扩展配置文件,以指定 SystemUI 如何加载您的扩展。例如: ```xml <extensions> <extension class="com.example.systemui.MyExtension" targetPackage="com.android.systemui" targetClass="com.android.systemui.statusbar.phone.PhoneStatusBar" attach="true" /> </extensions> ``` 其中,`MyExtension` 是您创建的扩展类的名称,`com.android.systemui.statusbar.phone.PhoneStatusBar` 是您要扩展的目标类的完全限定名称。 4. 最后,您需要创建一个扩展类,并实现所需的功能。例如,如果您想要添加一个新的按钮到状态栏,您可以创建一个 `View`,并将其添加到状态栏中。您可以使用 Android 提供的 `StatusBarManager` 类来添加视图。 以上是自定义 Android SystemUI 的大致步骤,希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值