中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

Android觸摸事件的應用詳解

前言

成都創(chuàng)新互聯公司自2013年起,是專業(yè)互聯網技術服務公司,擁有項目成都做網站、網站制作網站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元武山做網站,已為上家服務,為武山各地企業(yè)和個人服務,聯系電話:18982081108

上一篇講了Android觸摸事件的傳遞機制,具體可以看這里初識Android觸摸事件傳遞機制。既然知道Android中觸摸事件的傳遞分發(fā),那么它能解決什么樣的問題,在我們實際開發(fā)中如何應用,這點很重要,知道原理是為了解決問題而準備的。這篇文章的核心講的如何解決View的滑動沖突,這個問題在日常開發(fā)中很常見,比如內部嵌套Fragment視圖是左右滑動,外部用一個ScrollView來包含,可以上下滑動,如果不進行滑動沖突處理的話,就會造成外部滑動方向和內部滑動方向不一致。

目錄

常見的滑動沖突場景
滑動沖突的處理規(guī)則
外部攔截法
內部攔截法
小結

常見的滑動沖突場景

常見的滑動沖突場景可以簡單分為以下三種:

場景1:外部滑動方向和內部滑動方向不一致
場景2:外部滑動方向和內部滑動方向一致
場景3:上面兩種情況的嵌套

如圖:

Android觸摸事件的應用詳解

場景1,主要是將ViewPager和Fragment配合使用所組成的頁面滑動效果,主流應用幾乎都會使用這個效果。在這個效果中可以通過左右滑動來切換頁面,而每個頁面內部往往又是一個ListView,所以就造成了滑動沖突,但是在ViewPager內部處理了這種滑動沖突,因此在采用ViewPager時我們就無須關注這個問題,而如果把ViewPager換成ScrollView,那就必須自己手動處理,不然造成的結果就是內外兩層只能一層能夠滑動。

場景2,就復雜一點,當內外兩層都在同一個方向可以滑動的時候,顯然存在邏輯問題。因為當手指開始滑動的時候,系統無法知道用戶到底是想讓哪一層滑動,所以當手指滑動的時候就會出現問題,要么只有一層滑動,要么就是內外兩層都滑動但很卡頓。

場景3,是場景1和場景2兩種情況的嵌套,顯得更復雜了。比如外部有一個SlideMenu效果,內部有一個ViewPager,ViewPager的每一個頁面中又是一個ListView。雖然場景3滑動沖突看起來很復雜,但都是幾個單一的滑動沖突的疊加,因此需要一一拆解開來即可。

滑動沖突的處理規(guī)則

一般來說,不管滑動沖突有多么復雜,它都有既定的規(guī)則,根據這些規(guī)則我們就可以選擇合適的方法去處理。

對于場景1,它的處理規(guī)則就是:當用戶左右滑動時,需要讓外部的View攔截點擊事件,當用戶上下滑動,需要讓內部View攔截點擊事件。具體來說就是根據滑動是水平滑動還是豎直滑動來判斷到底是由誰來攔截事件。

如圖:

Android觸摸事件的應用詳解

簡單來說,就是根據水平方向和豎直方向的距離差來判斷,如果是Dx>Dy,那么則是水平滑動,如果是Dy>Dx,那么則是豎直滑動。

場景2,則是比較特殊,它無法根據滑動的角度,距離差以及速度差來做判斷。這個時候就需要從業(yè)務上找到突破點,比如,當處于某種狀態(tài)時需要外部View響應用戶的滑動,而處于另外一種狀態(tài)時需要內部View來響應View的滑動

對于場景3的話,它的滑動規(guī)則也更復雜,和場景2一樣,同樣是從業(yè)務上找到突破點。

外部攔截法

外部攔截法是指點擊事件都是先經過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要此事件,就不攔截了,這樣就可以解決滑動沖突的問題,外部攔截法需要重寫父容器的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,這樣保證后續(xù)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();
 }
}

這個情況的攔截條件就是父容器在滑動過程中水平距離差比垂直距離差大,那么就進行攔截,否則就不攔截,繼續(xù)傳遞事件。

內部攔截法

內部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交給父容器進行處理,這種方法和Android中的事件分發(fā)機制不一致,需要配合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)方法時,父元素才能繼續(xù)攔截所需的事件。

前面是用自定義類似的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();
 }
}

這個攔截規(guī)則也是父容器在滑動過程中水平距離差與垂直距離差相比。

小結

總的來說,滑動沖突的場景可以分為三種,內外部方向不一致、內外部方向一致、嵌套前面兩種情況。如何解決,不管多么復雜的滑動沖突,可以進行拆分,根據的一定的規(guī)則,第一種情況可根據滑動距離差、速度差和角度差來解決,第二種和第三種情況,可根據業(yè)務上找到突破點,業(yè)務上一種狀態(tài)需要響應,切換到另外一種狀態(tài)時則不響應,根據業(yè)務需求得出相應的處理規(guī)則,有了處理規(guī)則可以進行下一步處理。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持創(chuàng)新互聯。

網站題目:Android觸摸事件的應用詳解
轉載來于:http://www.rwnh.cn/article22/igiscc.html

成都網站建設公司_創(chuàng)新互聯,為您提供網頁設計公司、搜索引擎優(yōu)化、小程序開發(fā)移動網站建設、面包屑導航網站內鏈

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯

h5響應式網站建設
额敏县| 西宁市| 玛曲县| 江门市| 松溪县| 盈江县| 阿坝县| 舟山市| 宜兴市| 平顶山市| 南部县| 连城县| 萝北县| 武强县| 莱州市| 顺平县| 南宫市| 兰坪| 双桥区| 息烽县| 吕梁市| 固阳县| 高清| 从化市| 阜南县| 东台市| 佛坪县| 洪湖市| 房山区| 乌兰浩特市| 休宁县| 门头沟区| 淄博市| 邯郸市| 南投县| 惠来县| 图们市| 维西| 北流市| 惠东县| 昭苏县|