Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android滑動組件嵌套一般思路,多任務手勢思路,觸摸傳遞思路,【例】listview嵌套viewpager

android滑動組件嵌套一般思路,多任務手勢思路,觸摸傳遞思路,【例】listview嵌套viewpager

編輯:關於Android編程

在android UI開發中,我們經常會遇到這種需求:

兩個支持滑動的組件,比如listview嵌套多個listview,listview的item是一個viewpager或gallary?亦或是scrollview嵌套scrollview等等。

一般情況下,你還可能需要支持如下幾種功能:

¤ 兩層組件都可以滑動

¤ 不讓兩個組件同時滑動,或者讓兩個組件同時滑動並可以自己調節

¤ 不影響底層view的子view和嵌套view的子view的點擊事件


實現上述功能時,我們也經常遇到一些問題:

¤ 點擊事件被屏蔽

¤ view的滑動不流暢(這裡不討論)

¤ view的滑動條件邏輯不符合設計

¤ 不知道怎麼重寫函數滿足邏輯


這裡筆者介紹一下解決這類需求的一般思路,以及一個支持多任務手勢的listview和viewpager的需求案例。


觸摸傳遞思路:

如果說,需求要把兩個可以滑動的view嵌套在一起,那麼要注意一些問題

¤ 像listview和scrollview,viewpager等可以滑動的組件,都是有自己的滑動規則的,我們最好不去重寫怎麼滑動它們(即最好不要去監聽觸摸的坐標用代碼去滑動它們)。我們只要把我們需要的非滑動業務寫好就可以了,當然我們也不能阻斷默認滑動規則的執行


¤ viewgroup、view的事件分發傳遞機制需要特別清楚,你要知道,listview繼承自viewgroup,當一串觸摸事件發生時,當前activity收到這個事件,dispatch給頂層viewgroup,頂層viewgroup先是調用dispatchTouchEvent,該函數內部先在onInterceptTouchEvent函數決定是否要截斷,如果選擇截斷,執行自己的onTouchEvent,子view不會接受到這個TouchEvent;如果不截斷,該viewGroup會分發給所有點擊范圍內的子view(如果你不想分發給點擊范圍內的子view,你需要重寫更多dispatchEvent的部分),即調用子view的dispatchTouchEvent,只要子view中有一個返回true(代表子View消費了這個事件),則該viewGroup不會執行這個TouchEvent,如果沒有返回true,則調用該viewGroup的OnTouchEvent(),如果它返回false,說明沒有消費掉這個事件,接著調用onClick,如果還是沒有消費,則該viewgroup的dispatch函數會返回false。

這一套邏輯可能會很復雜,除卻view沒有onInterceptTouch以外,可以這麼總結:

每個view收到事件,如果通過判斷不決定阻斷該事件,判斷是否有一個子view要消費這個事件,如果沒有,則執行onTouchEvent,即嘗試自己消費,如果自己不消費,該view的dispatch就返回了false,表示該view包括該view的分支下沒有消費該事件。


¤ 要非常明確業務需求,因為它要明確地寫成代碼形式,還要寫在正確的地方(有時你可能會猶豫為了執行父view的一個多任務手勢,應該在父view截斷還是子view的dispatch返回一個false)

¤ 子view壟斷父view事件:this.getParent().requestDisallowInterceptTouchEvent(true);該方法禁止父view阻斷事件,即一定可以接受到事件,記得完成一套觸摸時關閉這個。


多任務手勢思路:

相信對讀者來說,設計一個view的多任務手勢並不是非常困難(重寫onTouchEvent,記錄按下、移動、抬起的坐標做一些相應的運算),但是這個問題放到viewq嵌套上,你可以考量了。你可能會遇到這樣一些問題

¤ 如果子view消費了touchEvent,父view的任何行為不會被調用。

解決:

1.如果你希望父子view同時消費這個事件,你需要重寫父view的dispatch並強行調用onTouchEvent。

2.如果這種情形,你不希望子view消費這個事件,有兩個方案:重寫父view的intercept,檢查手勢,阻斷這個事件;重寫子view的dispatch,檢查手勢,返回false。一個是父view強制阻斷,一個是子view強制不消費。

3.如果你當前狀態還並不明確應該由哪個view來消費這個事件,你大可以放任不管,直到判斷出需要阻斷或者消費(因為你業務需求對這種狀態沒有明確定義,就不需要去定義怎麼處理了)。


一個簡單的案例,附部分源碼:

需求描述:

listview嵌套viewpager,listview的每一個item由xml布局定義,布局中包括一個viewpager和其他部分。

支持的操作:

¤ viewpager可以左右滑動,listview可以上下滑動,不會同時在滑動中,自然地,只有一開始點到的那個viewpager可以滑動,不會滑動其他viewpager。

¤ listview支持雙指縮小操作。

¤ listview支持itemClick操作(不點擊到viewpager中的圖片)。

¤ viewpager中的圖片支持點擊操作。


先是listview的部分:

onItemClick定義了 我自己的業務事件,當點擊每一個item並且沒有被viewpager的圖片view消費時觸發。

在dispatchTouchEvent()中判斷如果當前手指數大於等於2,即雙指操作時,強行調用onTouchEvent,並返回,此時不會調用super.dispatchTouchEvent(),即事件不會走到子view裡面去。

在onTouchEvent()中我就可以簡單地實現多任務手勢的業務需求啦。

@Override
	public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) {
		// TODO Auto-generated method stub
		if (mSet.get(arg2).size() > 1)
			this.downGranularity(arg2);
	}

	/**
	 * 自定義listview 用於事件分發的處理。
	 * @author ipip
	 *  2014年8月4日上午10:46:53
	 */
	private class MyListView extends ListView {
		public MyListView(Context context) {
			super(context);
			this.setDivider(null);
			// TODO Auto-generated constructor stub
		}

		/**
		 * 處理listView的觸摸事件
		 */
		@Override
		public boolean onTouchEvent(MotionEvent ev) {
			switch (ev.getAction() & MotionEvent.ACTION_MASK) {
			case MotionEvent.ACTION_DOWN:
			case MotionEvent.ACTION_POINTER_DOWN:
				if (ev.getPointerCount() == 2)
					dst = measureFingers(ev);
				break;
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_POINTER_UP:

				if (ev.getPointerCount() == 2 && ndst < dst) {
					upGranularity();
					dst = -1;
				}
				break;

			case MotionEvent.ACTION_MOVE:
				if (ev.getPointerCount() >= 2) {
					ndst = measureFingers(ev);
				}
				break;
			}
			if (ev.getPointerCount() >= 2)
				return true;
			return super.onTouchEvent(ev);
		}
		/**
		 * 
		 */
		@Override
		public boolean dispatchTouchEvent(MotionEvent ev) {
			if (ev.getPointerCount() >= 2) {
				return onTouchEvent(ev);
			}
			return super.dispatchTouchEvent(ev);
		}
	}

接下來是viewpager的代碼:

先解釋一下我的onTouchEvent(),由於我的viewpager一行可以容納4張圖片(在適配器中重寫getPageWidth()),所以當圖片數小於4時,我不處理滑動事件,否則會出現圖片瞬移閃爍的現象(其實這個挺有趣的,還不清楚什麼原理)。

在dispatch中,首先判斷是否雙指操作。接著是記錄第一次操作的位置以及每次移動時的判斷。

public class MyViewPager extends ViewPager {

	public MyViewPager(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

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

	private float xDown;// 記錄手指按下時的橫坐標。
	private float xMove;// 記錄手指移動時的橫坐標。
	private float yDown;// 記錄手指按下時的縱坐標。
	private float yMove;// 記錄手指移動時的縱坐標。
	private boolean viewPagerScrolling = false;
	private boolean fatherScrolling = false;

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (this.getChildCount() < 4)
			return false;
		return super.onTouchEvent(ev);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		// TODO Auto-generated method stub
		if (ev.getPointerCount() >= 2)
			return false;

		switch (ev.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
		case MotionEvent.ACTION_POINTER_DOWN:
			xDown = ev.getRawX();
			yDown = ev.getRawY();
			fatherScrolling = false;
			break;
		case MotionEvent.ACTION_MOVE:
			xMove = ev.getRawX();
			yMove = ev.getRawY();
			if (fatherScrolling) {
				return false;
			}
			if (viewPagerScrolling) {
				return super.dispatchTouchEvent(ev);
			}

			if (Math.abs(yMove - yDown) < 10 && Math.abs(xMove - xDown) > 3) {
				this.getParent().requestDisallowInterceptTouchEvent(true);
				viewPagerScrolling = true;
			} else if (Math.abs(yMove - yDown) >= 10) {
				fatherScrolling = true;
				return false;
			} else
				return false;
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_POINTER_UP:
			viewPagerScrolling = false;
			if (ev.getPointerCount() == 1)
				this.getParent().requestDisallowInterceptTouchEvent(false);
			break;
		}
		return super.dispatchTouchEvent(ev);
	}
}

上面的關鍵句:

			if (fatherScrolling) {
				return false;
			}
			if (viewPagerScrolling) {
				return super.dispatchTouchEvent(ev);
			}

			if (Math.abs(yMove - yDown) < 10 && Math.abs(xMove - xDown) > 3) {
				this.getParent().requestDisallowInterceptTouchEvent(true);
				viewPagerScrolling = true;
			} else if (Math.abs(yMove - yDown) >= 10) {
				fatherScrolling = true;
				return false;
			} else
				return false;

如果在該點有明顯的橫向移動,並且豎直方向一定在給定數值內,我判定我要執行viewpager的橫向滑動操作,此時,我賦值viewPagerScrolling為true,那麼接下來的所有move都會默認調用super.dispatchTouchEvent(),並且拒絕父view的阻斷,即會應用默認的滑動方式直到一串事件的結束。如果豎直方向移動超過范圍,並且之前橫向移動不明顯,那麼我判定父view的listview要滑動,此時賦值fatherScrolling為ture,那麼之後每次move都會返回false,即不消費之後所有事件。

當然了,在所有手指抬起後,這些狀態被重置了。

之後,viewpager的每一個imageView都可以簡單地設置一個onClickListener(),父view和爺view不會阻斷它的點擊事件。

簡單敘述一下為什麼:
¤ itemClick和imageView的click為什麼沒有被阻斷?

前提是祖輩view們沒有阻斷它的事件,即祖輩view不會消費down-up,不會消費down-move-up。簡單地說就是,像按下立即抬起這樣的click的事件,兩層view都不會將它消費掉,可以以默認方式順利地傳給子view。

¤ 為什麼要在viewpager中判斷是否有一定的橫向移動?

如果直接讓viewpager消費這個事件,父view便沒有機會消費該事件了,我只有以一定的移動為基礎,才能判斷到底是豎著滑還是橫著滑。

你會說我可以先讓子view消費,當豎直方向移動太多就轉交給listview消費。那麼其實我還是要判斷橫向移動的距離,如果橫向移動了100px,然後因為豎直移動了15px就不讓viewpager消費跟業務需求不同,既然同樣要計算橫向距離,最好的方法應該就是先等待,時機成熟時鎖定消費該事件的view直到事件鏈結束。

¤ 為什麼在listview在dispatch中判斷是否雙指而不是在intercept?

呀,其實都可以的。。。

以下是效果圖,用新版adt的Screen Recording錄屏,好像從kitkat開始的adt都支持錄屏了:

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved