android父子控制元件手勢衝突的解決
首先,要談一下android父子控制元件之間事件的分發,對於事件的分發有幾個原則需要了解。
(1) android事件分發是從父控制元件向子控制元件逐級分發傳遞的。(2) 每一層控制元件都可能消費這個事件,消費後不再向下傳遞(這也是父子控制元件對於手勢操作衝突的主要原因)。
(3) android系統中的每個ViewGroup的子類都具有下面三個和事件分發處理密切相關的方法:
1.public boolean dispatchTouchEvent(MotionEvent ev) 這個方法用來分發TouchEvent
2.public boolean onInterceptTouchEvent(MotionEvent ev) 這個方法用來攔截TouchEvent
3.public boolean onTouchEvent(MotionEvent ev) 這個方法用來處理TouchEvent
(4) android的觸控是由一個ACTION_DOWN(按下),多個ACTION_MOVE(移動),一個ACTION_UP(抬起)組成。
父子控制元件間的事件傳遞,用一張圖可以更好的體現:
1
(1)、distachTouchEvent用於分發事件,true直接消費事件並不在分發,false向intercertTouchEvent分發
(2)、internceptTouchEvent用於攔截事件,true直接分發事件至自己view的onTouchEvent方法經行處理,false繼續分發
到子view的dispathcTouchEvent。
(3)、onTouchEevent作為事件處理的部分,響應由多個touchevent所組成的手勢。
下面我們用這些內容來處理一個具體的問題,在豎直滑動的scrollview中巢狀水平滑動的viewpager的問題。viewpager中巢狀listview也可以用類似的方法解決。
由於scrollview處於佈局的外層,來自系統的觸控事件會先傳遞至外層的scrollview,scrolliew對於事件的消將直接影響事件繼續分發至內層的viewpager。
先來看一下scrollview的distachTouchEvent原始碼:
@Override
361 public boolean More ...onInterceptTouchEvent(MotionEvent ev) {
362 /*
363 * This method JUST determines whether we want to intercept the motion.
364 * If we return true, onMotionEvent will be called and we do the actual
365 * scrolling there.
366 */
367
368 /*
369 * Shortcut the most recurring case: the user is in the dragging
370 * state and he is moving his finger. We want to intercept this
371 * motion.
372 */
373 final int action = ev.getAction();
374 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
375 return true;
376 }
377
378 if (!canScroll()) {
379 mIsBeingDragged = false;
380 return false;
381 }
382
383 final float y = ev.getY();
384
385 switch (action) {
386 case MotionEvent.ACTION_MOVE:
387 /*
388 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
389 * whether the user has moved far enough from his original down touch.
390 */
391
392 /*
393 * Locally do absolute value. mLastMotionY is set to the y value
394 * of the down event.
395 */
396 final int yDiff = (int) Math.abs(y - mLastMotionY);
397 if (yDiff > mTouchSlop) {
398 mIsBeingDragged = true;
399 }
400 break;
401
402 case MotionEvent.ACTION_DOWN:
403 /* Remember location of down touch */
404 mLastMotionY = y;
405
406 /*
407 * If being flinged and user touches the screen, initiate drag;
408 * otherwise don't. mScroller.isFinished should be false when
409 * being flinged.
410 */
411 mIsBeingDragged = !mScroller.isFinished();
412 break;
413
414 case MotionEvent.ACTION_CANCEL:
415 case MotionEvent.ACTION_UP:
416 /* Release the drag */
417 mIsBeingDragged = false;
418 break;
419 }
420
421 /*
422 * The only time we want to intercept motion events is if we are in the
423 * drag mode.
424 */
425 return mIsBeingDragged;
426 }
427
397-399可以看到,在scrollview中,當其捕捉到手指上下滑動的距離大於mTouchSlop時,onInterceptTouchEvent方法返回true,攔截事件至ontouchEvent中並消費,內層的viewpager將得不到觸控事件的傳遞。
雖然onscroll對事件是否攔截有一定的處理,但是有時僅僅這種效果不能滿足需要。這種處理策略意味著,我們水平滑動viewpager時,必須保持上下滑動的距離小於mTouchSlop,否則,觸控事件會被上層的scrollview直接消費。為了解決這種衝突,個人想法是上層的scrollview不僅要限制豎直方向的TouchSlop,同時要限制水平方向的TouchSlop,避免和viewpager的衝突。這樣需要我們重寫scrollview的onInterceptTouchEvent()方法。
具體的實現方法如下:
public class MScrollView extends ScrollView {
private float FistXLocation;
private float FistYlocation;
private boolean Istrigger = false;
public Animation animationUp;
public Animation animationDown;
private final int TRIGER_LENTH = 50;
private final int HORIZOTAL_LENTH = 20;
private TMHDItemsGridsAvtivity faAvtivity;
public TMHDScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
int deltaX = 0;
int deltaY = 0;
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
deltaX = (int)(FistXLocation - x);
deltaY = (int)(FistYlocation - y);
if (Math.abs(deltaY) > TRIGER_LENTH
&& Math.abs(deltaX) < HORIZOTAL_LENTH) {
Istrigger = true;
return super.onInterceptTouchEvent(ev);
//攔截這個手勢剩下的部分 ,使他不會響應viewpager的相關手勢
}
return false;//沒有觸發攔截條件,不攔截事件,繼續分發至viewpager
case MotionEvent.ACTION_DOWN:
FistXLocation = x;
FistYlocation = y;
if(getScaleY()<-400){
System.out.println(getScaleY());
}
requestDisallowInterceptTouchEvent(false);
return super.onInterceptTouchEvent(ev);
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (Istrigger) {
Istrigger = false;
return super.onInterceptTouchEvent(ev);
}
break;
}
return super.onInterceptTouchEvent(ev);
}
}
下面這部分就是自己定義的水平和垂直的事件攔截觸發條件。
private final int TRIGER_LENTH = 50;
private final int HORIZOTAL_LENTH = 20;
重寫的MScrollView,自定義了onInterceptTouchEvent()方法,從而自定義了攔截的條件。解決了父子兩個控制元件之間相互衝突的問題。
相關文章
- Flutter和iOS手勢衝突解決思路FlutteriOS
- Android com.android.support衝突解決Android
- Android 解決BottomSheetDialog 拖曳衝突問題Android
- com.android.support衝突的解決辦法Android
- git 解決衝突Git
- Git 解決衝突Git
- Android View 滑動衝突解決方式以及原理AndroidView
- iOS 開發中遇到的手勢衝突iOS
- 程式衝突及其解決
- git pull 衝突解決Git
- hash衝突解決方法
- Git衝突解決技巧Git
- git pull衝突的解決方案Git
- JAR衝突問題的解決JAR
- SVN解決衝突 記錄
- css命名衝突解決方法CSS
- iOS開發之解決MMDrawerController側滑選單與中心檢視手勢衝突iOSController
- 依賴衝突時的解決方法
- 聊聊Flutter中的常見滑動手勢衝突Flutter
- Git 衝突了怎麼辦,如何高效快速的解決程式碼衝突?Git
- git 解決版本衝突問題Git
- 解決預設方法衝突的規則
- hash解決衝突的方法優缺點
- 解決動態庫的符號衝突符號
- Git 解決本地遠端版本衝突Git
- Jar包衝突解決方案調研JAR
- GO 問題之多版本衝突解決Go
- maven依賴衝突以及解決方法Maven
- Elasticsearch——併發衝突以及解決方案Elasticsearch
- Maven依賴衝突解決總結Maven
- 解衝突用到的命令
- Maven Idea檢視解決jar包衝突MavenIdeaJAR
- git 解決衝突 —— git stash 當前修改Git
- 一招解決所有依賴衝突
- Git Worktree:解決分支依賴衝突的問題Git
- 一種巢狀滑動衝突的解決方案巢狀
- Flutter | 如何優雅的解決依賴版本衝突Flutter
- 如何克服解決Git衝突的恐懼症?(序)Git
- 解決Oracle XDB與Tomcat等的8080埠的衝突OracleTomcat