//20190509:上次做的缩放手势有点卡卡的不是很顺手,工作中已经修复,顺便再贴一次好用的过来。
本文的手势识别原理基于我上一篇文章:
https://blog.csdn.net/cjzjolly/article/details/85050221
而Android View的漫游缩放原理如下:
1、首先view的视觉宽度、高度缩放已知是通过setScaleX和setScaleY以view自身的中心进行缩放的,并且不会影响getWidth、getHeight得到的值——也就是说缩小view就像把view拉远看了一样而已,本质上没变小,而view的缩放不会影响getX,getY值——举个例子,当你把View缩放为原来的一半的大小的时候,实际视觉上看见的左上角的值为(只是额外提一下,实际不会用到):
(图中的0.5为假设的本次缩放到什么比例)
2、在调用了setScaleX和setScaleY对目标View的宽高进行了缩放后,就需要使目标View相对于缩放中心坐标进行收缩或者扩散,原理如下:
1)通过centerX = view.getX() + view.getWidth() / 2; centerY = view.getY() + view.getHeight() / 2;得到View的中心点
2)缩放中心px,py分别减去centerX,centerY,得到的差分别乘以(1-缩放量)后,值分给加给centerX,centerY,从而得到View新的中心位置。
3)view的新的中心位置分别减去view.getWidth() / 2和view.getHeight() / 2从而得到view应在的左上角位置,再送给setX,setY进行位置设定
原理图示:
(图中的0.8为假设的本次缩放到什么比例)
至此,缩放过程就完成了。
详细的View缩放移动代码如下:
package cjz.project.zoomandtranslateexample;
import android.content.Context;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Created by cjz on 2018/12/12.
*/
public class ZoomAndMoveFrameLayout extends FrameLayout{
private PointF currentCenter = new PointF();
private PointF prevCurrentCenter = null;
private float prevDistance = Float.MIN_VALUE;
public float totalScale = 1f;
private List<View> childViewList = new LinkedList<>();
/**
* 触摸点点距队列
**/
private Queue<Float> touchDistanceQueue = new LinkedBlockingQueue<>();
public ZoomAndMoveFrameLayout(Context context) {
super(context);
}
public ZoomAndMoveFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ZoomAndMoveFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private float avergeX = 0, avergeY = 0;
private int prevPointCount = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
prevDistance = 0;
prevPointCount = event.getPointerCount();
//算出移动中心坐标、点间距离
for(int i = 0; i < event.getPointerCount(); i++){
avergeX += event.getX(i);
avergeY += event.getY(i);
if(i + 1 < event.getPointerCount()){
prevDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2));
}
}
avergeX /= event.getPointerCount();
avergeY /= event.getPointerCount();
if(prevCurrentCenter == null){
prevCurrentCenter = new PointF(avergeX, avergeY);
} else {
prevCurrentCenter.set(avergeX, avergeY);
}
break;
case MotionEvent.ACTION_MOVE:
avergeX = 0;
avergeY = 0;
float nowDistance = 0;
//算出移动中心坐标、点间距离
for(int i = 0; i < event.getPointerCount(); i++){
avergeX += event.getX(i);
avergeY += event.getY(i);
if(i + 1 < event.getPointerCount()){
nowDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2));
}
}
//现在的点间距离 除以 上次点间距离 这次得到缩放比例
avergeX /= event.getPointerCount();
avergeY /= event.getPointerCount();
if((prevPointCount != event.getPointerCount()) || event.getPointerCount() <= 1 || prevPointCount <= 1){ //触摸点数突然改变 或者 触摸点不超过2,不允许缩放
prevDistance = nowDistance = 0;
}
//如果缩放数据有效,则进行平均平滑化并且进行缩放
if(prevDistance > 0 && nowDistance > 0){
touchDistanceQueue.add(nowDistance / prevDistance);
if(touchDistanceQueue.size() >= 6) {
Float point[] = new Float[touchDistanceQueue.size()];
touchDistanceQueue.toArray(point);
float avergDistance = 0;
for(int i = 0; i < point.length; i++){
avergDistance += point[i];
}
avergDistance /= point.length;
scale((float) Math.sqrt(avergDistance), avergeX, avergeY);
while(touchDistanceQueue.size() > 6){
touchDistanceQueue.poll();
}
}
}
prevPointCount = event.getPointerCount();
prevDistance = nowDistance;
//当前坐标 - 上次坐标 = 偏移值,然后进行位置偏移
if(prevCurrentCenter == null) {
prevCurrentCenter = new PointF(avergeX, avergeY);
} else {
translate(avergeX - prevCurrentCenter.x, avergeY - prevCurrentCenter.y);
prevCurrentCenter.set(avergeX, avergeY);
}
break;
case MotionEvent.ACTION_UP:
//抬起,清理干净数据
avergeX = 0;
avergeY = 0;
touchDistanceQueue.clear();
break;
}
return true;
//返回给ScaleGestureDetector来处理
// return mGestureDetector.onTouchEvent(event);
}
@Override
public void addView(View child) {
super.addView(child);
childViewList.add(child);
}
/**缩放函数**/
public void scale(float scale, float px, float py) {
for (View view : childViewList) {
//以本View中心点为缩放中心缩放
view.setScaleX(view.getScaleX() * scale);
view.setScaleY(view.getScaleY() * scale);
//求本view中心点在屏幕中的坐标
float centerX = view.getX() + view.getWidth() / 2;
float centerY = view.getY() + view.getHeight() / 2;
/**向缩放中心靠拢,例如缩放为原来的80%,那么缩放中心x到view中心x的距离则为0.8*(缩放中心x - view中心x),
* 那么view的x距离屏幕左边框的距离则 为 view中心x + (1 - 0.8) * (缩放x - view中心x) ****/
float centerXAfterScale = centerX + (px - centerX) * (1 - scale); //view中心向缩放中心聚拢或扩散
float centerYAfterScale = centerY + (py - centerY) * (1 - scale);
view.setX(centerXAfterScale - view.getWidth() / 2); //setXY是set左上角的x,y,所以view中心点要减去宽度/高度的一般来重新得到应该去的左上角坐标
view.setY(centerYAfterScale - view.getHeight() / 2);
// viewFind(view, this.scale);
Log.i("View" + view.hashCode() + "的信息", String.format("长度:%d, 宽度:%d, 坐标x:%f, 坐标y:%f", view.getWidth(), view.getHeight(), view.getX(), view.getY()));
}
Log.i("缩放", String.format("百分比:%f", totalScale));
}
/**移动函数**/
private void translate(float distanceX, float distanceY) {
for (View view : childViewList) {
view.setX(view.getX() + (distanceX));
view.setY(view.getY() + (distanceY));
Log.i("View" + view.hashCode() + "的信息", String.format("长度:%d, 宽度:%d, 坐标x:%f, 坐标y:%f", view.getWidth(), view.getHeight(), view.getX(), view.getY()));
}
Log.i("移动", String.format("x位移:%f, y位移:%f", distanceX, distanceY));
}
}
实现效果:
工程文件: