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.outHeightval scaleMatrix = Matrix()var srcRect = RectF(0f, 0f, imageWidth.toFloat(), imageHeight.toFloat())scaleMatrix.setRectToRect(srcRect, destRect, Matrix.ScaleToFit.CENTER)scaleMatrix.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 = 2while (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
代码
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 -> {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() }}return true}override fun computeScroll() {if (scroller.computeScrollOffset()) {val left = scroller.currXval top = scroller.currYval right = left + widthval bottom = top + heightlayout(left, top, right, bottom)if (!scroller.isFinished) {invalidate() }}}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}
}
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 -> {val 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->{val pointerIndex = event.actionIndexmActivePointerId = event.getPointerId(pointerIndex)downX = event.getX(pointerIndex)downY = event.getY(pointerIndex)}MotionEvent.ACTION_MOVE -> {mMoveType ?: return falseval 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 -> {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 += xOffsetif (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 += yOffsetif (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->canvas.save()canvas.clipOutRect(rect)canvas.drawColor(Color.argb(mCoverColor.alpha, mCoverColor.red, mCoverColor.green, mCoverColor.blue))canvas.restore()canvas?.drawRect(rect, mRectLinePaint)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)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)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)}}
}
效果图