View->裁剪框View的绘制,手势处理
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
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。