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
)
}
}
}
效果图
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d0b6deb0d06a43f6bfa0b138a6316e81.gif)