Android触摸事件的应用详解

前言

上一篇讲了Android触摸事件的传递机制,具体可以看这里初识Android触摸事件传递机制。既然知道Android中触摸事件的传递分发,那么它能解决什么样的问题,在我们实际开发中如何应用,这点很重要,知道原理是为了解决问题而准备的。这篇文章的核心讲的如何解决View的滑动冲突,这个问题在日常开发中很常见,比如内部嵌套Fragment视图是左右滑动,外部用一个ScrollView来包含,可以上下滑动,如果不进行滑动冲突处理的话,就会造成外部滑动方向和内部滑动方向不一致。

目录

常见的滑动冲突场景
滑动冲突的处理规则
外部拦截法
内部拦截法
小结

常见的滑动冲突场景

常见的滑动冲突场景可以简单分为以下三种:

场景1:外部滑动方向和内部滑动方向不一致
场景2:外部滑动方向和内部滑动方向一致
场景3:上面两种情况的嵌套

如图:

场景1,主要是将ViewPager和Fragment配合使用所组成的页面滑动效果,主流应用几乎都会使用这个效果。在这个效果中可以通过左右滑动来切换页面,而每个页面内部往往又是一个ListView,所以就造成了滑动冲突,但是在ViewPager内部处理了这种滑动冲突,因此在采用ViewPager时我们就无须关注这个问题,而如果把ViewPager换成ScrollView,那就必须自己手动处理,不然造成的结果就是内外两层只能一层能够滑动。

场景2,就复杂一点,当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题。因为当手指开始滑动的时候,系统无法知道用户到底是想让哪一层滑动,所以当手指滑动的时候就会出现问题,要么只有一层滑动,要么就是内外两层都滑动但很卡顿。

场景3,是场景1和场景2两种情况的嵌套,显得更复杂了。比如外部有一个SlideMenu效果,内部有一个ViewPager,ViewPager的每一个页面中又是一个ListView。虽然场景3滑动冲突看起来很复杂,但都是几个单一的滑动冲突的叠加,因此需要一一拆解开来即可。

滑动冲突的处理规则

一般来说,不管滑动冲突有多么复杂,它都有既定的规则,根据这些规则我们就可以选择合适的方法去处理。

对于场景1,它的处理规则就是:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动,需要让内部View拦截点击事件。具体来说就是根据滑动是水平滑动还是竖直滑动来判断到底是由谁来拦截事件。

如图:

简单来说,就是根据水平方向和竖直方向的距离差来判断,如果是Dx>Dy,那么则是水平滑动,如果是Dy>Dx,那么则是竖直滑动。

场景2,则是比较特殊,它无法根据滑动的角度,距离差以及速度差来做判断。这个时候就需要从业务上找到突破点,比如,当处于某种状态时需要外部View响应用户的滑动,而处于另外一种状态时需要内部View来响应View的滑动

对于场景3的话,它的滑动规则也更复杂,和场景2一样,同样是从业务上找到突破点。

外部拦截法

外部拦截法是指点击事件都是先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件,就不拦截了,这样就可以解决滑动冲突的问题,外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,伪代码如下:

 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
 boolean intercepted = false;
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  intercepted = false;
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  if (父容器需要点击当前事件) {
  intercepted = true;
  } else {
  intercepted = false;
  }
  break;
 }
 case MotionEvent.ACTION_UP: {
  intercepted = false;
  break;
 }
 default:
  break;
 }
 mLastXIntercept = x;
 mLastYIntercept = y;

 return intercepted;
 }

首先ACTION_DOWN这个事件,父容器必须返回false,这样保证后续move和up的事件可以传递给子View,根据move事件来决定是否拦截,如果父容器拦截就返回true,否则返回false。

实现一个自定义类似ViewPager的控件,嵌套ListView的效果,源代码如下:

public class HorizontalScrollViewEx extends ViewGroup {
 private static final String TAG = "HorizontalScrollViewEx";

 private int mChildrenSize;
 private int mChildWidth;
 private int mChildIndex;

 // 分别记录上次滑动的坐标
 private int mLastX = 0;
 private int mLastY = 0;
 // 分别记录上次滑动的坐标(onInterceptTouchEvent)
 private int mLastXIntercept = 0;
 private int mLastYIntercept = 0;

 private Scroller mScroller;  //弹性滑动对象
 private VelocityTracker mVelocityTracker; //追踪滑动速度

 public HorizontalScrollViewEx(Context context) {
 super(context);
 init();
 }

 public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
 super(context, attrs);
 init();
 }

 public HorizontalScrollViewEx(Context context, AttributeSet attrs,
  int defStyle) {
 super(context, attrs, defStyle);
 init();
 }

 private void init() {
 mScroller = new Scroller(getContext());
 mVelocityTracker = VelocityTracker.obtain();
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
 boolean intercepted = false;
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  intercepted = false;
  if (!mScroller.isFinished()) {
  mScroller.abortAnimation();
  intercepted = true;
  }
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastXIntercept;
  int deltaY = y - mLastYIntercept;
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
  intercepted = true;
  } else {
  intercepted = false;
  }
  break;
 }
 case MotionEvent.ACTION_UP: {
  intercepted = false;
  break;
 }
 default:
  break;
 }

 Log.d(TAG, "intercepted=" + intercepted);
 mLastX = x;
 mLastY = y;
 mLastXIntercept = x;
 mLastYIntercept = y;

 return intercepted;
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
 mVelocityTracker.addMovement(event);
 int x = (int) event.getX();
 int y = (int) event.getY();
 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  if (!mScroller.isFinished()) {
  mScroller.abortAnimation();
  }
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastX;
  scrollBy(-deltaX, 0);
  break;
 }
 case MotionEvent.ACTION_UP: {
  int scrollX = getScrollX();
  mVelocityTracker.computeCurrentVelocity(1000);
  float xVelocity = mVelocityTracker.getXVelocity();
  if (Math.abs(xVelocity) >= 50) {
  mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
  } else {
  mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
  }
  mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
  int dx = mChildIndex * mChildWidth - scrollX;
  smoothScrollBy(dx, 0);
  mVelocityTracker.clear();
  break;
 }
 default:
  break;
 }

 mLastX = x;
 mLastY = y;
 return true;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 int measuredWidth = 0;
 int measuredHeight = 0;
 final int childCount = getChildCount();
 measureChildren(widthMeasureSpec, heightMeasureSpec);

 int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 if (childCount == 0) {
  setMeasuredDimension(0, 0);
 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
  final View childView = getChildAt(0);
  measuredHeight = childView.getMeasuredHeight();
  setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
 } else if (widthSpecMode == MeasureSpec.AT_MOST) {
  final View childView = getChildAt(0);
  measuredWidth = childView.getMeasuredWidth() * childCount;
  setMeasuredDimension(measuredWidth, heightSpaceSize);
 } else {
  final View childView = getChildAt(0);
  measuredWidth = childView.getMeasuredWidth() * childCount;
  measuredHeight = childView.getMeasuredHeight();
  setMeasuredDimension(measuredWidth, measuredHeight);
 }
 }

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 int childLeft = 0;
 final int childCount = getChildCount();
 mChildrenSize = childCount;

 for (int i = 0; i < childCount; i++) {
  final View childView = getChildAt(i);
  if (childView.getVisibility() != View.GONE) {
  final int childWidth = childView.getMeasuredWidth();
  mChildWidth = childWidth;
  childView.layout(childLeft, 0, childLeft + childWidth,
   childView.getMeasuredHeight());
  childLeft += childWidth;
  }
 }
 }

 private void smoothScrollBy(int dx, int dy) {
 mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
 invalidate();
 }

 @Override
 public void computeScroll() {
 if (mScroller.computeScrollOffset()) {
  scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  postInvalidate();
 }
 }

 @Override
 protected void onDetachedFromWindow() {
 mVelocityTracker.recycle();
 super.onDetachedFromWindow();
 }
}

这个情况的拦截条件就是父容器在滑动过程中水平距离差比垂直距离差大,那么就进行拦截,否则就不拦截,继续传递事件。

内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法复杂。伪代码如下:

 @Override
 public boolean dispatchTouchEvent(MotionEvent event) {
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastX;
  int deltaY = y - mLastY;
  if (父容器需要此类点击事件) {
  mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
  }
  break;
 }
 case MotionEvent.ACTION_UP: {
  break;
 }
 default:
  break;
 }

 mLastX = x;
 mLastY = y;
 return super.dispatchTouchEvent(event);
 }

当子元素调用requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。

前面是用自定义类似的ViewPager,现在重写一个ListView,我们可以自定义一个ListView,叫做ListViewEx,然后对内部拦截法的模板代码进行修改即可。

public class ListViewEx extends ListView {
 private static final String TAG = "ListViewEx";

 private HorizontalScrollViewEx2 mHorizontalScrollViewEx2;

 // 分别记录上次滑动的坐标
 private int mLastX = 0;
 private int mLastY = 0;

 public ListViewEx(Context context) {
 super(context);
 }

 public ListViewEx(Context context, AttributeSet attrs) {
 super(context, attrs);
 }

 public ListViewEx(Context context, AttributeSet attrs, int defStyle) {
 super(context, attrs, defStyle);
 }

 public void setHorizontalScrollViewEx2(
  HorizontalScrollViewEx2 horizontalScrollViewEx2) {
 mHorizontalScrollViewEx2 = horizontalScrollViewEx2;
 }

 @Override
 public boolean dispatchTouchEvent(MotionEvent event) {
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastX;
  int deltaY = y - mLastY;
  Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
  mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
  }
  break;
 }
 case MotionEvent.ACTION_UP: {
  break;
 }
 default:
  break;
 }

 mLastX = x;
 mLastY = y;
 return super.dispatchTouchEvent(event);
 }
}

同时对于包含ListViewEx外部布局进行修改,在onInterceptTouchEvent事件上不进行拦截

public class HorizontalScrollViewEx2 extends ViewGroup {
 private static final String TAG = "HorizontalScrollViewEx2";

 private int mChildrenSize;
 private int mChildWidth;
 private int mChildIndex;
 // 分别记录上次滑动的坐标
 private int mLastX = 0;
 private int mLastY = 0;

 // 分别记录上次滑动的坐标(onInterceptTouchEvent)
 private int mLastXIntercept = 0;
 private int mLastYIntercept = 0;

 private Scroller mScroller;
 private VelocityTracker mVelocityTracker;

 public HorizontalScrollViewEx2(Context context) {
 super(context);
 init();
 }

 public HorizontalScrollViewEx2(Context context, AttributeSet attrs) {
 super(context, attrs);
 init();
 }

 public HorizontalScrollViewEx2(Context context, AttributeSet attrs,
  int defStyle) {
 super(context, attrs, defStyle);
 init();
 }

 private void init() {
 mScroller = new Scroller(getContext());
 mVelocityTracker = VelocityTracker.obtain();
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
 int x = (int) event.getX();
 int y = (int) event.getY();
 int action = event.getAction();
 if (action == MotionEvent.ACTION_DOWN) {
  mLastX = x;
  mLastY = y;
  if (!mScroller.isFinished()) {
  mScroller.abortAnimation();
  return true;
  }
  return false;
 } else {
  return true;
 }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
 Log.d(TAG, "onTouchEvent action:" + event.getAction());
 mVelocityTracker.addMovement(event);
 int x = (int) event.getX();
 int y = (int) event.getY();
 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  if (!mScroller.isFinished()) {
  mScroller.abortAnimation();
  }
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastX;
  int deltaY = y - mLastY;
  Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
  scrollBy(-deltaX, 0);
  break;
 }
 case MotionEvent.ACTION_UP: {
  int scrollX = getScrollX();
  int scrollToChildIndex = scrollX / mChildWidth;
  Log.d(TAG, "current index:" + scrollToChildIndex);
  mVelocityTracker.computeCurrentVelocity(1000);
  float xVelocity = mVelocityTracker.getXVelocity();
  if (Math.abs(xVelocity) >= 50) {
  mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
  } else {
  mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
  }
  mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
  int dx = mChildIndex * mChildWidth - scrollX;
  smoothScrollBy(dx, 0);
  mVelocityTracker.clear();
  Log.d(TAG, "index:" + scrollToChildIndex + " dx:" + dx);
  break;
 }
 default:
  break;
 }

 mLastX = x;
 mLastY = y;
 return true;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 int measuredWidth = 0;
 int measuredHeight = 0;
 final int childCount = getChildCount();
 measureChildren(widthMeasureSpec, heightMeasureSpec);

 int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 if (childCount == 0) {
  setMeasuredDimension(0, 0);
 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
  final View childView = getChildAt(0);
  measuredHeight = childView.getMeasuredHeight();
  setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
 } else if (widthSpecMode == MeasureSpec.AT_MOST) {
  final View childView = getChildAt(0);
  measuredWidth = childView.getMeasuredWidth() * childCount;
  setMeasuredDimension(measuredWidth, heightSpaceSize);
 } else {
  final View childView = getChildAt(0);
  measuredWidth = childView.getMeasuredWidth() * childCount;
  measuredHeight = childView.getMeasuredHeight();
  setMeasuredDimension(measuredWidth, measuredHeight);
 }
 }

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 Log.d(TAG, "width:" + getWidth());
 int childLeft = 0;
 final int childCount = getChildCount();
 mChildrenSize = childCount;

 for (int i = 0; i < childCount; i++) {
  final View childView = getChildAt(i);
  if (childView.getVisibility() != View.GONE) {
  final int childWidth = childView.getMeasuredWidth();
  mChildWidth = childWidth;
  childView.layout(childLeft, 0, childLeft + childWidth,
   childView.getMeasuredHeight());
  childLeft += childWidth;
  }
 }
 }

 private void smoothScrollBy(int dx, int dy) {
 mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
 invalidate();
 }

 @Override
 public void computeScroll() {
 if (mScroller.computeScrollOffset()) {
  scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  postInvalidate();
 }
 }

 @Override
 protected void onDetachedFromWindow() {
 mVelocityTracker.recycle();
 super.onDetachedFromWindow();
 }
}

这个拦截规则也是父容器在滑动过程中水平距离差与垂直距离差相比。

小结

总的来说,滑动冲突的场景可以分为三种,内外部方向不一致、内外部方向一致、嵌套前面两种情况。如何解决,不管多么复杂的滑动冲突,可以进行拆分,根据的一定的规则,第一种情况可根据滑动距离差、速度差和角度差来解决,第二种和第三种情况,可根据业务上找到突破点,业务上一种状态需要响应,切换到另外一种状态时则不响应,根据业务需求得出相应的处理规则,有了处理规则可以进行下一步处理。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • android 多点触摸图片缩放的具体实现方法

    布局: 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/relativeLayout1"    android:layout_width="fill_parent

  • Android修改源码解决Alertdialog触摸对话框边缘消失的问题

    研究其父类时候发现,可以设置这么一条属性,在AlertDialog.Builder.create()之后才能调用这两个方法 方法一: setCanceledOnTouchOutside(false);调用这个方法时,按对话框以外的地方不起作用.按返回键还起作用 方法二: setCanceleable(false);调用这个方法时,按对话框以外的地方不起作用.按返回键也不起作用 这两个方法都属于Dialog方法,可查阅源码 修改后的源码如下: 复制代码 代码如下: case 1:         

  • 简单讲解Android开发中触摸和点击事件的相关编程方法

    在Android上,不止一个途径来侦听用户和应用程序之间交互的事件.对于用户界面里的事件,侦听方法就是从与用户交互的特定视图对象截获这些事件.视图类提供了相应的手段. 在各种用来组建布局的视图类里面,你可能会注意到一些公共的回调方法看起来对用户界面事件有用.这些方法在该对象的相关动作发生时被Android框架调用.比如,当一个视图(如一个按钮)被触摸时,该对象上的onTouchEvent()方法会被调用.不过,为了侦听这个事件,你必须扩展这个类并重写该方法.很明显,扩展每个你想使用的视图对象(只

  • Android中SurfaceView和view画出触摸轨迹

    一.引言          想实现一个空白的画板,上面可以画出手滑动的轨迹,就这么一个小需求.一般就来讲就两种实现方式,view或者surfaceview.下面看看两种是如何实现的. 二.实现原理          先简单说一下实现原理:        (1)用一张白色的Bitmap作为画板        (2)用canvas在bitmap上画线        (3)为了画出平滑的曲线,要用canvas的drawPath(Path,Paint)方法.        (4)同时使用贝塞尔曲线来使曲

  • Android 触摸事件监听(Activity层,ViewGroup层,View层)详细介绍

    Android不同层次的触摸事件监听 APP开发中,经常会遇到有关手势处理的操作,比如向右滑动返回上一个页面.关于触摸事件的处理,我们可以大概处理在不同的层次上. Activity层:可以看做触摸事件获取的最顶层 ViewGroup层:ViewGroup层可以自主控制是否让子View获取触摸事件 View层:可以决定自己是否真正的消费触摸事件,如果不消费抛给上层ViewGroup Activity级别的手势监听:(右滑动返回上层界面) Activity层手势监听的使用场景:一般用于当前页面中没有

  • android中处理各种触摸事件的方法浅谈

    Android里有两个类android.view.GestureDetectorandroid.view.GestureDetector.SimpleOnGestureListener(另外android.widget.Gallery好像是更牛x的OnGestureListener )1)新建一个类继承SimpleOnGestureListener,HahaGestureDetectorListener可以实现以下event事件.boolean onDoubleTap(MotionEvent e

  • Android 的触摸事件详解及示例代码

    由于触摸(Touch)而触发的事件 Android的事件:onClick, onScroll,onFling等等,都是由许多个Touch组成的.其中Touch的第一个状态肯定是ACTION_DOWN,表示按下了屏幕.之后,touch将会有后续事件,可能是: ACTION_MOVE //表示为移动手势 ACTION_UP //表示为离开屏幕 ACTION_CANCEL //表示取消手势,不会由用户产生,而是由程序产生的 一个Action_DOWN, n个ACTION_MOVE, 1个ACTION_

  • Android触摸事件传递图解

    本博文讲解流程 TouchEvent相关事件简介 流程图分解讲解 总结与归纳 一.TouchEvent相关事件简介 android TouchEvent相关事件有  1 dispatchTouchEvent 这个方法用来分发TouchEvent  2 onInterceptTouchEvent 这个方法用来拦截TouchEvent  3 onTouchEvent 方法用来处理TouchEvent 比较特殊一点的是onInterceptTouchEvent 事件,在activity中和view中是

  • 解析Android开发中多点触摸的实现方法

    多点触摸技术在实际开发过程中,用的最多的就是放大缩小功能.比如有一些图片浏览器,就可以用多个手指在屏幕上操作,对图片进行放大或者缩小.再比如一些浏览器,也可以通过多点触摸放大或者缩小字体.其实放大缩小也只是多点触摸的实际应用样例之一,有了多点触摸技术,在一定程度上就可以创新出更多的操作方式来,实现更酷的人机交互. 理论上,Android系统本身可以处理多达256个手指的触摸,这主要取决于手机硬件的支持.当然,支持多点触摸的手机,也不会支持这么多点,一般是支持2个点或者4个点.对于开发者来说,编写

  • 解决Android SurfaceView绘制触摸轨迹闪烁问题的方法

    本文分享了解决SurfaceView触摸轨迹闪烁问题的方法,供大家参考,具体内容如下 第一种解决SurfaceView触摸轨迹闪烁问题的方法: 由于SurfaceView使用双缓存机制,两张画布轮流显示到屏幕上.那么,要存储触摸轨迹并避免两张画布内容不一致造成的闪烁问题,完全可以利用保存绘制过程并不断重新绘制的方法解决闪烁,而且这样还顺带解决了多次试验中偶尔出现的因为moveTo()函数不能读取到参数执行默认设置(参数设为上次的触摸点)而出现的断线连接闪烁问题,详细代码如下: package c

随机推荐