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

07-08 1034阅读

XML文件


    
        
        
    
    

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(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  {
                              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()  {
                      // 所有手指抬起,重置状态
                      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() 
                  // 当前裁剪框的左右边界加上距离最小裁剪框的距离
                  val xOffset = (minRect.width() - rect.width()) / 2
                  rect.left -= xOffset
                  rect.right += xOffset
                  // 如果左边界小于最小裁剪框的左边界,那么左边界就等于最小裁剪框的左边界
                  if (rect.left 
                  // 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
                  )
              }
          }
      }
      

      效果图

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

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]