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" >
< LinearLayout
android: layout_width= " match_parent"
android: layout_height= " wrap_content"
android: orientation= " vertical" >
< com.yang.app.MyRootView
android: 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.MyCropView
android: 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 ? MyCropView
mRootView = 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. outWidth
imageHeight = preOption. outHeight
val 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_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
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
代码
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 -> {
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. currX
val top = scroller. currY
val right = left + width
val bottom = top + height
layout ( left, top, right, bottom)
if ( ! scroller. isFinished) {
invalidate ( )
}
}
}
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
}
}
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 -> {
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-> {
val pointerIndex = event. actionIndex
mActivePointerId = 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) - downX
var deltaY = event. getY ( pointerIndex) - downY
downX = event. getX ( pointerIndex)
downY = event. getY ( pointerIndex)
val originalRect = getInitCropRect ( )
val startCropRect = getCropRect ( )
val endCropRect = RectF ( startCropRect)
when ( mMoveType) {
MoveType. LEFT_TOP -> {
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 ( ) < 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. 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 ( ) <= minRect. width ( ) ) {
val xOffset = ( minRect. width ( ) - rect. width ( ) ) / 2
rect. left -= xOffset
rect. 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 ( ) ) / 2
rect. top -= yOffset
rect. 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->
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
)
}
}
}
效果图