最近需要用到一个类似微信图片的查看缩放功能,PhotoView本身的功能不太够用。于是将PhotoView的源码本地化,先读懂,再修改。对于android新人来说学到了不少东西。在这里做一下积累,以免以后忘记。
向下继续阅读请集成好PhotoView类库配合使用。
PhotoView库的核心类是PhotoViewAttacher,而他由几个核心构建构成。我们一个个拆开来看。
OnTouchListener
从监听事件这些驱动部件来入手容易一些。对于代码的解释我直接放在注释里。
@Override
public boolean onTouch(View view, MotionEvent event) {
boolean handled = false;
//先判断是否启用了缩放,view的drawable是否不为空
if (mZoomEnabled && ((ImageView) view).getDrawable() != null) {
switch (event.getAction()) {
//当用户的第一根手指触碰到我们的view的时候
case MotionEvent.ACTION_DOWN:
ViewParent parent = view.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
//取消左右翻页动画
cancelFling();
//MotionEvent.ACTION_DOWN中没有什么具体操作,我们看一下MotionEvent.ACTION_UP
break;
case MotionEvent.ACTION_CANCEL:
//当用于最后一根手指离开view的时候
case MotionEvent.ACTION_UP:
//判断当前的缩放比例是否小于约定的最小缩放比例
if (getScale() < DEFAULT_MIN_SCALE) {
//获取当前正在展示的矩形
RectF rect = getDisplayRect();
if (rect != null) {
//矩形不为空则 调用缩放动画
view.post(new AnimatedZoomRunnable(getScale(), DEFAULT_MIN_SCALE,
rect.centerX(), rect.centerY()));
handled = true;
}
}
break;
}
阅读完以上内容可得知,手指抬起时调用这个缩放动画,那就直接看这个AnimatiedZoomRunnable类中查看。不看代码,直接debug,或者打log看代码调用情况就可以知道,
private class AnimatedZoomRunnable implements Runnable {
private final float mFocalX, mFocalY;
private final long mStartTime;
private final float mZoomStart, mZoomEnd;
public AnimatedZoomRunnable(final float currentZoom, final float targetZoom,
final float focalX, final float focalY) {
//从上面传入的数据可以得知这些参数的作用
//矩形中心的横坐标
mFocalX = focalX;
//矩形中心的纵坐标
mFocalY = focalY;
//动画开启时的时间戳
mStartTime = System.currentTimeMillis();
//动画开始前 矩形的比例
mZoomStart = currentZoom;
//动画完成时想要到达的比例
mZoomEnd = targetZoom;
}
@Override
public void run() {
//先直接去看interpolate()方法
float t = interpolate();
//所以我自己的代码中直接为t 改了个名字 叫 progressPercent (进度百分比)
//当前应该缩放到的比例 = 原本的缩放比例 + 缩放进度百分比 x 需要缩放的全部的量
float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
// 当前需要缩放的比例 = 当前需要缩放到的比例 / 当前比例
float deltaScale = scale / getScale();
//调用 onScale方法
onScale(deltaScale, mFocalX, mFocalY);
//这里与前面插值器的内容对应。经历时间超过约定的动画演示时间后,t本身超过1。与1取更小返回1。插值器收到了1这个整数,返回结果也是1。最后这个t得出=1的值,不在循环调用AnimatedZoomRunnable。
if (t < 1f) {
Compat.postOnAnimation(mImageView, this);
}
}
private float interpolate() {
//首先1f * 计算结果的作用就是强转为float精度,效果与 (float)相同
//返回去查看mZoomDuration是约定好的缩放时间,所以PhotoView中设定的自动缩放动画无论比例大小都是相同时间完成的。
//所以这个参数t实际得到的是当前缩放动画应该完成到哪个阶段的百分比,也可以说是缩放进度的进度条。
float t = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration;
//下面这两句的作用实际是插值器,插值器是类似一种中间帧的概念,让本身速度不变的变化有了变化的节奏。PhotoView的作者采用的是 先加速后减速插值器。详细内容可以参考下面的网址,看我就能懂了
// https://blog.csdn.net/xiaochuanding/article/details/73200149
//与1取更小 是因为 获得的时间t是有时间间隔的,getInterpolation(t)如果不传入整数,他就会拿去继续插值,不会返回1。知道有一次碰巧t获取了一个整数值并传入。getInterpolation(t)才会传一个1回来。
//我之前测试过如果 Math.min(1.1f,t);与1.1来取最小,这样就永远返回不来整数,那这个插值器就不会永远返回不了1了。
t = Math.min(1f, t);
t = mInterpolator.getInterpolation(t);
//其实上面这两行直接删掉也没有什么不妥,个人认为短短200毫秒的动画变化,正常人也看不出来什么区别。
return t;
}
}