Android View跟随手势漫游缩放方法

//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));
    }
}

实现效果:

 

工程文件:

https://download.csdn.net/download/cjzjolly/10856128

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值