具体UI效果如下:
思路
绘制5个rect,其中四个为半透明深色背景,一个为透明背景的裁剪内容框
之前也考虑过用region,但是自测的时候,发现两个region之间颜色会相互影响,可能是我代码问题(有了解的小伙伴可以指导一下哈),就用了5个Rect来绘制开发效率会更高一些。
2023年8月24号更新:
考虑到图片裁剪定位思路比较复杂,这里把最新的定位代码也贴上来!
具体代码如下:
package com.xingzhi.customview
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
/**
*
*Date:2023/5/13
*Time:20:08
*author:wushengqi
*
* https://blog.csdn.net/qq_39312146/article/details/129053307
*
* https://blog.csdn.net/yihonglvyu1/article/details/122089901
*
* Region:
* https://blog.csdn.net/qq_27061049/article/details/104534867
*
* 多点触控:
* https://blog.51cto.com/u_15749390/5570836
*/
class ImageCropView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
View(context, attrs, defStyleAttr, defStyleRes) {
companion object{
val TAG = ImageCropView::class.java.simpleName
}
init {
init()
}
constructor(context: Context?):this(context, attributeSet = null){
}
constructor(context: Context?, attributeSet: AttributeSet?) : this(context, attributeSet, defStyleAttr = 0) {
}
constructor(context: Context?, attributeSet: AttributeSet?, defStyleAttr:Int) : this(context, attributeSet, defStyleAttr, defStyleRes = 0){
}
private lateinit var mBitmap: Bitmap//图片
private var mBitmapRect:Rect? = null//图片绘制大小
private var mCropRect:Rect? = null//裁剪框
private var mPaint:Paint? = null //画笔
private var mBgPaint:Paint? = null //裁剪框之外的颜色
private var mCenterPaint:Paint? = null //裁剪框中心的颜色
private var mCropX:Float = 0.0f//裁剪框的X坐标
private var mCropY:Float = 0.0f//裁剪框的Y坐标
private var mCropWidth:Float = 0F//裁剪框的宽度
private var mCropHeight:Float = 0F//裁剪框的高度
private var mOldX:Float = 0.0f//上一次触摸的X坐标
private var mOldY:Float = 0.0f//上一次触摸的Y坐标
private var mOldCropX:Float = 0.0f//上一次裁剪框的X坐标
private var mOldCropY:Float = 0.0f//上一次裁剪框的Y坐标
private var drawBitmapHeight = 0F
private var drawBitmapWidth = 0F
private var drawBitmapLeft = 0F
private var drawBitmapTop = 0F
private var drawBitmapRight = 0F
private var drawBitmapBottom = 0F
private var firstDraw = true
private var initCropWidth = 0
private var initCropHeight = 0
private fun init() {
mPaint = Paint()
mPaint!!.style = Paint.Style.STROKE
mPaint!!.strokeWidth = 4f
mPaint!!.color = Color.WHITE
mBgPaint = Paint()
mBgPaint!!.style = Paint.Style.FILL
mBgPaint!!.color = Color.parseColor("#AA000000")
mCenterPaint = Paint()
mCenterPaint!!.style = Paint.Style.FILL
mCenterPaint!!.color = Color.parseColor("#00000000")
}
//设置图片
fun setBitmap(bitmap: Bitmap) {
mBitmap = bitmap
invalidate()
}
/**
* 绘制bitmap的基准
*/
enum class FillBitmapStand{
WIDTH_FILL, //填充满控件宽度
HEIGHT_FILL //填充满控件高度
}
var mFillStand = FillBitmapStand.WIDTH_FILL
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (mBitmap != null) {
Log.d(TAG, "onDraw")
val bitmapWidth = mBitmap.width
val bitmapHeight = mBitmap.height
// 如果图片的高度和控件实际高度比大于宽度比,则高度填充满整个屏幕,宽度按比例缩放
if (bitmapHeight.toFloat()/ measuredHeight > bitmapWidth / measuredWidth){
drawBitmapHeight = measuredHeight.toFloat()
drawBitmapWidth = ((bitmapWidth * ( measuredHeight.toFloat()/bitmapHeight)))
drawBitmapLeft = (measuredWidth - drawBitmapWidth) / 2
drawBitmapTop = 0F
mFillStand = FillBitmapStand.HEIGHT_FILL
}else{
drawBitmapWidth = measuredWidth.toFloat()
drawBitmapHeight = (bitmapHeight * (measuredWidth.toFloat()/bitmapWidth))
drawBitmapLeft = 0F
drawBitmapTop = (measuredHeight - drawBitmapHeight)/ 2
mFillStand = FillBitmapStand.WIDTH_FILL
}
//绘制图片
this.drawBitmapRight = drawBitmapLeft + drawBitmapWidth
this.drawBitmapBottom = drawBitmapTop + drawBitmapHeight
mBitmapRect = Rect(
drawBitmapLeft.toInt(),
drawBitmapTop.toInt(),
drawBitmapRight.toInt(),
drawBitmapBottom.toInt(),
)
//绘制图片
canvas.drawBitmap(mBitmap!!, null, mBitmapRect!!, mPaint)
//如果裁剪框位置没有初始化,则将裁剪框位置置于中心
if (firstDraw){
Log.e(TAG, "图片宽:$drawBitmapWidth 高:$drawBitmapHeight")
if(drawBitmapWidth/mCropWidth > drawBitmapHeight/mCropHeight){
mCropWidth = mCropWidth * (drawBitmapHeight - 100)/ mCropHeight
mCropHeight = drawBitmapHeight - 100
}else{
mCropHeight = mCropHeight * (drawBitmapWidth - 100) / mCropWidth
mCropWidth = drawBitmapWidth - 100
}
Log.e(TAG, "当前裁剪框宽:$mCropWidth 高:$mCropHeight")
mCropY = (measuredHeight - mCropHeight)/2
mCropX = (measuredWidth - mCropWidth)/ 2
firstDraw = false
}
//绘制裁剪框
mCropRect = Rect(mCropX.toInt(),
mCropY.toInt(),
(mCropX + mCropWidth).toInt(),
(mCropY + mCropHeight).toInt()
)
//绘制裁剪框部分
canvas.drawRect(mCropRect!!, mCenterPaint!!)
//绘制非裁剪框部分
var topRect = Rect(0, 0, measuredWidth, mCropY.toInt())
var leftRect = Rect(0, mCropY.toInt(), mCropX.toInt(), (mCropHeight + mCropY).toInt())
var RightRect = Rect((mCropX + mCropWidth).toInt(), mCropY.toInt(), measuredWidth, (mCropHeight + mCropY).toInt())
var bottomRect = Rect(0, (mCropHeight + mCropY).toInt(), measuredWidth, measuredHeight)
canvas.drawRect(topRect, mBgPaint!!)
canvas.drawRect(leftRect, mBgPaint!!)
canvas.drawRect(RightRect, mBgPaint!!)
canvas.drawRect(bottomRect, mBgPaint!!)
}
}
private fun drawRegion(canvas: Canvas, rgn: Region, paint: Paint) {
val iter = RegionIterator(rgn)
val r = Rect()
while (iter.next(r)) {
canvas.drawRect(r, paint)
}
}
/**
* 设置裁剪框宽高
*/
fun setCropSize(width:Int, height: Int){
Log.e(TAG, "原始宽:$width 高:$height")
initCropWidth = width
initCropHeight = height
mCropHeight = height.toFloat()
mCropWidth = width.toFloat()
firstDraw = true
invalidate()
}
fun getViewSize():Double{
return Math.sqrt((drawBitmapWidth * drawBitmapWidth + drawBitmapHeight * drawBitmapHeight).toDouble())
}
/**
* 绘制缩放裁剪框
*/
fun drawScaleStateCropRect(event: MotionEvent){
//计算距离
val nowDistance = distance(event)
val diffDistance = nowDistance - mOldDistance
mOldDistance = nowDistance
//控件的对角线长度
val viewSize = getViewSize()
//计算缩放率 乘以2提高缩放效率
var scale = diffDistance / viewSize * 2
scale += 1
var oldCropHeight = mCropHeight
var oldCropWidth = mCropWidth
mCropHeight = (mCropHeight * scale).toFloat()
mCropWidth = (mCropWidth * scale).toFloat()
mOldCropY = mCropY
mOldCropX = mCropX
mCropX = mCropX - (mCropWidth - oldCropWidth)/2
mCropY = mCropY - (mCropHeight - oldCropHeight)/ 2
//如果超出边界则不重绘,傻逼了,这个是最简单的算法了,搞了半天
if (mCropX <= drawBitmapLeft || mCropX + mCropWidth > drawBitmapRight || mCropY <= drawBitmapTop || mCropY + mCropHeight> drawBitmapBottom){
mCropX = mOldCropX
mCropY = mOldCropY
mCropHeight = oldCropHeight
mCropWidth = oldCropWidth
return
}
/**
控制缩放边界失败,这个是个傻逼算法,留着以做教训
if (mCropX <= drawBitmapLeft){
mCropX = drawBitmapLeft
var nowWidth = Math.abs(mCropX - mOldCropX) * 2 + oldCropWidth;
Log.d(TAG, "当前宽度:${nowWidth}")
var nowScale = nowWidth/oldCropWidth;
resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
mCropX = drawBitmapLeft
return
}
if (mCropX + mCropWidth > drawBitmapRight){
mCropX = drawBitmapRight - mCropWidth
var nowWidth = Math.abs(-mCropX + mOldCropX) * 2 + oldCropWidth;
Log.d(TAG, "当前宽度:${nowWidth}")
var nowScale = nowWidth/oldCropWidth;
resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
mCropX = drawBitmapRight - mCropWidth
return
}
if (mCropY <= drawBitmapTop){
mCropY = drawBitmapTop
var nowHeight = Math.abs(mCropY - mOldCropY) * 2 + oldCropHeight;
Log.d(TAG, "当前高度:${nowHeight}")
var nowScale = nowHeight/oldCropHeight;
resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
mCropY = drawBitmapTop
return
}
if (mCropY + mCropHeight> drawBitmapBottom){
mCropY = drawBitmapBottom - mCropHeight
var nowHeight = (mCropY - mOldCropY) * 2 + oldCropHeight;
Log.d(TAG, "当前高度:${nowHeight}")
var nowScale = nowHeight/oldCropHeight;
resetScaleRect(nowScale, oldCropWidth, oldCropHeight)
mCropY = drawBitmapBottom - mCropHeight
return
}*/
invalidate()
}
fun resetScaleRect(scale: Float, oldCropWidth: Float, oldCropHeight:Float){
Log.e(TAG, "重置初始值 scale:${scale} oldCropWidth:${oldCropWidth} oldCropHeight:${oldCropHeight}" )
mCropHeight = (oldCropHeight * scale)
mCropWidth = (oldCropWidth * scale)
Log.e(TAG, "重置前位置 mCropX:${mOldCropX} mCropY:${mOldCropX}" )
Log.e(TAG, "重置后宽度:${mCropWidth} 高度:${mCropHeight}")
mCropX = mOldCropX
mCropY = mOldCropY
mCropX = mCropX - (mCropWidth - oldCropWidth)/2
mCropY = mCropY - (mCropHeight - oldCropHeight)/ 2
Log.e(TAG, "重置后位置 mCropX:${mCropX} mCropY:${mCropY}" )
if (mCropX < drawBitmapLeft){
mCropX = drawBitmapLeft
}
mOldCropY = mCropY
mOldCropX = mCropX
invalidate()
}
enum class ActionState{
MOVE_STATE, SCALE_STATE
}
var canMoveAction = false
var mActionState = ActionState.MOVE_STATE
var mOldDistance: Double = 0.0 //上次两点之间的距离
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mActionState = ActionState.MOVE_STATE
mOldX = event.x
mOldY = event.y
mOldCropX = mCropX
mOldCropY = mCropY
canMoveAction = mOldX > mCropX && mOldX < mCropX + mCropWidth && mOldY > mCropY && mOldY < mCropY + mCropHeight
}
MotionEvent.ACTION_MOVE -> {
//裁剪框处于移动状态
if (canMoveAction && mActionState == ActionState.MOVE_STATE){
val dx = event.getX(0) - mOldX
val dy = event.getY(0) - mOldY
//保存裁剪框的位置
mCropX = mOldCropX + dx
mCropY = mOldCropY + dy
if (mCropX <= drawBitmapLeft){
mCropX = drawBitmapLeft
}
if (mCropX + mCropWidth > drawBitmapRight){
mCropX = drawBitmapRight - mCropWidth
}
if (mCropY <= drawBitmapTop){
mCropY = drawBitmapTop
}
if (mCropY + mCropHeight> drawBitmapBottom){
mCropY = drawBitmapBottom - mCropHeight
}
invalidate()
}
if (canMoveAction && mActionState == ActionState.SCALE_STATE){
drawScaleStateCropRect(event)
}
}
MotionEvent.ACTION_UP ->{
canMoveAction = false
mActionState = ActionState.MOVE_STATE
}
MotionEvent.ACTION_POINTER_DOWN ->{
canMoveAction = true
mActionState = ActionState.SCALE_STATE
mOldDistance = distance(event)
}
MotionEvent.ACTION_POINTER_UP ->{
mActionState = ActionState.MOVE_STATE
canMoveAction = false
}
}
return true
}
//裁剪图片
fun crop(): Bitmap? {
val scale = mBitmap.height / drawBitmapHeight
val width = (mCropWidth * scale).toInt()
val height = (mCropHeight * scale).toInt()
/// 裁剪点相对于View中图片的位置,跟实际图片位置是两回事
val relativeX = mCropX - drawBitmapLeft
val relativeY = mCropY - drawBitmapTop
return Bitmap.createBitmap(mBitmap!!, (relativeX * scale).toInt(), (relativeY * scale).toInt(), width, height)
}
// 计算两个触摸点之间的距离
private fun distance(event:MotionEvent ): Double {
var x = event.getX(0) - event.getX(1);
var y = event.getY(0) - event.getY(1);
return Math.sqrt((x * x + y * y).toDouble());
}
}
文章来源地址https://www.toymoban.com/news/detail-771444.html
文章来源:https://www.toymoban.com/news/detail-771444.html
到了这里,关于(UI)Android自定义图片裁剪的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!