终于分解的步骤基本已经完成了,接下来我们讲一下裁剪框和图片的联动以及动画的更新。
其实从前面几次的讲解,其实简单来说就是两个RectF的维护-----裁剪框和图片。那么除了移动以外我们主要做的就是找到动画开始和结束的状态,也就是两个RectF的。
接下来我们拓展一下我们的估值器,让它也计算裁剪框的RectF的变化
if (startValue.winFrame != null) {
Log.d(TAG, "${endValue?.winFrame}")
left = startValue.winFrame?.left!! + fraction * (endValue.winFrame?.left!! - startValue.winFrame?.left!!)
top = startValue.winFrame?.top!! + fraction * (endValue.winFrame?.top!! - startValue.winFrame?.top!!)
right = startValue.winFrame?.right!! + fraction * (endValue.winFrame?.right!! - startValue.winFrame?.right!!)
bottom = startValue.winFrame?.bottom!! + fraction * (endValue.winFrame?.bottom!! - startValue.winFrame?.bottom!!)
clipImageState?.winFrame = RectF(left, top, right, bottom)
} else {
clipImageState?.winFrame = null
}
我们在手指按下时,记录一下锚点,即是否点击到了裁剪框。
override fun onDown(e: MotionEvent?): Boolean {
Log.d(TAG, "onDown")
mAnchor = clipImageWindowView.getAnchor(e?.x!!, e.y)
if (mAnchor != null) {
isChanged = true
}
isSteady = false
return true
}
而在ontouchEvent中我们监听手指松开的一系列情况,大致分为一下三种:
1.当前拖动裁剪框完毕
那么我们的思路很明确,一是把裁剪框放大,而是图片的放大和移动
具体关于RectF的计算逻辑很简单,首先是看裁剪框是应该上下顶到View的边界还是左右顶到View的边界。然后就可以确定缩放比例,之后记录裁剪框中心的位移,将该位移应用到放大后的图片,使得裁剪框内图片内容不发生变化。
2.手动缩放图片
手动缩放图片我们需要处理一个特殊case,就是缩放之后图片小于了裁剪框,此时我们要把图片放大到至少与裁剪框对齐。
3.手动拖拽图片
手动拖拽我们也需要处理特殊的case,就是图片越过边界之后恢复到与裁剪框对齐。
我的具体实现可能比较冗杂,建议大家自己捋一捋逻辑还是很有帮助的,之后我会上传到github上欢迎大家拍砖。
/**
* 监听手指抬起动作进行图片的复位
*/
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
var handled = mScaleGestureDetector?.onTouchEvent(event) ?: false
handled = mGestureDetector?.onTouchEvent(event) ?: false || handled
if (event?.actionMasked == MotionEvent.ACTION_UP ||
event?.actionMasked == MotionEvent.ACTION_CANCEL) {
/**
* 临时变量用于存储图片frame的起始状态作为动画的开始状态
*/
val startFrame = RectF()
startFrame.set(zoomImageView.getFrame())
if (mAnchor != null) {
val tempList = clipImageWindowView.endState
val winEndState = tempList[1] as RectF
val tempScale = tempList[0] as Float
val imageEndState = zoomImageView.getScaleEnd(tempScale, clipImageWindowView.mFrame.centerX(),
clipImageWindowView.mFrame.centerY())
// 如果裁剪框大于了图片的最终位置,则需要对图片新的位置进行修正
M.reset()
if (winEndState.left < imageEndState.left) {
M.postTranslate(winEndState.left - imageEndState.left, 0f)
}
if (winEndState.top < imageEndState.top) {
M.postTranslate(0f, winEndState.top - imageEndState.top)
}
if (winEndState.right > imageEndState.right) {
M.postTranslate(winEndState.right - imageEndState.right, 0f)
}
if (winEndState.bottom > imageEndState.bottom) {
M.postTranslate(0f, winEndState.bottom - imageEndState.bottom)
}
M.mapRect(imageEndState)
startAnimation(ClipImageState(startFrame, clipImageWindowView.mFrame),
ClipImageState(imageEndState, winEndState))
return handled
}
if (needToReset) {
if (startFrame.width() < clipImageWindowView.mFrame.width() ||
startFrame.height() < clipImageWindowView.mFrame.height()) {
val scale = max(clipImageWindowView.mFrame.width() / startFrame.width(),
clipImageWindowView.mFrame.height() / startFrame.height())
zoomImageView.onScale(scale, clipImageWindowView.mFrame.centerX(),
clipImageWindowView.mFrame.centerY())
}
needToReset = false
}
startAnimation(ClipImageState(startFrame), ClipImageState(zoomImageView.getResetTransEnd()))
}
return handled
}
还有一个问题,我们在拖动裁剪框的时候,裁剪框可能大于图片的边界,这时候我们需要实时放大一下图片,以适应裁剪框的变化,这里逻辑还有待优化,这显然是个很愚蠢的实现方案,鉴于本渣渣水平有限,优化我会在完成整个库之后统一进行,这里就是给一个思路,即实时更改图片的RectF
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
if (mAnchor != null) {
clipImageWindowView.onScroll(mAnchor, -distanceX, -distanceY)
/**
* 修正图片frame
*/
if (zoomImageView.getFrame().left > clipImageWindowView.mFrame.left) {
scaleOffset = zoomImageView.getFrame().width() / (zoomImageView.getFrame().width() - distanceX)
zoomImageView.onScale(scaleOffset, clipImageWindowView.mFrame.centerX(),
clipImageWindowView.mFrame.centerY())
}
if (zoomImageView.getFrame().top > clipImageWindowView.mFrame.top) {
scaleOffset = zoomImageView.getFrame().height() / (zoomImageView.getFrame().height() - distanceY)
zoomImageView.onScale(scaleOffset, clipImageWindowView.mFrame.centerX(),
clipImageWindowView.mFrame.centerY())
}
if (zoomImageView.getFrame().right < clipImageWindowView.mFrame.right) {
scaleOffset = zoomImageView.getFrame().width() / (zoomImageView.getFrame().width() + distanceX)
zoomImageView.onScale(scaleOffset, clipImageWindowView.mFrame.centerX(),
clipImageWindowView.mFrame.centerY())
}
if (zoomImageView.getFrame().bottom < clipImageWindowView.mFrame.bottom) {
scaleOffset = zoomImageView.getFrame().height() / (zoomImageView.getFrame().height() + distanceY)
zoomImageView.onScale(scaleOffset, clipImageWindowView.mFrame.centerX(),
clipImageWindowView.mFrame.centerY())
}
} else {
zoomImageView.onScroll(distanceX, distanceY)
}
invalidate()
return true
}
至此,整个图片裁剪的主要代码分析已经结束了,大家尽量领会精神,虽然我学的是软件工程,但是我的代码封装和优化还是用的很烂,之后我会在github上传一份代码相对优美,结构相对完整的demo,欢迎大家围观。
感兴趣的小伙伴或者希望一起学习Android 相关知识的欢迎加我微信一起玩耍呀!
PS 我就是个小白,第一次尝试写小小的模块,还希望各位大佬多多指教,不喜勿喷(逃
微信号:qbwk43
最后大家可以看看初步效果
http://www.iqiyi.com/w_19s6z4tq69.html
项目github地址:https://github.com/lqy20160609/ImageClip