MotionEvent分析及ImageView缩放实现

这个类在各种View和用户的手势操作之间的交互存在很大的自定义空间。要理解清楚这个类的一些特性和意义,对自定义的新型控件很有帮助

先翻译一下开发者文档的描述

Overview

Motion events describe movements in terms of an action code and a set of axis values. The action code specifies the state change that occurred such as a pointer going down or up. The axis values describe the position and other movement properties.

运动事件描述了关于一个动作代码和一组轴值的动作。动作代码描述的是状态改变的发生,例如一个触点按下或者抬起。轴值描述的是位置和其他运动属性。

For example, when the user first touches the screen, the system delivers a touch event to the appropriate View with the action code ACTION_DOWN and a set of axis values that include the X and Y coordinates of the touch and information about the pressure, size and orientation of the contact area.

例如,当用户第一次按下屏幕,系统将带有动作代码为ACTION_DOWN和一族含有x、y坐标和压力、大小、方向轴值的触摸时间传递给适当的View。

Some devices can report multiple movement traces at the same time. Multi-touch screens emit one movement trace for each finger. The individual fingers or other objects that generate movement traces are referred to as pointers. Motion events contain information about all of the pointers that are currently active even if some of them have not moved since the last event was delivered.

一些设备能够同时反馈多个运动。多点触控屏能够为每一根手指生成单独的运动轨迹。单独的手指或者其他能够产生运动轨迹的对象都被叫做pointers。运动事件包含了所有这些当前活动的pointers即使其中一些并没有移动。

The number of pointers only ever changes by one as individual pointers go up and down, except when the gesture is canceled.

当pointer按下或者抬起的时候pointers的数量每次只会以1为单位进行变化

Each pointer has a unique id that is assigned when it first goes down (indicated by ACTION_DOWN or ACTION_POINTER_DOWN). A pointer id remains valid until the pointer eventually goes up (indicated by ACTION_UP or ACTION_POINTER_UP) or when the gesture is canceled (indicated by ACTION_CANCEL).

当按下时,每个pointer都会有一个独立的id。这个id会一直保持直到抬起或者姿势被取消。

The MotionEvent class provides many methods to query the position and other properties of pointers, such as getX(int)getY(int)getAxisValue(int),getPointerId(int)getToolType(int), and many others. Most of these methods accept the pointer index as a parameter rather than the pointer id. The pointer index of each pointer in the event ranges from 0 to one less than the value returned by getPointerCount().

这些方法中的大多数都以pointer的index作为参数而不是pointer的id。各个pointer的index在事件中从0变到getPointerCount()-1.

The order in which individual pointers appear within a motion event is undefined. Thus the pointer index of a pointer can change from one event to the next but the pointer id of a pointer is guaranteed to remain constant as long as the pointer remains active. Use the getPointerId(int) method to obtain the pointer id of a pointer to track it across all subsequent motion events in a gesture. Then for successive motion events, use the findPointerIndex(int) method to obtain the pointer index for a given pointer id in that motion event.

pointer的index从一个事件到另一个事件的时候会变化,但是其id会保持不变。调用getPointerId就能够获取到id了。有了这个ID之后我们就能够在整个运动的过程中一直监控这个pointer了,findPointerIndex就可以通过id去得到index了,也就是说index和id是有办法互换的。

Mouse and stylus buttons can be retrieved using getButtonState(). It is a good idea to check the button state while handling ACTION_DOWN as part of a touch event. The application may choose to perform some different action if the touch event starts due to a secondary button click, such as presenting a context menu.

这类触摸的功能都是以一种状态机设计存在,针对两根手指进行缩放操作

Event的getAction()的操作可以检测到pointer1、pointer2的按下和抬起动作事件。

那我们针对这个状态机需要设计的状态有:DONE(初始状态或者说是完成状态),POINTER_1_DOWN_2_UP(pointer1按下,pointer2抬起),POINTER_1_UP_2_DOWN(pointer1抬起,pointer2按下),ZOOMING(放大状态).一共四个状态

状态机就是要在每一种状态下,收到一个新的事件时状态如何变迁。但写程序的时候是反过来,收到某种事件,然后根据不同的状态做变迁

然后在状态变迁之后,根据当前的状态来做实际的图片的显示操作或者是一些变量状态的更新。

针对图像缩放的原理,最基本的就是以两根手指触点的中心作为缩放的原点;然后以两根手指的初始距离作为基准,在移动过程中实时计算两根手指之间的距离,用这个实时距离和基准的比例作为缩放的一个scale幅度

public class MyImageView extends ImageView implements View.OnTouchListener {

    private static final String TAG = "MyImageView";

    private static final int DONE = 0;
    private static final int POINTER_1_DOWN_2_UP = 1;
    private static final int POINTER_1_UP_2_DOWN = 2;
    private static final int ZOOMING = 3;

    private float pointer_1_x;
    private float pointer_1_y;

    private float pointer_2_x;
    private float pointer_2_y;

    private float pointer_center_x;
    private float pointer_center_y;

    private int focus_state = 0;

    private int current_state = DONE;
    private float init_distance = 0;
    private float current_distance = 0;

    private Matrix matrix = new Matrix();
    private Matrix currentMatrix = new Matrix();
    private Matrix originalMatrix = new Matrix();

    public MyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        this.setOnTouchListener(this);
    }

    public MyImageView(Context context) {
        super(context);
        this.setOnTouchListener(this);
    }

    private float distance(float x1, float y1, float x2, float y2) {
        return (float) Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        int cnt = event.getPointerCount();
        Log.d("MyImageView","pointer cnt is:"+cnt+" action is:"+event.getAction());

        //起来的时候切换缩放的原点;按下的时候,记录位置以及两个pointer之间的距离
        switch(event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //状态变迁过程done->pointer_1_down
            //这个action_down在整个过程中只会出现一次
            current_state = POINTER_1_DOWN_2_UP;
            focus_state = 1;
            //记录这个pointer的坐标位置
            pointer_1_x = event.getX(event.getPointerCount()-1);
            pointer_1_y = event.getY(event.getPointerCount()-1);

            currentMatrix.set(this.getImageMatrix());
            originalMatrix.set(this.getImageMatrix());
            Log.d(TAG,"current state is:"+current_state);
            Log.d(TAG,"x_1:"+pointer_1_x+";y_1:"+pointer_1_y);
            break;
        case MotionEvent.ACTION_POINTER_1_DOWN:
            if(current_state == POINTER_1_UP_2_DOWN ) {
                current_state = ZOOMING;
                pointer_1_x = event.getX(event.getPointerCount()-1);
                pointer_1_y = event.getY(event.getPointerCount()-1);
                pointer_center_x = (pointer_1_x + pointer_2_x)/2;
                pointer_center_y = (pointer_1_y + pointer_2_y)/2;
                init_distance = distance(pointer_1_x,pointer_1_y,pointer_2_x,pointer_2_y);
                Log.d(TAG,"current state is:"+current_state);
                Log.d(TAG,"init distance is:"+init_distance);
            }
            break;
        case MotionEvent.ACTION_POINTER_1_UP:
            if(current_state == ZOOMING) {
                focus_state = 2;
                current_state = POINTER_1_UP_2_DOWN;

            }
            else if(current_state == POINTER_1_DOWN_2_UP) {
                focus_state = 0;
                current_state = DONE;
                this.setImageMatrix(originalMatrix);
            }
            init_distance = 0;
            Log.d(TAG,"current state is:"+current_state);
            Log.d(TAG,"init distance is:"+init_distance);
            break;
        case MotionEvent.ACTION_POINTER_2_DOWN:
            if(current_state == POINTER_1_DOWN_2_UP) {
                current_state = ZOOMING;
                pointer_2_x = event.getX(event.getPointerCount()-1);
                pointer_2_y = event.getY(event.getPointerCount()-1);
                pointer_center_x = (pointer_1_x + pointer_2_x)/2;
                pointer_center_y = (pointer_1_y + pointer_2_y)/2;
                init_distance = distance(pointer_1_x,pointer_1_y,pointer_2_x,pointer_2_y);
                Log.d(TAG,"current state is:"+current_state);
                Log.d(TAG,"init distance is:"+init_distance);
            }
            break;
        case MotionEvent.ACTION_POINTER_2_UP:
            if(current_state == ZOOMING) {
                focus_state = 1;
                current_state = POINTER_1_DOWN_2_UP;

            }
            else if(current_state == POINTER_1_UP_2_DOWN) {
                focus_state = 0;
                current_state = DONE;
                this.setImageMatrix(originalMatrix);
            }
            init_distance = 0;
            Log.d(TAG,"current state is:"+current_state);
            Log.d(TAG,"init distance is:"+init_distance);
            break;
        case MotionEvent.ACTION_MOVE:
            if(current_state == ZOOMING) {
                //1st update the 2 pointer coordinate
                if(focus_state == 1) {
                    pointer_1_x = event.getX(0);
                    pointer_1_y = event.getY(0);;
                    pointer_2_x = event.getX(1);;
                    pointer_2_y = event.getY(1);;
                }
                else if(focus_state == 2) {
                    pointer_1_x = event.getX(1);
                    pointer_1_y = event.getY(1);
                    pointer_2_x = event.getX(0);
                    pointer_2_y = event.getY(0);
                }
                //2nd count the current distance
                current_distance = distance(pointer_1_x,pointer_1_y,pointer_2_x,pointer_2_y);

                float scale = current_distance/init_distance;
                Log.d(TAG,"current state is:"+current_state);
                Log.d(TAG,"current distance is:"+current_distance);
                Log.d("Scale","scale is :"+scale);
                matrix.set(currentMatrix);
                matrix.postScale(scale, scale,pointer_center_x,pointer_center_y);
                this.setImageMatrix(matrix);
            }
            break;
        }

        return true;
    }

}

总结一下这类view的扩展:

1.要增加触摸交互的方式,核心是要在原有的基础上新增view的OnTouchListener类,中间常用的就是状态机的设计,下拉刷新的实现也同理是实现了listview的onTouchListener与此同时设计了4个状态的状态机

2.针对这种后台数据并不复杂的view,就没有必要考虑那种adapter的操作,但是那种列表性质的view,在触摸操作的过程中还需要考虑对后台数据的更改,比如下拉刷新中就要在出于refreshing的时候增加adapter中的数据

时间: 2024-10-26 04:43:53

MotionEvent分析及ImageView缩放实现的相关文章

ImageView 缩放

<ImageView android:id="@+id/imageview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/zixun" android:scaleType="matrix" /> 关键点:缩放的内容不能是图片的背景,一定要是图片的内容

手势 触摸【缩放】GestureDetector MotionEvent 案例

GestureDetector和ScaleGestureDetector示例 /** * 演示[单点触摸手势识别器] * 演示[缩放手势识别器]最简单的使用 * @author 白乾涛 */ public class FirstActivity extends Activity implements OnTouchListener { private ImageView iv; private GestureDetector mGestureDetector;//单击和双击事件手势识别器 pri

[Android]可缩放性ImageView(可以放大缩小)

由于项目需求的原因,最近一直在研究可缩放性ImageView,用本文来记录一下最近所学: 该ImageView的实现功能有: 1)初步打开时,图片按比例满屏(填充ImageView)显示. 2)在放大缩小过程中,可以控制最大放大比例和最小缩小比例. 3)在缩放过程中,若图片的宽或高小于ImageView,则在图片在宽或高居中显示. 4)在放大后,可以移动图片,并且限制好移动的边界,不会超出图片. 5)实现双击放大或缩小的功能.(若当前图片显示为最大的比例则缩小为最小比例,若不是最小比例则放大了最

Android ImageView手势缩放完整的实现

已经有很多开源的缩放控件了,实际做项目没有必要重复造轮子,但对于学习来说自己亲自实现一个缩放的ImageView是大有益处的.所以这里分享一下自己学习的心得. 1.创建一个类继承ImageView. public class GestureImageView extends ImageView { public GestureImageView(Context context) { super(context); } public GestureImageView(Context context

(二)弥补图片自由缩放出现的间隙

在上一篇文章中,初步实现了自定义ImageView的多点触控.但是从最终效果来看,却发现自由缩放时,图片与屏幕出现了空白间隙,这当然是让人十分厌恶的.在这篇文章中,就来解决这个问题.如果你还没读过上一篇文章,可以点击下面的链接: http://www.cnblogs.com/fuly550871915/p/4939629.html 先贴出上一篇文章所实现的最终效果图吧,如下: 从上图中,可以看到,图片缩放后,与屏幕之间形成了很大的空白间隙.现在来解决这个问题,主要是两个方面: (1)图片自由缩放

ImageView android:scaleType=&quot;centerCrop&quot;

转载地址:http://www.cnblogs.com/yejiurui/archive/2013/02/25/2931767.html 在网上查了好多资料,大致都雷同,大家都是互相抄袭的,看着很费劲,不好理解,自己总结一下,留着需要看的话来查找. 代码中的例子如下: <ImageView android:id="@+id/iv_bit_1" android:layout_width="@dimen/passcode_width" android:layout

【Android开源项目分析】自定义圆形头像CircleImageView的使用和源码分析

本文分为三大部分: CircleImageView的使用 CircleImageView源码分析 Android自定义View总结 CircleImageView项目源码下载: https://github.com/hdodenhof/CircleImageView 打开源码会发现主要就是一个继承了ImageView 的类--CircleImageView .java,代码优雅精致,效果很nice.下面会进行源码分析,让我加深了不少Canvas.BitmapShader.Matrix相关知识.

Android ImageView 正确使用姿势

转载来自http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653578233&idx=1&sn=aea773c1e815fdef910fba28d765940b&chksm=84b3b1feb3c438e8372850a36bdcb87fdfb1ca793793a7c9598bcc792aabbb0f417b7a32c989&mpshare=1&scene=1&srcid=1117pJi4bq

IOS UIScrollView详解 & 图片缩放功能

一 UIScrollView 简介 UIScrollView是能滚动的视图控件,可以通过滚动的方式来展示类容. 二 UIScrollView常见属性 //设置UIScrollView滚动的位置 @property(nonatomic) CGPoint contentOffset;  //设置UIScrollView内容的尺寸,滚动范围 @property(nonatomic) CGSize contentSize;  //设置UIScrollView的4周增加额外的滚动区域 @property(