引言
阅览图片是生活中必不可少的事,而这个过程包括了许多的操作比如拖动图片,双击放大缩小,双指拖动固定区域放大等。笔者直接贴出自己写的代码然后逐步分析设置思路。
设计源码
public static void setGesture(final ImageView image) {
imageView = image;
defeautHeight = imageView.getHeight(); //得到图片默认宽度
defeautWidth = imageView.getWidth(); //得到图片默认高度
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
/**
* 先进行双击判断,单个手指操作返回false,执行后续
*/
if (gesture(event)) ;
/***
* 单个手指操作,先判断是否双击, 超过规定时间则返回false交给后续
*/
else if (isdbclick(event)) {
if (i == 0) {
Animator.twoClickAnimator(imageView, MAXRATIO);
i = 1;
} else if (i == 1) {
Animator.twoClickAnimator(imageView, MINRATIO);
i = 0;
}
}
/***
* 既不是双指操作也不是双击则执行ImageView的move
*/
else move(event);
return true;
}
});
}
setGesture()为唯一外部可调用方法,传入一个ImageView该工具类便为其设置onTouch事件,当然事件里包括了移动双击以及双指的操作。gesture(event)用于判断处理双指操作,isdbclick(event)用于判断处理双击操作,move(event)用于处理滑动操作,也就是说核心分为三步, 笔者用到if else结构意味着这几个操作是独立运行的。也就是在双指放大过程不能相应移动或者双击。从易到难,先了解move(event)方法。
三部曲一 move(event)
private static void move(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
downX = (int) event.getX(); //downX 记录move()方法down事件距ImageView左边距离
downY = (int) event.getY(); //同上
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (tooAnimalEnd == true) { //动画执行结束,由于部分动画是我们通过ValuesAnimator定义的,所以在动画执行期间操作使得视觉感很差,所以用此来控制
difference_X += (int) (event.getX() - downX); //发生一个move事件记录X方向偏移量
difference_Y += (int) (event.getY() - downY); //同上
if (Math.abs(difference_X) > refreshValues) { //refreshValues 为自定义的偏移量阀值,超过这个值则刷新ImageView的位置
imageView.setX(imageView.getX() + difference_X); //刷新ImageView的位置
difference_X = 0; //清空偏移量记录值
}
if (Math.abs(difference_Y) > refreshValues) {
imageView.setY(imageView.getY() + difference_Y);
difference_Y = 0;
}
} else { //执行动画期间,暂不考虑
downX = (int) event.getX();
downY = (int) event.getY();
}
}
if (event.getAction() == MotionEvent.ACTION_UP) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
decideBigLocationBounds(); //判断完全放大化图片是否偏离屏幕缘边
}
}, 200); //防止多动画重叠引起数据异常所以延迟200ms执行
decideLittleLocationBounds(); //判断未完全放大化图片是否脱离屏幕边缘
}
}
总结:move(event)方法并不难,只是其他的操作会影响图片的显示效果所以要在内部加上判断、tooAnimalEnd会在所有动画开始的时候值为false,动画结束为true,所以播放动画过程不能移动。而down事件却可以响应并附相关值所以要在
move中用else{}不断匹配down的值,以保证下次操作正常。
为什么会有 decideBigLocationBounds()与 decideLittleLocationBounds()主要是产生的动画效果不同所以笔者还是区分开了。
紧接着move()方法内部看下decideBigLocationBounds()。
decideBigLocationBounds();
这个方法笔者先上效果图
当ImageView的宽度或者高度超过屏幕,此时当一方发生偏移后则反弹回原始位置,看源码
/*
判断放大后图片位置
*/
private static void decideBigLocationBounds() {
if (getWidth() > point.x) {
if (getLeft() > 0) {
Animator.locationBounds(imageView, LEFT_BOUNDS);
}
if (getRight() < point.x) {
Animator.locationBounds(imageView, RIGHT_BOUNDS);
}
}
if (getHeight() > point.y) {
if (getTop() > 0) {
Animator.locationBounds(imageView, TOP_BOUNDS);
}
if (getBottom() < point.y) {
Animator.locationBounds(imageView, BOTTOM_BOUNDS);
}
}
}
其中point.x point.y封装了屏幕的宽高,当触发条件后则执行 Animator.locationBounds()方法, Animayor类主要负责所有动画操作,当前了解locationBounds()即可
Animator.locationBounds()
public static void locationBounds(ImageView imageView, int bounds) {
switch (bounds) {
case LEFT_BOUNDS:
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView, "translationX", imageView.getTranslationX(), imageView.getTranslationX() - getLeft());
objectAnimator1.setDuration(200);
objectAnimator1.addListener(new AnimatorAdapter());
objectAnimator1.start();
break;
case TOP_BOUNDS:
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView, "translationY", imageView.getTranslationY(), imageView.getTranslationY() - getTop());
objectAnimator2.setDuration(200);
objectAnimator2.addListener(new AnimatorAdapter());
objectAnimator2.start();
break;
case RIGHT_BOUNDS:
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(imageView, "translationX", imageView.getTranslationX(), imageView.getTranslationX() + point.x - getRight());
objectAnimator3.setDuration(200);
objectAnimator3.addListener(new AnimatorAdapter());
objectAnimator3.start();
break;
case BOTTOM_BOUNDS:
ObjectAnimator objectAnimator4 = ObjectAnimator.ofFloat(imageView, "translationY", imageView.getTranslationY(), imageView.getTranslationY() + point.y - getBottom());
objectAnimator4.setDuration(200);
objectAnimator4.addListener(new AnimatorAdapter());
objectAnimator4.start();
break;
}
}
其实就是左上右下四中类型的ObjectAnimator, bounds用于控制那种方向的动画,执行动画会使得ImageView移动到屏幕边缘,注意我们添加的Listenner与上述动画开始结束的标志tooAnimalEnd 息息相关,看下AnimatorAdapter
AnimatorAdapter
private static class AnimatorAdapter extends AnimatorListenerAdapter {
@Override
public void onAnimationStart(android.animation.Animator animation) {
super.onAnimationStart(animation);
tooAnimalEnd = false;
}
@Override
public void onAnimationEnd(android.animation.Animator animation) {
super.onAnimationEnd(animation);
tooAnimalEnd = true;
}
}
没错,这个适配器仅仅只是提供了动画开始结束的标志。那么decideBigLocationBounds()就到位了,接下来瞧瞧 decideLittleLocationBounds()。
decideLittleLocationBounds()
我们叫它未完全放大化的图片,具体怎样算未放大化我们暂不考虑,只需知道它的大小在某个范围就行,
private static void decideLittleLocationBounds() {
/*
判断未完全放大化图片位置
*/
if (getRight() < 0 || getBottom() < 0 || getLeft() > point.x || getTop() > point.y) {
imageView.setX(point.x / 2 - getWidth() / 2);
imageView.setY(point.y / 2 - getHeight() / 2);
}
}
很简单,当这个ImageView完全脱离屏幕可视区域后将其重新设定为屏幕中心,看下效果。
ok这样move(event)方法就到此结束了,紧接着isdbclick(event)方法。
三部曲二 isdbclick(event)
这个方法定义为判断双击,怎样算双击呢? 在规定的时间里再次点击首次触碰的区域及为双击,直接看源码;
private static Boolean isdbclick(MotionEvent event) {
Boolean back = false; //返回值标志是否双击
timer = new Timer(); //Timer计时器
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (isClick == false) { //首次点击是isClick为false,
isClick = true; //再次触摸isClick为true
fristClick[0] = (int) event.getX(); //得到第一次点击时X
fristClick[1] = (int) event.getY(); //得到第一次点击时Y
timer.schedule(new TimerTask() {
@Override
public void run() { //第一次点击时开启一个计时器,
isClick = false; //当超过规定时间重新初始化isClick=false,表示未点击
fristClick[0] = 0; //清空XY值
fristClick[1] = 0;
}
}, 250);
}
/**
* 时间在范围内,判断位置是否合适
*/
else { //在规定的时间内第二次的触摸
sencondClick[0] = (int) event.getX(); //得到第二次触摸的X,Y值
sencondClick[1] = (int) event.getY();
/***
*判断第二次触摸是否在规定的区域内
*/
if (Math.abs(sencondClick[0] - fristClick[0]) < 100 && Math.abs(sencondClick[1] - fristClick[1]) < 100) {
back = true; // 是
} else {
sencondClick[0] = 0; //不在规定的区域则清空记录值
sencondClick[1] = 0;
}
isClick = false; //重新初始化isClick=false,表示未点击
timer.cancel(); // 结束这个timer
}
}
return back;
}
解释笔者已经注释在内了,最后得到的back即是否满足了双击,不用往上翻了
/***
* 单个手指操作,先判断是否双击, 超过规定时间则返回false交给后续
*/
else if (isdbclick(event)) {
if (i == 0) {
Animator.twoClickAnimator(imageView, MAXRATIO);
i = 1;
} else if (i == 1) {
Animator.twoClickAnimator(imageView, MINRATIO);
i = 0;
}
}
i记录放大还是缩小,所以判断完双击后最后还是交给Animator.twoClickAnimator()方法执行,所以继续…..
Animator.twoClickAnimator();
public static void twoClickAnimator(final ImageView imageView, float ratio) {
/***
* imageView.getWidth() / defeautWidth 比值得到当前倍率,并达到目标倍率实现规定的放大缩小
*/
ValueAnimator valueAnimator = ValueAnimator.ofFloat(imageView.getWidth() / defeautWidth, ratio);
valueAnimator.setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float changeful = (float) animation.getAnimatedValue(); //得到不断变化的倍率值
int newWidth = (int) (defeautWidth * changeful); //得到新的宽度
int newHeight = (int) (defeautHeight * changeful); //得到新的高度
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) imageView.getLayoutParams();
layoutParams.width = newWidth;
layoutParams.height = newHeight;
imageView.setLayoutParams(layoutParams); //重新设置ImageView的宽高
}
});
valueAnimator.addListener(new AnimatorAdapter());
valueAnimator.setInterpolator(new LinearInterpolator()); //线性变化
valueAnimator.start();
/***
* 如果是缩小则判断缩小后图片是否脱离可视区域,并做相关处理
*/
if (ratio == MINRATIO) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
decideLittleLocationBounds(); //判断缩小后ImageView是否脱离屏幕
}
}, 300);
}
if (ratio == MAXRATIO) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
decideBigLocationBounds(); //判断放大后ImageView是否发生偏离
}
}, 300);
}
}
注意参数ratio 表示图片的最大最小化倍率,记得上述我们说的完全放大化其实就是倍率最大时,我们从isdbclick(event)方法里控制放大还是缩小。这里可能会有一个疑问,我们通过原始的FrameLayout.LayoutParams重新赋予新的宽高后,那他的位置是否发生变化。 结果证明:位置发生变化了即getX(),getY()发生变化,但图片的中心却没有变化,看下我的测试方法:
Log.e(""+(getLeft()+getRight())/2,""+(getTop()+getBottom()/2));
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) imageView.getLayoutParams();
layoutParams.width = newWidth;
layoutParams.height = newHeight;
imageView.setLayoutParams(layoutParams); //重新设置ImageView的宽高
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(""+(getLeft()+getRight())/2,""+(getTop()+getBottom()/2));
}
}).start();
}
});
getLeft(),是我自定义的方法内部是getX()所以测试准确, 我再变化前测了一遍,最后线程延迟再次测量,看下结果
可以看到x的值保持不变,而y的值由于高度差不同所以不够精确,不过这个已经可以证明基于原LayoutParams的变化是中心变化。
ok isdbclick(event)也就到此结束了,最后一个也是相对最难的一个。
三部曲三 gesture(event)
双指操作即是多点触控那就少不了相关MotionEvent,先了解一下各种事件
MotionEvent.ACTION_DOWN
接收到第一个触摸点的Down事件
MotionEvent.ACTION_POINTER_DOWN
接收到两个及两个以上触摸点后的Down事件
MotionEvent.ACTION_MOVE
任意一个触摸点发生移动
MotionEvent.ACTION_POINTER_UP
两个及两个以上触摸点其中任意一个停止触摸
注意:多个触摸点的事件判断event.getAction() & MotionEvent.ACTION_MASK
event.getPointerCount()
可以得到当前触摸点个数
event.getX(index)
根据触摸的先后顺序得到不同点的getX()值
贴出源码:
private static Boolean gesture(MotionEvent event) {
int touchIndex = event.getPointerCount(); //得到当前的触摸个数
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
/***
* 第二个手指触摸
*/
fristTouchX = (int) event.getX(0); //得到第一个手指getX()值
fristTouchY = (int) event.getY(0);
secondTouchX = (int) event.getX(1); //得到第二个手指getX()值
secondTouchY = (int) event.getY(1);
double a = Math.abs(fristTouchX - secondTouchX);
double b = Math.abs(fristTouchY - secondTouchY);
length = Math.sqrt(a * a + b * b); //得到两个触摸点之间的距离, 固定值
isTwoTouch = true; // 双指操作标记,并作为返回值返回,阻止isdbclick(event)以及move(event)执行
changeWidth = getWidth(); //changeWidth主要记录变化的Width中上次的Width值,先在down事件中初始化
changeHeight = getHeight(); //同上
twoTouchBeforeRatio = (float) getWidth() / defeautWidth; //双指操作之前的图片倍率,由于双指操作放大是根据默认的宽高值进行的所以需要保存当前的倍率。
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (touchIndex > 1) {
double newfristTouchX = event.getX(0); // 新的不断变化的getX()值
double newfristTouchY = event.getY(0);
double newsecondTouchX = event.getX(1); //同上
double newsecondTouchY = event.getY(1);
double a = Math.abs(newfristTouchX - newsecondTouchX);
double b = Math.abs(newfristTouchY - newsecondTouchY);
double newLenght = Math.sqrt(a * a + b * b); //新的不断变化的距离值
float multiplying = (float) (newLenght / length); //根据距离得到一个线性变化的比值
float[] differenceValues = getdifferences(getCenter(newfristTouchX, newfristTouchY, newsecondTouchX, newsecondTouchY)); //局部放大的差值
/***
* 执行imageView的放大移动
*/
Animator.twoTouchBig(multiplying + twoTouchBeforeRatio - 1, differenceValues);
}
}
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
/**
* 单手指离开
*/
if ((float) getWidth() / defeautWidth > MAXRATIO) {
Animator.tooMuchReboundAndBig(imageView, MAXRATIO);
}
if ((float) getWidth() / defeautWidth < MINRATIO) {
Animator.tooMuchReboundAndBig(imageView, MINRATIO);
}
decideLittleLocationBounds();
}
if (event.getAction() == MotionEvent.ACTION_UP) {
/***
* 所有手指离开
*/
isTwoTouch = false;
}
return isTwoTouch;
}
基本的解释在内了,双指操作核心就在于得到一个呈线性变化的值,然后将这个值与ImageView的宽高发生关系所以ImageView也就呈线性变化了,所以最合适的就是之间的距离。现在分析上面的方法。
getCenter()
private static int[] getCenter(double firstX, double fristY, double secondX, double secondY) {
int centerX = (int) ((firstX + secondX) / 2);
int centerY = (int) ((fristY + secondY) / 2);
return new int[]{centerX, centerY};
}
根据参数传入的两个点坐标得到触控中心的坐标并将其返回。
getdifferences(int[] touchCenter)
核心方法, 固定位置放大的算法就在这里实现的
private static float[] getdifferences(int[] touchCenter) {
/***
* changeWidth 上次的Width
* getWidth() 当前的Width
* differenceXMax 边缘的最大变化值
*/
float differenceXMax = (changeWidth - getWidth()) / 2;
float differenceYMax = (changeHeight - getHeight()) / 2;
/*
*根据相似求出触摸中心发生的变化值的比率
*/
float ratioX = (float) (getWidth() / 2 - touchCenter[0]) / (changeWidth / 2);
float ratioY = (float) (getHeight() / 2 - touchCenter[1]) / (changeHeight / 2);
/***
* 得到触摸中心的变化值
*/
float touchDifferenceX = differenceXMax * ratioX;
float touchDifferenceY = differenceYMax * ratioY;
changeWidth = getWidth(); //更新宽度
changeHeight = getHeight();
return new float[]{-touchDifferenceX, -touchDifferenceY};
}
由于放大缩小是按照中心变化的所以我们根据相似可求得放大后触控中心的偏移量,再将它原封不动移回去即可。所以返回值作为还原的参数,接下来就是将ImageVIew放大缩小并将其还原的方法。
twoTouchBig()
public static void twoTouchBig(float ratio, float[] differenceValues) {
int newWidth = (int) (ratio * defeautWidth); //根据默认宽高以及传入的倍率不断更改实际倍率
int newHeight = (int) (ratio * defeautHeight);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) imageView.getLayoutParams();
layoutParams.width = newWidth;
layoutParams.height = newHeight;
imageView.setLayoutParams(layoutParams);
/***
* 注意上述过程ImageView仍然是中心放大缩小
*/
imageView.setTranslationX(imageView.getTranslationX() + differenceValues[0]); //根据触控中心偏移量移回去
imageView.setTranslationY(imageView.getTranslationY() + differenceValues[1]);
}
注意我们在调用这个方法时候传入倍率为(multiplying + twoTouchBeforeRatio - 1)multiplying为触控距离比率, twoTouchBeforeRatio为ImageView变化前倍率,由于我们需要的是根据距离使得ImageView变化量从零开始所以最后 -1。ok 固定位置放大到这里就结束了。 当然最后还剩下一个方法。
tooMuchReboundAndBig()
如果双指操作大于小于我们规定的最大或者最小倍率时如果不加以控制那最大最小不就没有意义了,所以这个方法用于纠正变化量,在双指操作结束时判断。
public static void tooMuchReboundAndBig(final ImageView imageView, final float ratio) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat((float) getWidth() / defeautWidth, ratio);
valueAnimator.setDuration(200);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) imageView.getLayoutParams();
float changeful = (float) animation.getAnimatedValue();
layoutParams.width = (int) (changeful * defeautWidth);
layoutParams.height = (int) (changeful * defeautHeight);
imageView.setLayoutParams(layoutParams);
}
});
valueAnimator.addListener(new AnimatorAdapter());
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.start();
}
上述即从当前的倍率大小变化到我们规定的倍率大小。
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
/**
* 单手指离开
*/
if ((float) getWidth() / defeautWidth > MAXRATIO) {
Animator.tooMuchReboundAndBig(imageView, MAXRATIO);
}
if ((float) getWidth() / defeautWidth < MINRATIO) {
Animator.tooMuchReboundAndBig(imageView, MINRATIO);
}
}
当一个手指离开时进行判断,如果宽度(高度)大于小于我们规定的值时就将其纠正回来。
if (event.getAction() == MotionEvent.ACTION_UP) {
/***
* 所有手指离开
*/
isTwoTouch = false;
}
最后的up事件返回false,等于将事件交给move(event)的up 判断图片的位置是否合适。最后看下完整的效果图
完整的源码:
package shuai.com.gesture;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.Point;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by Administrator on 2018/3/31.
*/
public class ImageViewGesture {
static ImageView imageView;
static Point point = new Point();
static Timer timer = new Timer();
static int i = 0;
static int MAXRATIO = 4;
static int MINRATIO = 1;
static final int LEFT_BOUNDS = 1;
static final int TOP_BOUNDS = 2;
static final int RIGHT_BOUNDS = 3;
static final int BOTTOM_BOUNDS = 4;
static int changeWidth; //双指放大操作时,变化前的left
static int changeHeight; //双指放大操作时,变化前的top
static int defeautWidth;
static int defeautHeight;
static float twoTouchBeforeRatio;
static Boolean tooAnimalEnd = true;
static int refreshValues=1;
/***
* 判断是否双击
* @param image
*/
static int[] fristClick = new int[2];
static int[] sencondClick = new int[2];
static Boolean isClick = false;
/***
* 计算触摸移动
* @param imageView
*/
static double downX = 0;
static double downY = 0;
static int difference_X = 0;
static int difference_Y = 0;
/***
* 双指手势操作
* @param image
*/
static double fristTouchX;
static double fristTouchY;
static double secondTouchX;
static double secondTouchY;
static double length;
static Boolean isTwoTouch = false;
static Handler handler = new Handler();
public static void setGesture(final ImageView image) {
imageView = image;
defeautHeight = imageView.getHeight();
defeautWidth = imageView.getWidth();
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
/**
* 先进行双击判断,单个手指操作返回false,执行后续
*/
if (gesture(event)) ;
/***
* 单个手指操作,先判断是否双击, 超过规定时间则返回false交给后续
*/
else if (isdbclick(event)) {
if (i == 0) {
Animator.twoClickAnimator(imageView, MAXRATIO);
i = 1;
} else if (i == 1) {
Animator.twoClickAnimator(imageView, MINRATIO);
i = 0;
}
}
/***
* 既不是双指操作也不是双击则执行ImageView的move
*/
else move(event);
return true;
}
});
}
private static Boolean isdbclick(MotionEvent event) {
Boolean back = false; //返回值标志是否双击
timer = new Timer(); //Timer计时器
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (isClick == false) { //首次点击是isClick为false,
isClick = true; //再次触摸isClick为true
fristClick[0] = (int) event.getX(); //得到第一次点击时X
fristClick[1] = (int) event.getY(); //得到第一次点击时Y
timer.schedule(new TimerTask() {
@Override
public void run() { //第一次点击时开启一个计时器,
isClick = false; //当超过规定时间重新初始化isClick=false,表示未点击
fristClick[0] = 0; //清空XY值
fristClick[1] = 0;
}
}, 250);
}
/**
* 时间在范围内,判断位置是否合适
*/
else { //在规定的时间内第二次的触摸
sencondClick[0] = (int) event.getX(); //得到第二次触摸的X,Y值
sencondClick[1] = (int) event.getY();
/***
*判断第二次触摸是否在规定的区域内
*/
if (Math.abs(sencondClick[0] - fristClick[0]) < 100 && Math.abs(sencondClick[1] - fristClick[1]) < 100) {
back = true; // 是
} else {
sencondClick[0] = 0; //不在规定的区域则清空记录值
sencondClick[1] = 0;
}
isClick = false; //重新初始化isClick=false,表示未点击
timer.cancel(); // 结束这个timer
}
}
return back;
}
private static void move(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
downX = (int) event.getX();
downY = (int) event.getY();
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (tooAnimalEnd == true) { //动画执行结束
difference_X += (int) (event.getX() - downX);
difference_Y += (int) (event.getY() - downY);
if (Math.abs(difference_X) > refreshValues) {
imageView.setX(imageView.getX() + difference_X);
difference_X = 0;
}
if (Math.abs(difference_Y) > refreshValues) {
imageView.setY(imageView.getY() + difference_Y);
difference_Y = 0;
}
} else {
downX = (int) event.getX();
downY = (int) event.getY();
}
}
if (event.getAction() == MotionEvent.ACTION_UP) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
decideBigLocationBounds();
}
}, 200);
decideLittleLocationBounds();
}
}
private static Boolean gesture(MotionEvent event) {
int touchIndex = event.getPointerCount(); //得到当前的触摸个数
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
/***
* 第二个手指触摸
*/
fristTouchX = (int) event.getX(0); //得到第一个手指getX()值
fristTouchY = (int) event.getY(0);
secondTouchX = (int) event.getX(1); //得到第二个手指getX()值
secondTouchY = (int) event.getY(1);
double a = Math.abs(fristTouchX - secondTouchX);
double b = Math.abs(fristTouchY - secondTouchY);
length = Math.sqrt(a * a + b * b); //得到两个触摸点之间的距离, 固定值
isTwoTouch = true; // 双指操作标记,并作为返回值返回,阻止isdbclick(event)以及move(event)执行
changeWidth = getWidth(); //changeWidth主要记录变化的Width中上次的Width值,先在down事件中初始化
changeHeight = getHeight(); //同上
twoTouchBeforeRatio = (float) getWidth() / defeautWidth; //双指操作之前的图片倍率,由于双指操作放大是根据默认的宽高值进行的所以需要保存当前的倍率。
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (touchIndex > 1) {
double newfristTouchX = event.getX(0); // 新的不断变化的getX()值
double newfristTouchY = event.getY(0);
double newsecondTouchX = event.getX(1); //同上
double newsecondTouchY = event.getY(1);
double a = Math.abs(newfristTouchX - newsecondTouchX);
double b = Math.abs(newfristTouchY - newsecondTouchY);
double newLenght = Math.sqrt(a * a + b * b); //新的不断变化的距离值
float multiplying = (float) (newLenght / length); //根据距离得到一个线性变化的比值
float[] differenceValues = getdifferences(getCenter(newfristTouchX, newfristTouchY, newsecondTouchX, newsecondTouchY)); //局部放大的差值
/***
* 执行imageView的放大移动
*/
Animator.twoTouchBig(multiplying + twoTouchBeforeRatio - 1, differenceValues);
}
}
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
/**
* 单手指离开
*/
if ((float) getWidth() / defeautWidth > MAXRATIO) {
Animator.tooMuchReboundAndBig(imageView, MAXRATIO);
}
if ((float) getWidth() / defeautWidth < MINRATIO) {
Animator.tooMuchReboundAndBig(imageView, MINRATIO);
}
}
if (event.getAction() == MotionEvent.ACTION_UP) {
/***
* 所有手指离开
*/
isTwoTouch = false;
}
return isTwoTouch;
}
/*
判断放大后图片位置
*/
private static void decideBigLocationBounds() {
if (getWidth() > point.x) {
if (getLeft() > 0) {
Animator.locationBounds(imageView, LEFT_BOUNDS);
}
if (getRight() < point.x) {
Animator.locationBounds(imageView, RIGHT_BOUNDS);
}
}
if (getHeight() > point.y) {
if (getTop() > 0) {
Animator.locationBounds(imageView, TOP_BOUNDS);
}
if (getBottom() < point.y) {
Animator.locationBounds(imageView, BOTTOM_BOUNDS);
}
}
}
/*
判断未完全放大化图片位置
*/
private static void decideLittleLocationBounds() {
if (getRight() < 0 || getBottom() < 0 || getLeft() > point.x || getTop() > point.y) {
imageView.setX(point.x / 2 - getWidth() / 2);
imageView.setY(point.y / 2 - getHeight() / 2);
}
}
private static class AnimatorAdapter extends AnimatorListenerAdapter {
@Override
public void onAnimationStart(android.animation.Animator animation) {
super.onAnimationStart(animation);
tooAnimalEnd = false;
}
@Override
public void onAnimationEnd(android.animation.Animator animation) {
super.onAnimationEnd(animation);
tooAnimalEnd = true;
}
}
/**
* 所有动画行为交给这个类执行
*/
private static class Animator {
public static void tooMuchReboundAndBig(final ImageView imageView, final float ratio) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat((float) getWidth() / defeautWidth, ratio);
valueAnimator.setDuration(200);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) imageView.getLayoutParams();
float changeful = (float) animation.getAnimatedValue();
layoutParams.width = (int) (changeful * defeautWidth);
layoutParams.height = (int) (changeful * defeautHeight);
imageView.setLayoutParams(layoutParams);
}
});
valueAnimator.addListener(new AnimatorAdapter());
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.start();
}
public static void twoClickAnimator(final ImageView imageView, float ratio) {
/***
* imageView.getWidth() / defeautWidth 比值得到当前倍率,并达到目标倍率实现规定的放大缩小
*/
ValueAnimator valueAnimator = ValueAnimator.ofFloat(imageView.getWidth() / defeautWidth, ratio);
valueAnimator.setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float changeful = (float) animation.getAnimatedValue(); //得到不断变化的倍率值
int newWidth = (int) (defeautWidth * changeful); //得到新的宽度
int newHeight = (int) (defeautHeight * changeful); //得到新的高度
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) imageView.getLayoutParams();
layoutParams.width = newWidth;
layoutParams.height = newHeight;
imageView.setLayoutParams(layoutParams); //重新设置ImageView的宽高
}
});
valueAnimator.addListener(new AnimatorAdapter());
valueAnimator.setInterpolator(new LinearInterpolator()); //线性变化
valueAnimator.start();
/***
* 如果是缩小则判断缩小后图片是否脱离可视区域,并做相关处理
*/
if (ratio == MINRATIO) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
decideLittleLocationBounds(); //判断缩小后ImageView是否脱离屏幕
}
}, 300);
}
if (ratio == MAXRATIO) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
decideBigLocationBounds(); //判断放大后ImageView是否发生偏离
}
}, 300);
}
}
public static void locationBounds(ImageView imageView, int bounds) {
switch (bounds) {
case LEFT_BOUNDS:
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView, "translationX", imageView.getTranslationX(), imageView.getTranslationX() - getLeft());
objectAnimator1.setDuration(200);
objectAnimator1.addListener(new AnimatorAdapter());
objectAnimator1.start();
break;
case TOP_BOUNDS:
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView, "translationY", imageView.getTranslationY(), imageView.getTranslationY() - getTop());
objectAnimator2.setDuration(200);
objectAnimator2.addListener(new AnimatorAdapter());
objectAnimator2.start();
break;
case RIGHT_BOUNDS:
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(imageView, "translationX", imageView.getTranslationX(), imageView.getTranslationX() + point.x - getRight());
objectAnimator3.setDuration(200);
objectAnimator3.addListener(new AnimatorAdapter());
objectAnimator3.start();
break;
case BOTTOM_BOUNDS:
ObjectAnimator objectAnimator4 = ObjectAnimator.ofFloat(imageView, "translationY", imageView.getTranslationY(), imageView.getTranslationY() + point.y - getBottom());
objectAnimator4.setDuration(200);
objectAnimator4.addListener(new AnimatorAdapter());
objectAnimator4.start();
break;
}
}
public static void twoTouchBig(float ratio, float[] differenceValues) {
int newWidth = (int) (ratio * defeautWidth);
int newHeight = (int) (ratio * defeautHeight);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) imageView.getLayoutParams();
layoutParams.width = newWidth;
layoutParams.height = newHeight;
imageView.setLayoutParams(layoutParams);
/***
* 注意上述过程ImageView任然是中心放大缩小
*/
imageView.setTranslationX(imageView.getTranslationX() + differenceValues[0]);
imageView.setTranslationY(imageView.getTranslationY() + differenceValues[1]);
}
}
private static int getLeft() {
return (int) imageView.getX();
}
private static int getRight() {
return (int) (imageView.getX() + imageView.getWidth());
}
private static int getTop() {
return (int) imageView.getY();
}
private static int getBottom() {
return (int) (imageView.getY() + imageView.getHeight());
}
private static int getWidth() {
return imageView.getWidth();
}
private static int getHeight() {
return imageView.getHeight();
}
/*
* 此方法能得到双指操作的中心坐标
*/
private static int[] getCenter(double firstX, double fristY, double secondX, double secondY) {
int centerX = (int) ((firstX + secondX) / 2);
int centerY = (int) ((fristY + secondY) / 2);
return new int[]{centerX, centerY};
}
/*
* 此方法根据中心坐标计算出触控所在区域并进行放大缩小时移动
*/
private static float[] getdifferences(int[] touchCenter) {
/***
* changeWidth 上次的Width
* getWidth() 当前的Width
* differenceXMax 边缘的最大变化值
*/
float differenceXMax = (changeWidth - getWidth()) / 2;
float differenceYMax = (changeHeight - getHeight()) / 2;
/*
*根据相似求出触摸中心发生的变化值的比率
*/
float ratioX = (float) (getWidth() / 2 - touchCenter[0]) / (changeWidth / 2);
float ratioY = (float) (getHeight() / 2 - touchCenter[1]) / (changeHeight / 2);
/***
* 得到触摸中心的变化值
*/
float touchDifferenceX = differenceXMax * ratioX;
float touchDifferenceY = differenceYMax * ratioY;
changeWidth = getWidth(); //更新宽度
changeHeight = getHeight();
return new float[]{-touchDifferenceX, -touchDifferenceY};
}
}
注意ImageView设置width和height都为wrap_content ,在执行setGesture()方法时候确保imageView已经可视。