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? = 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 ? 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 = - 1 var imageHeight = - 1 val preOption = BitmapFactory. Options ( ) . apply { inJustDecodeBounds = true BitmapFactory. 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 = 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. displayMetricsreturn TypedValue. applyDimension ( TypedValue. COMPLEX_UNIT_DIP, dp, metrics)
}
自定义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 = 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 = null invalidate ( ) } } 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) / 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}
}
class MyCropView ( context: Context, attrs: AttributeSet) : View ( context, attrs) , RectChangedListener { private val mRectLinePaint = Paint ( ) . apply { isAntiAlias = true color = Color. WHITEstrokeWidth = dpToPx ( context, 1.5f ) style = Paint. Style. STROKE} private val mCornerAndCenterLinePaint = Paint ( ) . apply { isAntiAlias = true color = Color. REDstrokeWidth = dpToPx ( context, 3f ) style = Paint. Style. STROKE} private val mDividerLinePaint = Paint ( ) . apply { isAntiAlias = true color = Color. WHITEstrokeWidth = 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 -> { 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 false 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 = - 1f downY = - 1f mMoveType = null mActivePointerId = - 1 } MotionEvent. ACTION_POINTER_UP -> { val pointerIndex = event. actionIndexval 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 ( ) <= minRect. width ( ) ) { val xOffset = ( minRect. width ( ) - rect. width ( ) ) / 2 rect. 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 ( ) ) / 2 rect. 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 ( ) / 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) 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) } }
}
效果图