当前位置: 首页 > news >正文

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

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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><com.yang.app.MyRootViewandroid: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.MyCropViewandroid: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? = nullvar mRootView: MyRootView? = nullvar 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? MyCropViewmRootView = 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 = -1var imageHeight = -1val preOption = BitmapFactory.Options().apply {// 只获取图片的宽高inJustDecodeBounds = trueBitmapFactory.decodeResource(resources, imageId, this)}imageWidth = preOption.outWidthimageHeight = preOption.outHeight// 计算缩放比例val scaleMatrix = Matrix()// 确定未缩放Bitmap的RectFvar srcRect = RectF(0f, 0f, imageWidth.toFloat(), imageHeight.toFloat())// 通过目标RectF, 确定缩放数值,存储在scaleMatrix中scaleMatrix.setRectToRect(srcRect, destRect, Matrix.ScaleToFit.CENTER)// 缩放数值再映射到原始Bitmap上,得到缩放后的RectFscaleMatrix.mapRect(srcRect)val finalOption = BitmapFactory.Options().apply {if (imageHeight > 0 && imageWidth > 0) {inPreferredConfig = Bitmap.Config.RGB_565inSampleSize = 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 = fromWidthvar bitmapHeight = fromHeightif (fromWidth > toWidth|| fromHeight > toHeight) {var inSampleSize = 2// 计算最大的inSampleSize值,该值是2的幂,并保持原始宽高大于目标宽高while (bitmapWidth >= toWidth && bitmapHeight >= toHeight) {bitmapWidth /= 2bitmapHeight /= 2inSampleSize *= 2}return inSampleSize}return 1
}fun setRectChangeListener(listener: RectChangedListener) {mRectChangeListener = listener
}fun dpToPx(context: Context, dp: Float): Float {val metrics = context.resources.displayMetricsreturn TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics)
}

自定义View代码

  • 显示图片的View
class MyRootView constructor(context: Context, attrs: AttributeSet? ) : View(context, attrs) {private var lastX = 0fprivate var lastY = 0fprivate val scroller = OverScroller(context)private var tracker: VelocityTracker? = nullprivate var initialLeft = 0private var initialTop = 0private var mDestRect: RectF? = nullprivate val mScaleMatrix = Matrix()private var mRectChangeListener: RectChangedListener? = nullprivate var mPaint = Paint().apply {isAntiAlias = trueisFilterBitmap = true}private var mOriginBitmap: Bitmap? = nulloverride fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {super.onLayout(changed, left, top, right, bottom)if (initialLeft == 0) initialLeft = leftif (initialTop == 0) initialTop = topmDestRect = 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.rawXlastY = event.rawY}MotionEvent.ACTION_MOVE -> {if (tracker == null) {tracker = VelocityTracker.obtain()tracker?.addMovement(event)}val dx = event.rawX - lastXval dy = event.rawY - lastYval 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.rawXlastY = 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 = nullinvalidate() // 请求重绘View,这会导致computeScroll()被调用}}return true}override fun computeScroll() {if (scroller.computeScrollOffset()) {// 更新View的位置val left = scroller.currXval top = scroller.currYval right = left + widthval bottom = top + heightlayout(left, top, right, bottom)if (!scroller.isFinished) {invalidate()  // 继续请求重绘View,直到滑动结束}}}fun setOriginBitmap(bitmap: Bitmap) {mOriginBitmap = bitmapinvalidate()}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.widthval scaleY = mDestRect?.height()!! / bitmap.heightval scale = Math.min(scaleX, scaleY)val dx = (mDestRect?.width()!! - bitmap.width!! * scale) / 2val dy = (mDestRect?.height()!! - bitmap.height!! * scale) / 2mScaleMatrix.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 = truecolor = Color.WHITEstrokeWidth = dpToPx(context, 1.5f)style = Paint.Style.STROKE}private val mCornerAndCenterLinePaint = Paint().apply {isAntiAlias = truecolor = Color.REDstrokeWidth = dpToPx(context, 3f)style = Paint.Style.STROKE}private val mDividerLinePaint = Paint().apply {isAntiAlias = truecolor = Color.WHITEstrokeWidth = dpToPx(context, 3f) / 2falpha = (0.5 * 255).toInt()style = Paint.Style.STROKE}private val mLineOffset = dpToPx(context, 3f) / 2fprivate 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 = 0fprivate var downY = 0fenum class MoveType {LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTTOM, LEFT, TOP, RIGHT, BOTTOM}private var mMoveType : MoveType?= nullprivate 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 = -1override fun onTouchEvent(event: MotionEvent?): Boolean {when (event?.actionMasked) {MotionEvent.ACTION_DOWN -> {// 记录第一根手指的位置和idval pointerIndex = event.actionIndexmActivePointerId = 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_TOPreturn true}val leftBottomRect = getStartCropCornerRect(cropRect.left, cropRect.bottom)if (leftBottomRect.contains(event.x , event.y)) {mMoveType = MoveType.LEFT_BOTTOMreturn true}val rightTopRect = getStartCropCornerRect(cropRect.right, cropRect.top)if (rightTopRect.contains(event.x , event.y)) {mMoveType = MoveType.RIGHT_TOPreturn true}val rightBottomRect = getStartCropCornerRect(cropRect.right, cropRect.bottom)if (rightBottomRect.contains(event.x , event.y)) {mMoveType = MoveType.RIGHT_BOTTOMreturn true}val leftCenterRect = getStartCropCenterRect(cropRect.left, cropRect.left, cropRect.top, cropRect.bottom)if (leftCenterRect.contains(event.x , event.y)) {mMoveType = MoveType.LEFTreturn true}val rightCenterRect = getStartCropCenterRect(cropRect.right, cropRect.right, cropRect.top, cropRect.bottom)if (rightCenterRect.contains(event.x , event.y)) {mMoveType = MoveType.RIGHTreturn true}val topCenterRect = getStartCropCenterRect(cropRect.left, cropRect.right, cropRect.top, cropRect.top)if (topCenterRect.contains(event.x , event.y)) {mMoveType = MoveType.TOPreturn true}val bottomCenterRect = getStartCropCenterRect(cropRect.left, cropRect.right, cropRect.bottom, cropRect.bottom)if (bottomCenterRect.contains(event.x , event.y)) {mMoveType = MoveType.BOTTOMreturn true}return true}MotionEvent.ACTION_POINTER_DOWN->{// 记录第二根手指的位置和idval pointerIndex = event.actionIndexmActivePointerId = 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) - downXvar deltaY = event.getY(pointerIndex) - downYdownX = event.getX(pointerIndex)downY = event.getY(pointerIndex)val originalRect = getInitCropRect()val startCropRect = getCropRect()val endCropRect = RectF(startCropRect)when (mMoveType) {MoveType.LEFT_TOP -> {endCropRect.left += deltaXendCropRect.top += deltaY}MoveType.LEFT_BOTTOM -> {endCropRect.left += deltaXendCropRect.bottom += deltaY}MoveType.RIGHT_TOP -> {endCropRect.right += deltaXendCropRect.top += deltaY}MoveType.RIGHT_BOTTOM -> {endCropRect.right += deltaXendCropRect.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 = -1fdownY = -1fmMoveType = nullmActivePointerId = -1}MotionEvent.ACTION_POINTER_UP -> {// 假如屏幕上有两根手指// 按下第二根,抬起第二根,mActivePointerId == pointerId, 活动手指更新为第一根// 按下第二根,抬起第一根,mActivePointerId != pointerId, 活动手指为第二根,不变val pointerIndex = event.actionIndexval pointerId = event.getPointerId(pointerIndex)if (mActivePointerId == pointerId) {// 选择一个新的活动手指val newPointerIndex = if (pointerIndex == 0) 1 else 0mActivePointerId = 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()) / 2rect.left -= xOffsetrect.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()) / 2rect.top -= yOffsetrect.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() / 3val x2 = rect.left + rect.width() * 2 / 3val y1 = rect.top + rect.height() / 3val y2 = rect.top + rect.height() * 2 / 3canvas.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.lryc.cn/news/394954.html

相关文章:

  • 语言模型的进化:从NLP到LLM的跨越之旅
  • 应急响应--网站(web)入侵篡改指南
  • vue3+vue-router+vite 实现动态路由
  • Okhttp hostnameVerifier详解
  • TCP的p2p网络模式
  • 力扣-贪心算法4
  • 动手学深度学习6.2 图像卷积-笔记练习(PyTorch)
  • 展开说说:Android服务之bindService解析
  • node-sass 老版本4.14.0 安装失败解决办法
  • 最近很火的字幕截图生成器
  • 使用RabbitMQ实现可靠的消息传递机制
  • Function Call ReACT,Agent应用落地的加速器_qwen的function calling和react有什么不同
  • Java的JSONPath(fastjson)使用总结
  • 【大模型】大语言模型:光鲜背后的阴影——事实准确性和推理能力的挑战
  • Java面向对象练习(1.手机类)(2024.7.4)
  • 智慧生活新篇章,Vatee万腾平台领航前行
  • Spring Cloud Gateway报sun.misc.Unsafe.park(Native Method)
  • select single , select endselect
  • 后端学习(一)
  • 【活动行】参与上海两场线下活动,教育生态行业赛总决赛活动和WAIC人工智能大会活动 - 上海活动总结
  • conda 安装设置
  • 用PlantUML和语雀画UML类图
  • uniapp微信小程序电子签名
  • MetaPoint_速读
  • 数据库逆向工程工具reverse_sql
  • 四大内网穿透利器对比
  • 【LeetCode】每日一题:跳跃游戏 II
  • SpringBoot拦截器
  • uniapp中实现跳转链接到游览器(安卓-h5)
  • WPF UI 界面布局 魔术棒 文字笔记识别 技能提升 布局功能扩展与自定义 继承Panel的对象,测量与排列 系列七