Android图片处理:PinchImageView源码解析,2024年最新android编程权威指南第3版

//设置矩阵并重绘

mOuterMatrix.setValues(mResult);

……

invalidate();

}

@Override

protected void onDraw(Canvas canvas) {

……

//在绘制前设置变换矩阵

setImageMatrix(getCurrentImageMatrix(matrix));

……

super.onDraw(canvas);

……

}

缩放平移后,图片可能出现边框进入图片控件的情况,此时需要修正位置。用最终缩放后的图片边界和控件边界对比矫正即可。

Matrix testMatrix = MathUtils.matrixTake(innerMatrix);

testMatrix.postConcat(animEnd);

RectF testBound = MathUtils.rectFTake(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

testMatrix.mapRect(testBound);

刚才已经知道, animEnd记录的是当前双击变换操作作用在外部矩阵的结果,把它和内部矩阵(innerMatrix)相乘就得到了最终对原图(testBound)的变换矩阵(testMatrix)。

//修正位置

float postX = 0;

float postY = 0;

if (testBound.right - testBound.left < displayWidth) {

postX = displayWidth / 2f - (testBound.right + testBound.left) / 2f;

} else if (testBound.left > 0) {

postX = -testBound.left;

} else if (testBound.right < displayWidth) {

postX = displayWidth - testBound.right;

}

……

//应用修正位置

animEnd.postTranslate(postX, postY);

这里修正位置很容易看懂,就不说了,纠正源码的两个错误: postX = displayWidth / 2f - (testBound.right + testBound.left) / 2f; 里的 testBound.right + testBound.left 应为 testBound.right - testBound.left。没贴出来的 postY 也要改下。

1.2 惯性滑动(Fling)

PinchImageView 的惯性滑动是自己处理衰减的…… 每次衰减的程度还一样,不支持插值器,比起PhotoView 使用 OverScroller 来处理滑动,就显得有点简陋了。 GestureDetectoronFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) 包含x、y轴的加速度,加速度单位是像素/秒,每秒60帧,转换成像素/帧即 velocityX/60velocityY/60PinchImageView 使用 FlingAnimator来做动画,动画更新初始滑动距离 velocityX/60,然后乘以衰减值(FLING_DAMPING_FACTOR,0.9),待下次更新使用。

//移动图像并给出结果

boolean result = scrollBy(mVector[0], mVector[1], null);

mVector[0] *= FLING_DAMPING_FACTOR;

mVector[1] *= FLING_DAMPING_FACTOR;

//速度太小或者不能移动了就结束

if (!result || MathUtils.getDistance(0, 0, mVector[0], mVector[1]) < 1f) {

animation.cancel();

}

scrollBy(float xDiff, float yDiff, MotionEvent motionEvent) 方法处理滚动,主要考虑图片边界和控件边界的处理,跟上面缩放时的修正位置是一样的原理,图片边界的获取也跟缩放时是一样的。

//获取内部变换矩阵

matrix = getInnerMatrix(matrix);

//乘上外部变换矩阵

matrix.postConcat(mOuterMatrix);

rectF.set(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

matrix.mapRect(rectF);

最后对 mOuterMatrix 进行平移变换(postTranslate),invalidate 触发 onDraw 给图片设置新矩阵。

3.2 双指缩放、单指移动

双指缩放、单指移动是在 onTouch 里做的。

3.2.1 双指缩放

原理:记录双指在屏幕上距离,外部矩阵的缩放值与此距离相除的商为单位距离的缩放值,以这个缩放值去乘以双指滑动后的距离得到一个新的缩放值,用这个缩放值给外部矩阵做缩放变换得到最终的外部矩阵。

很明显,mScaleBase 这个单位距离的缩放值是斜率,决定了双指缩放的速度。那么决定双指缩放速度的因素有:当前外部矩阵的缩放大小、双指间初始距离。外部矩阵缩放越大,双指间初始距离越小,双指滑动缩放越快。 还有一个要注意的是图片的缩放中心点,在 PinchImageView 中,双指缩放变换是在单位矩阵中进行的。所以当双指按下的时候需要记录外部矩阵变换之前的中心点,源码里用 mScaleCenter 成员变量来记录这个点(PS:建议肉眼屏蔽源码里在所有用到这个变量地方的注释,你会晕的)。 快速看下相关的代码:

private PointF mScaleCenter = new PointF();

private float mScaleBase = 0;

……

public boolean onTouchEvent(MotionEvent event) {

……

int action = event.getAction() & MotionEvent.ACTION_MASK;

if (action == MotionEvent.ACTION_POINTER_DOWN) {

//切换到缩放模式

mPinchMode = PINCH_MODE_SCALE;

//保存缩放的两个手指

saveScaleContext(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

}else if (action == MotionEvent.ACTION_MOVE) {

……

//两个缩放点间的距离

float distance = MathUtils.getDistance(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

//保存缩放点中点

float[] lineCenter = MathUtils.getCenterPoint(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

mLastMovePoint.set(lineCenter[0], lineCenter[1]);

//处理缩放

scale(mScaleCenter, mScaleBase, distance, mLastMovePoint);

……

}

}

在多指按下的时候记录当前的是双指缩放模式,saveScaleContext()记录上面提到的 mScaleBasemScaleCenter 。在 MotionEvent.ACTION_MOVE 里处理缩放逻辑。看下 saveScaleContext 的处理。

private void saveScaleContext(float x1, float y1, float x2, float y2) {

mScaleBase = MathUtils.getMatrixScale(mOuterMatrix)[0] / MathUtils.getDistance(x1, y1, x2, y2);

float[] center = MathUtils.inverseMatrixPoint(MathUtils.getCenterPoint(x1, y1, x2, y2), mOuterMatrix);

mScaleCenter.set(center[0], center[1]);

}

mScaleBase 上面已经讲过了,这里主要提下 inverseMatrixPoint,看下方法定义:

public static float[] inverseMatrixPoint(float[] point, Matrix matrix) {

if (point != null && matrix != null) {

float[] dst = new float[2];

//计算matrix的逆矩阵

Matrix inverse = matrixTake();

matrix.invert(inverse);

//用逆矩阵变换point到dst,dst就是结果

inverse.mapPoints(dst, point);

//清除临时变量

matrixGiven(inverse);

return dst;

} else {

return new float[2];

}

}

srcMatrix.invert(targetMatrix)srcMatrix 矩阵的逆矩阵存到 targetMatrix 中,martrix.mapPoints(targetPoint, srcPoint);srcPoint 应用矩阵变换并存放到 targetPoint 中。很明显这个方法的作用的是得到经过矩阵变换之前的点。 mScaleCenter 存的正是外部矩阵变换之前的点的位置。 接下来看下缩放的处理。

private void scale(PointF scaleCenter, float scaleBase, float distance, PointF lineCenter) {

……

//计算图片从fit center状态到目标状态的缩放比例

float scale = scaleBase * distance;

Matrix matrix = MathUtils.matrixTake();

//按照图片缩放中心缩放,并且让缩放中心在缩放点中点上

matrix.postScale(scale, scale, scaleCenter.x, scaleCenter.y);

//让图片的缩放中点跟随手指缩放中点

matrix.postTranslate(lineCenter.x - scaleCenter.x, lineCenter.y - scaleCenter.y);

mOuterMatrix.set(matrix);

……

}

很容易看懂,上面都讲过了。这里吐槽一下,如果 mOuterMatrix 发生过错切、旋转、透视变换,那不就废了吗? 还有一个多个手指抬起一个手指的情况。注释已经修改过了,很容易看懂。

if (action == MotionEvent.ACTION_POINTER_UP) {

if (mPinchMode == PINCH_MODE_SCALE) {

//event.getPointerCount()表示抬起手指时点的数量,包含抬起的那个点

if (event.getPointerCount() > 2) {

//event.getAction() >> 8得到的是当前抬起的点的索引。第一个点抬起了,那么让第二个点和第三个点作为缩放控制点

if (event.getAction() >> 8 == 0) {

saveScaleContext(event.getX(1), event.getY(1), event.getX(2), event.getY(2));

//第二个点抬起了,那么让第一个点和第三个点作为缩放控制点

} else if (event.getAction() >> 8 == 1) {

saveScaleContext(event.getX(0), event.getY(0), event.getX(2), event.getY(2));

}

}

//如果抬起的点等于2,那么此时只剩下一个点,也不允许进入单指模式,因为此时可能图片没有在正确的位置上

}

}

最后需要在松手的时候修正下边界。进入 scaleEnd 方法。大多数代码其实刚才都分析过了,这里只讲一个变量,scalePost

private void scaleEnd() {

……

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

img

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
3942451)]

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-X84Kr9jI-1712613942452)]

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值