android MotionEvent的相关的类的介绍
16lz
2021-01-24
常见的类的介绍
1 MotionEvent 触摸事件 2 View 视图的位置信息 3 ViewConfiguration View相关设置 4 VelocityTracker 速度追踪器 5 Scroller 用来滑动的类 MotionEvent
Android API http://developer.android.com/reference/android/view/MotionEvent.html 官方的一些介绍 MotionEvent 形容触摸屏幕产生的事件( 这里用点击事件来代替),这个点击事件的对象包含一些Action 的信息和一系列的坐标值的信息,其中action的信息包含了state改变的信息,比如从move -> up,坐标的信息则包括了一些的位置和其他的属性的信息 比如说,当用户第一次触摸屏幕的时候,系统就会传递一个motionevent的信息给适当的一个view,这个信息包括一些actioncode为Action_Down, 和一系列的值其中包括可能为:x,y,压力,触摸区域的带下,时间等一些列信息 一些设备可以报告的多个多个运动轨迹,多触摸屏幕中一个运动轨迹对应一个手指,这时的手指对应的运动轨迹设为pointer,而motionevent包含所有的pointer的信息,即使的这个pointer不再移动从上次传达的信息 pointer 的数量改变只有当手指down或者up的时候改变,除非这个手势被取消cancle() 每一个pointer的都有一个ID来标示当down的事件发生后,ACTION_DOWNorACTION_POINTER_DOWN,这个ID值一直存在的,直到ACTION_UPorACTION_POINTER_UP)或者这个手势被取消 (indicatedbyACTION_CANCEL). motionevent提供的一系列的方法用来查询属性的信息,比如getX(int index),getY(int index),getAxisValue(int axis),getPointId(int indx),getToolType(int index),大多数情况下,通过接受index来得到信息,而不是pointer id,这个index 的值介于0到getpointercount(). Index 和pointer id的区别多个事件的时候,motionevent的顺序是不确定的(index),但是pointer 的id自始至终都是一样的,只要它还存在,我们可以通过getPointId(int index),获得pointer 的id值,也可以通过findPointIndex(int pointId),来获取对应的index的值。 为了提高效率,action_move的时候,提供一个批机制,就是处理单个对象包含多个运动的样本,当前的pointer可以使用getX(int index),getY(int index)获取批最前的事件 ,而使用getHistoricalX(int index)和getHistoricalY(int index)来获取批里面最早的其他的事件,举个例子public boolean onTouchEvent(MotionEvent event) { printMotinEvent(event); return true;}void printMotinEvent(MotionEvent ev) { final int historySize = ev.getHistorySize(); final int pointerCount = ev.getPointerCount(); Log.e("history", "history的数量"+historySize); for (int h = 0; h < historySize; h++) { for (int p = 0; p < pointerCount; p++) { Log.e("history", "id:"+ev.getPointerId(p)+ ",2 x:"+ev.getHistoricalX(p, h)+",3 y:"+ev.getHistoricalY(p, h)); } } for (int p = 0; p < pointerCount; p++) { Log.e("now", ev.getEventTime()+"time"); Log.e("now", "1 id:"+ev.getPointerId(p)+",2 x"+ ev.getX(p)+",3 y"+ ev.getY(p)); }}
打印的log如下:
motionevent 即传递,我们触摸屏幕所有的信息!并且一个触摸事件时由多个movetionevent的组成的, 常见的api如下 event.getAction() or event.getAction() & MotionEvent.ACTION_MASK //获取触控动作比如 ACTION_DOWN, event.getPointerCount() //获取触控点的数量,比如2则可能是两个手指同时按压屏幕 event.getPoninerId(int index)//对于每个触控的点的细节,我们可以通过一个循环执行getPointerId方法获取索引 event.getX(int index)//获取第index个触控点的x位置 event.getY(int index)//获取第index个触控点的y位置 event.getPressure(int index)//LCD可以感应出用户的手指压力,当然具体的级别由驱动和物理硬件决定的 event.getDownTime()//按下开始时间 event.getEventTime()//事件结束时间 event.getEventTime()-event.getDownTime()//总共按下时花费时间
view视图的位置信息
position的信息:以下四个信息代表相对父容器的位置 left=getLeft(); right = getRight(); up = getUp(); bottom = getBottom(); android 3.0 之后的添加的一些新的位置信息 translationX = getTranslatinX(); translationY = getTranslationY(); x = getX() y = getY() 注意前后的关系,left,right,up,bottom四个点的信息在平移的过程是不会发生改变的,translationx和translationy 默任初始为0,三者之前的关系时 getX() = getTranslationX() + getLeft() getY() = getTranslationY()+getTop(); 代码如下tv = (TextView) findViewById(R.id.tv);btn = (Button) findViewById(R.id.btn);final ObjectAnimator translationX = ObjectAnimator.ofFloat(tv, "translationX", 0,50);btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { translationX.setDuration(300); translationX.start(); }});translationX.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { Log.e("TAG","start x = "+tv.getX()+",translationX = " + tv.getTranslationX() +",left = "+tv.getLeft()); } @Override public void onAnimationEnd(Animator animation) { Log.e("TAG","end x = "+tv.getX()+",translationX = " + tv.getTranslationX() +",left = "+tv.getLeft()); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { }});
打印的log如下
ViewConfiguration
包含一些方法去获取标准的常量在ui 上,比如一些时间,大小,或者距离,这是android 帮我们设定好的。 android api http://developer.android.com/reference/android/view/ViewConfiguration.html ViewConfiguration.get(Context context); 举个常用的例子,自定义一个长按的手势识别器代码如下(需要默认认为最小的滑动的距离和默认最小的进入长按状态的时间)LongPressGestureDetector
public class LongPressGestureDetector { private ViewConfiguration _configuration; private int _minDistance ; private int _delay; private Context _context; private OnlongPressListener _listener; private Handler _handler ; private Runnable _longPressRunnable; private float _downX; private float _downY; private long _downTime; public LongPressGestureDetector(Context context, OnlongPressListener listener){ _listener = listener; _context = context; _handler = new Handler(Looper.getMainLooper()); _configuration = ViewConfiguration.get(context); _minDistance = _configuration.getScaledTouchSlop(); _delay = _configuration.getLongPressTimeout(); _longPressRunnable = new LongPressRunnale(); } //这里以单指操作 public boolean onTouch(MotionEvent event){ int actionCode = event.getAction() & MotionEvent.ACTION_MASK; switch (actionCode){ case MotionEvent.ACTION_DOWN: _handler.removeCallbacks(_longPressRunnable); _downX = event.getX(); _downY = event.getY(); _downTime = event.getDownTime(); Log.e("TAG",event.getEventTime()+"downtime"+event.getDownTime()); _handler.postDelayed(_longPressRunnable,_delay); case MotionEvent.ACTION_MOVE: int distanceX = (int) Math.abs(event.getX() - _downX); int distanceY = (int) Math.abs(event.getY() - _downY); if(distanceY > _minDistance || distanceX > _minDistance ){ _handler.removeCallbacks(_longPressRunnable); } break; case MotionEvent.ACTION_UP: if(event.getEventTime() - _downTime < _delay) { _handler.removeCallbacks(_longPressRunnable); } Log.e("TAG",event.getEventTime()+"downtime"+event.getDownTime()); break; } return true; } interface OnlongPressListener{ void onLongPress(); } class LongPressRunnale implements Runnable{ @Override public void run() { Log.e("TAG", "进入到了长按的状态了"); if(_listener != null){ _listener.onLongPress(); } } }}这里是以单指作为操作的,如果选择用多指,我们根据每一个pointer的id来判断时否发生改变了, activity的代码
public class MainActivity extends AppCompatActivity { private LongPressGestureDetector _longPressGestureDetector; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); _longPressGestureDetector = new LongPressGestureDetector(this,new LongPressGestureDetector.OnlongPressListener(){ @Override public void onLongPress() { Log.e("TAG","开始在主线程更新UI了"); } }); } @Override public boolean onTouchEvent(MotionEvent event) { return _longPressGestureDetector.onTouch(event); }}注意到了这里面在onTouchEvent()全部交给LongPressGestureDetector的去处理,并且在LongPressGestureDetector的ontouch()的方法里面返回了true,这样做其实不好,但是这里只是为了介绍下了ViewConfiguration的这个类,接着长按的进入长按的回调方法中,Log如下:
VelocityTracker
android api http://developer.android.com/reference/android/view/VelocityTracker.html 用来追踪触摸事件的速度的,主要是为了在filing的等手势的后面的平滑滑动,通过obtain()获取实例,在你想追踪的event的之前使用addmovement(MotionEvent event)去追踪这个event 的,紧接着调用computeCurrentVelocity(int units ),最后就可以使用getXVelocity(int pointerId ),getYVelocity(int pointId)来获取了当前的pointer的在x,y的速度Scroller
andorid API http://developer.android.com/reference/android/widget/Scroller.html 这个用于封装了滚动的类,你可以用这个scroller收集你需要制作的动画的一个数据,其实里面只是存储着一些的关于滑动的信息,真正的滑动的实现是通 view中重写的computeScroll()的方法来实现的, 滑动的实现的原理 我们知道自定义viewgroup的dispatchdraw的方法中的调用drawChild(),drawChild()的源码如下。 protectedbooleandrawChild(Canvascanvas,Viewchild,longdrawingTime){returnchild.draw(canvas,this,drawingTime); } 也就以为着去调用响应的child的Draw方法。从而完成整体的draw的效果,在view的中的draw的代码中可以找到computeScroll()的代码。 我们通过在computeScroll()的中使用postInvalidate()从而使整个布局在重绘,从而完成滑动的实现 结论 从滑动的原理可以看出来,滑动其实我门的draw的部分,这也就意味着的实现滑动的是内容而不是view的视图的位置,也就是意味着view 的layout的布局信息自始至终都未变过,在滑动的过程中。 常见用法 publicbooleancomputeScrollOffset() 当想要知道新的位置时,调用此函数。如果返回true,表示动画还没有结束。位置改变以提供一个新的位置。 publicvoidextendDuration(intextend) 延长滚动动画时间。 publicvoidfling(intstartX,intstartY,intvelocityX,intvelocityY,intminX,intmaxX,intminY,intmaxY) 在fling手势开始滚动。滚动的距离取决于fling的初速度。 publicvoidstartScroll(intstartX,intstartY,intdx,intdy)以提供的起始点和将要滑动的距离开始滚动。 publicinttimePassed()返回自滑动开始经过的时间 利用Scroller和VelocityTracker,我们实现一个可以在fling的时候能够具有滑动惯性。并且在滑动的时候一起跟随滑动,但是没有加外层的限制 代码如下 首先上了布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.rrtoyewx.person.FlingViewGroup android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image1" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image2" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image3" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image4" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image5" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image6" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image7" /> </com.example.rrtoyewx.person.FlingViewGroup></RelativeLayout>
布局很简单就是自定义了viewgroup了,然后里面放了7张图片。
下面的放上了这个自定义viewgroup的实现
package com.example.rrtoyewx.person;import android.content.Context;import android.graphics.Point;import android.graphics.PointF;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.widget.Scroller;/** * Created by Rrtoyewx on 15/11/4. */public class FlingViewGroup extends ViewGroup { private VelocityTracker _velocityTracker; private Scroller _scroller; private ViewConfiguration _viewConfiguration; private PointF _prePoint; private PointF _tempPoint; private PointF _curPoint; private Point _distancePoint; private int _touchMinDistance; private int _maxFlingVelocity; private int _minFlingVelocity; public FlingViewGroup(Context context) { super(context); } public FlingViewGroup(Context context, AttributeSet attrs) { super(context, attrs); _scroller = new Scroller(context); _viewConfiguration = ViewConfiguration.get(context); _touchMinDistance = _viewConfiguration.getScaledTouchSlop(); _maxFlingVelocity = _viewConfiguration.getScaledMaximumFlingVelocity(); _minFlingVelocity = _viewConfiguration.getScaledMinimumFlingVelocity(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); child.layout(l + i * (r - l), t, r + i * (r - l), b); } } //这里以单指来分析即pointerIndex = 0; @Override public boolean onTouchEvent(MotionEvent event) { int actionCode = event.getAction() & MotionEvent.ACTION_MASK; if(_velocityTracker == null){ _velocityTracker = VelocityTracker.obtain(); } _velocityTracker.addMovement(event); switch (actionCode) { case MotionEvent.ACTION_DOWN: if (_scroller.isFinished()) { _scroller.abortAnimation(); } _prePoint = new PointF(); _prePoint.x = event.getX(0); _prePoint.y = event.getY(0); break; case MotionEvent.ACTION_MOVE: calculateMovePoint(event); _curPoint = _tempPoint; if (Math.abs(_prePoint.x - _curPoint.x) > _touchMinDistance || (Math.abs(_prePoint.y - _curPoint.y) > _touchMinDistance)) { if (_distancePoint != null) { _distancePoint = null; } _distancePoint = new Point(); _distancePoint.x = (int) (_curPoint.x - _prePoint.x); _distancePoint.y = (int) (_curPoint.y - _prePoint.y); scrollTo(-_distancePoint.x + getScrollX(), -_distancePoint.y + getScrollY()); _prePoint = _curPoint; } break; case MotionEvent.ACTION_UP: int pointId = event.getPointerId(0); final VelocityTracker velocityTracker = _velocityTracker; velocityTracker.computeCurrentVelocity(1000, _maxFlingVelocity); int xVelocity = (int) velocityTracker.getXVelocity(pointId); int yVelocity = (int) velocityTracker.getYVelocity(pointId); Log.e("TAG", "xVelocity : " + xVelocity + ",xVelocitY: " + yVelocity); if(Math.abs(xVelocity)>_minFlingVelocity && Math.abs(yVelocity ) > _minFlingVelocity ){ _scroller.fling(getScrollX(),getScrollY(), -xVelocity, -yVelocity,Integer.MIN_VALUE,Integer.MAX_VALUE,Integer.MIN_VALUE,Integer.MAX_VALUE); awakenScrollBars(_scroller.getDuration()); invalidate(); } case MotionEvent.ACTION_CANCEL: resetState(); break; } return true; } /** * 计算event的具体信息 * @param event */ private void calculateMovePoint(MotionEvent event) { _tempPoint = new PointF(); int historySize = event.getHistorySize(); for (int h = 0; h < historySize; h++) { float historicalX = event.getHistoricalX(0, h); _tempPoint.x += historicalX; float historicalY = event.getHistoricalY(0, h); _tempPoint.y += historicalY; } _tempPoint.x += event.getX(0); _tempPoint.y += event.getY(0); _tempPoint.x /= (historySize + 1); _tempPoint.y /= (historySize + 1); } private void resetState() { if (_velocityTracker != null) { _velocityTracker.clear(); _velocityTracker.recycle(); _velocityTracker = null; } } @Override public void computeScroll() { super.computeScroll(); if (_scroller.computeScrollOffset()) { scrollTo(_scroller.getCurrX(), _scroller.getCurrY()); postInvalidate(); } } }
简单的说下的代码的 1 初始化的时候将ViewConfiguration和Scroller,并初始化一些常量, 2 onLayout()一次将孩子放在整个手机屏幕,依次往右放 3 onTouchEvent()中,处理滑动的逻辑,并过滤一些我们不要的motionevent 4 重写computeScroll(),让其能够平滑的滑动 最后附上效果图(模拟器太难滑的了,效果不佳,还请见谅),
更多相关文章
- Android(安卓)签名信息读取
- Android查看包名和获取包名
- React Navigation Android(安卓)返回键事件处理
- Android收集崩溃信息的原理
- Android中的popWindow
- Android之防快速重复点击的全局设置
- android点击文本框之外的地方隐藏键盘
- Android获取所有安装APP信息
- Android拖动改变小球位置