栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Android图片处理:PinchImageView源码解析,刚从阿里、头条面试回来

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android图片处理:PinchImageView源码解析,刚从阿里、头条面试回来

*/

public static Matrix matrixTake(Matrix matrix) {

Matrix result = mMatrixPool.take();

if (matrix != null) {

result.set(matrix);

}

return result;

}

然后去获取内部变换矩阵,并存在 innerMatrix 中。

public Matrix getInnerMatrix(Matrix matrix) {

……

//原图大小

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

//控件大小

RectF tempDst = MathUtils.rectFTake(0, 0, getWidth(), getHeight());

//计算fit center矩阵

matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);

……

return matrix;

}

MathUtils.rectFTake 跟 matrixTake 方法是一样的,只是取出的是 rectF 。关键在于 matrix.setRectToRect 方法,上面已经介绍过了。 继续往下看:

//当前总的缩放比例

float innerScale = MathUtils.getMatrixScale(innerMatrix)[0];

float outerScale = MathUtils.getMatrixScale(mOuterMatrix)[0];

float currentScale = innerScale * outerScale;

这里把内部矩阵的缩放和外部缩放相乘,得到了最终的缩放,内外不影响的设计确实挺好的。 接下来开始计算和进行缩放。

float nextScale = currentScale < MAX_SCALE ? MAX_SCALE : innerScale;

//如果接下来放大大于最大值或者小于fit center值,则取边界

if (nextScale > maxScale) {

nextScale = maxScale;

}

if (nextScale < innerScale) {

nextScale = innerScale;

}

//开始计算缩放动画的结果矩阵

Matrix animEnd = MathUtils.matrixTake(mOuterMatrix);

//计算还需缩放的倍数

animEnd.postScale(nextScale / currentScale, nextScale / currentScale, x, y);

//将放大点移动到控件中心

animEnd.postTranslate(displayWidth / 2f - x, displayHeight / 2f - y);

……

//启动矩阵动画

mScaleAnimator = new ScaleAnimator(mOuterMatrix, animEnd);

mScaleAnimator.start();

这段代码很骚,我们先来梳理下缩放的思路:双击图片,肯定是要以动画的形式来做的,那么动画的开头,自然是当前的变换位置,变换到目标缩放值 nextScale 的倍数是 nextScale / currentScale,遵从手势操作记录在外部矩阵 mOuterMatrix 的原则,动画初始 matrix 拷贝自 mOuterMatrix。 这段代码其实是有问题的。innerScale 是对原图进行 fitCenter 变换后的缩放值,假设原图很大,变换后 innerScale 值为0.2f, maxScale 为2,没有进行过手势操作,outerScale 为1,这时候来看下算的结果:

就是说你双击一下,一下子看到的图片放大了10倍…… 要知道现在很多图宽高都是比手机屏幕大的…… ScaleAnimator 里只做了一件事,不断更新 mOuterMatrix 的值,然后 invalidate ,在 onDraw 里刷新视图。

@Override

public void onAnimationUpdate(ValueAnimator animation) {

//获取动画进度

float value = (Float) animation.getAnimatedValue();

//根据动画进度计算矩阵中间插值

for (int i = 0; i < 9; i++) {

mResult[i] = mStart[i] + (mEnd[i] - mStart[i]) * value;

}

//设置矩阵并重绘

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 也要改下。

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

浏览器打开:qq.cn.hn/FTe 免费领取
1.2 惯性滑动(Fling)

PinchImageView 的惯性滑动是自己处理衰减的…… 每次衰减的程度还一样,不支持插值器,比起PhotoView 使用 OverScroller 来处理滑动,就显得有点简陋了。 GestureDetector 的 onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) 包含x、y轴的加速度,加速度单位是像素/秒,每秒60帧,转换成像素/帧即 velocityX/60 、 velocityY/60。PinchImageView 使用 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()记录上面提到的 mScalebase 和 mScaleCenter 。在 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 存的正是外部矩阵变换之前的点的位置。 接下来看下缩放的处理。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/463020.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号