Android的View 相關記錄

         View在Android是所有控件的基類,他是一種界面層的控件的一種抽象,代表了一個控件。除了View外還有ViewGroup,裏面可以包含許多控件。在Android中,我們可以將View的這種關係稱之爲控件樹,類似數據結構中的樹,具有一定的層級關係。

 

View的位置參數

View的位置主要由它的四個頂點來決定,分別對應於View的四個屬性:top,left,right,bottom。top代表左上角的縱座標,left是左上角的橫座標,right是右下角的橫座標,bottom是右下角的縱座標。需要注意的是這些都是相對於View的父容器來說的,是一種相對座標。

  

所有View的存在如下的關係式:

                                                width =right - left;

                                                height = bottom - top;

我們獲得left,top,right,bottom的只可以通過getXXX()方法,例如getLeft()獲得left的值。

從Android3.0開始,View增加了幾個額外的參數:x,y,translationX和translationY,其中x和y是View的左上角左邊,而translationX和translationY的View的左上角相對於父容器的偏移量,其默認值是0

 public float getX() {
        return mLeft + (mTransformationInfo != null ? mTransformationInfo.mTranslationX : 0);
    }
 public float getY() {
        return mTop + (mTransformationInfo != null ? mTransformationInfo.mTranslationY : 0);
    }

可以得出 x=left+translationX,y=top+translationY,需要注意的是,View在平移的過程中top和left表示的是原始左上角的位置信息,其值並不會改變,此時發生改變的是x,y,translationX,translationY這四個參數。

 

MotionEvent

在手指接觸屏幕後所產生的一系列事件中,典型的事件有如下幾種

ACTION_DOWN--手指剛剛接觸屏幕

ACTION_MOVE--手指在屏幕上移動

ACTION_UP--手指從屏幕上鬆開的一瞬間。

正常情況下,事件的順序位down-->move-->....-->move-->up,會伴隨着多個move的操作。通過MotionEvent我們可以獲得點擊事件發生的X和Y座標,其中getX和getY返回的是相對於當前View的左上角的X和Y座標,而getRawX和getRawY返回的是相對於整個手機屏幕的X和y 座標。處理View的點擊事件和事件攔截的時候要非常注意,具體可以參考我的 Android事件分發機制

 

TouchSlop

touchSlop是我們Android系統所能識別的被認爲滑動的最小距離,也就是說,當我們手指在屏幕上滑動的時候,如果滑動的距離小於touchSlop,系統將不會認爲用戶進行了滑動的操作。在不同的設備上,這個參數設置可能大小不相同。但是我們都可以通過如下的方法來獲取這個常量的值:ViewConfiguration.get(getContext()).getScaledTouchSlop()。當然我們實際開發中也可以不用這個常量值,而自己設定一個常量值,當我們的滑動具體超過我們設定的固定值之後,我們才認爲發生了滑動操作。但是一般不建議我們這樣做。

 

VelocityTracker

速度追蹤,用於追蹤手指滑動過程中的速度,包括水平和豎直方向的速度。他的使用過程非常簡單,首先在View的onTouchEvent方法中追蹤當前單擊事件的速度:

 

        VelocityTracker velocityTracker = VelocityTracker.obtain();
    	velocityTracker.addMovement(event);

當我們知道當前的速度的時候 ,偶們可以通過下面的方法獲得當前的X或者Y方向的速度:

   	velocityTracker.computeCurrentVelocity(1000);
    	int xVelocity = (int) velocityTracker.getXVelocity();
    	int yVelocity = (int) velocityTracker.getYVelocity();

獲取速度之前首先調用第一行代碼計算當前的速度,參數位毫秒

        velocityTracker.clear();
    	velocityTracker.recycle();

最後在不需要使用速度追蹤的時候,調用clear方法重置並回收內存。

 

 

Scroller

彈性滑動對象,用於實現View的彈性滑動。我們知道,當使用View的scrollTo或者ScrollBy方法來進行滑動的時候,其過程是瞬間完成的,這種沒有過渡效果的滑動用戶體驗不好。這個時候就可以使用Scroller來實現有過渡效果的滑動,其過程不是在瞬間完成的,而是在一定的時間間隔內完成的。Scroller本身無法讓View彈性滑動,它需要和View的computeScroll方法配合使用草能完成這個功能。在開發過程中他的典型代碼是固定的。

        Scroller scroller = new Scroller(getContext());
	//緩慢滾動到指定的位置
	private void smoothScrollTo(int destX,int destY){
		int scrollX = getScrollX();
		int delta = destX - scrollX;
		//1000ms內划向destX,效果就是慢慢滑動
		scroller.startScroll(scrollX, 0, delta, 0,1000);
		invalidate();
	}
	
	@Override
	public void computeScroll() {
		if(scroller.computeScrollOffset()){
			scrollTo(scroller.getCurrX(), scroller.getCurrY());
			postInvalidate();
		}
	}

 

 

 

View的滑動

 

實現View滑動的三種方式:

1.通過View自身提供的scrollTo/scrollBy來實現滑動

2.通過對View施加平移效果的動畫來實現滑動

3.通過改變View的LayoutParams使得View重新佈局來實現滑動

 

1.scrollTo/scrollBy 只能改變View中內容的佈局位置 而不能改變view的位置 其中的mScrollX的值指的是 view的左邊緣和view內容左邊緣的水平距離,單位是像素,mScrollY指的是 view的上邊緣和view內容上邊緣的垂直距離

當View的左邊緣在View內容的左邊緣的右邊時mScrollX爲正值 反之爲負值 (下圖所示)

當View的上邊緣在View內容的上邊緣的下邊時mScrollY爲正值 反之爲負值

2.使用動畫實現滑動

主要是控制translationX和translationY的屬性值 可以使用傳統的View動畫 使用傳統動畫往往要調用fillAfter設爲TRUE 保存動畫運行後的狀態 也可以使用屬性動畫 但是如果採用屬性動畫的話,爲了能夠兼容3.0以下的版本,需要採用開源動畫庫nineoldandroids

View 的普通tween動畫只能改變View影像的位置 實際位置也沒有改變 當然類似點擊事件的操作也不會隨之移動 此時可以使用屬性動畫來實現 但是得兼顧版本問題。

 

以上滑動只能實現View的滑動 這種滑動指的是那種從起始位置到目標位置的瞬間滑動 給用戶的體驗非常不好 因此Scroller就派上用場了 他可以實現View的彈性滑動,其工作原理是:

Scroller本身並不能實現滑動,他需要配合View的computeScroll方法才能完成彈性滑動的效果,他不斷地讓view重繪,而每一次重繪距滑動的起始時間會有一個時間間隔,通過這個時間間隔Scroller就可以得出View當前的滑動的位置,知道了滑動位置就可以通過scrollTo方法來完成View的滑動。就這樣view的每一次重繪導致view進行小幅度的滑動,而多次小幅度的滑動就構成了彈性滑動。

Scroller中比較重要的有computeScrollOffset方法,這個方法只要計算隨着時間的流逝計算scrollX和scrollY的值 返回爲TRUE時 表示滑動還未結束 返回false的時候表示整個滑動結束

 

 

常見的滑動衝突場景

 

場景1:外部View的滑動方向和內部的滑動方向不一致

場景2:外部的滑動方向和內部的滑動方向一致

場景3:上面2種情況的嵌套

對於場景1,主要是將ViewPager和Fragment的配合使用所組成的頁面滑動效果,在這種效果中,可以通過左右滑動來切換頁面,而每個頁面內部往往又是一個ListView,本來這種情況中存在滑動衝突的,可是系統ViewPager幫我們做了這種滑動衝突的處理。如果不是使用ViewPager而是ScrollView,那就必須手動處理滑動衝突了。除了這種典型的滑動,還有外部上下 內部左右滑動的衝突 也是屬於這一類的衝突的。

對於場景2,當內外兩層都在同一個方向可以滑動的時候,顯然存在邏輯問題。因爲當手指滑動的時候,系統無法知道用戶到底是想讓那一層滑動,所有當手指滑動的時候就會出現問題,要麼只有一層能夠滑動要麼就是內外兩層都滑動得很卡頓。在實際開發中這種場景主要是指內外兩層同時能上下滑動或者內外兩層同時能夠左右滑動。

對於場景3,場景3是場景1和場景2的嵌套。舉個例子來說,外部是一個SlideMenu效果,然後內部有個ViewPager,ViewPager的每一個效果又是一個ListView。雖然場景的滑動衝突看起來很複雜,但是他的幾個單一的滑動衝突的疊加,因此只需要處理內層和中層,中層和外層之間的滑動衝突即可,而具體的處理方法其實和場景1,場景2是相同的。

 

滑動衝突的處理規則

    對於上面的場景1,他的處理規則是:當用戶左右滑動時,需要讓外部的View攔截點擊事件,當上下滑動的時候,需要讓內部的View攔截點擊事件。這個時候就必須根據滑動特徵來解決滑動衝突。我們可以根據水平和豎直的滑動距離來判斷滑動的方向。假如記錄水平滑動距離位dx,豎直滑動距離爲dy。我們可以約定當dy>dx的時候認爲是在豎直方向上滑動,或者認爲當dy大於我們固定的某一個值的時候,可以認爲在豎直方向上滑動,其他的都認爲水平滑動。

  對於場景2,比較特殊,他無法根據距離或者滑動角度來確定是外部View滑動還是內部View滑動。那麼此時,我們就必須從業務上找到突破口了。比如業務上規定:當處於某種狀態時需要外部的view響應用戶的滑動,而處於另外的一種狀態的時候需要內部View來響應用戶的滑動操作。比如ScrollView中嵌套listview,可以根據listview是否滑動到頂部來確定是響應listview的滑動還是scrollView的滑動。

 對於場景3,他也無法用場景1的處理方法來處理。此時也必須根據業務上的需要來處理滑動衝突。

 

滑動衝突的處理方法

     處理滑動衝突的方法一般有2種:

外部攔截法:就是由外部的父View來決定是自己處理滑動事件還是交給子View來處理滑動事件

內部攔截法:父View不管滑動事件,全部傳遞給子View來處理,而子View只處理自己該處理的滑動事件,將自己處理不了或者不處理的滑動事件再回傳給父View來處理。此時有時候需要結合requestDisallowInterceptTouchEvent方法讓父View不處理滑動事件才能正常工作。

看看外部攔截法 是怎麼操作的呢?我們一般重寫外部容器的onInterceptTocuchEvent方法,然後根據判斷看看外部容器是否需要攔截

內部攔截法的操作就是外部容器不攔截事件,內部控件重寫dispatchTouchEvent方法,讓內部控件去根據滑動條件 調動父控件的requestDisallowIntercepTouchEvent告訴父控件要不要攔截,requestDisallowIntercepTouchEvent方法參數位true的時候就是讓外部控件不要攔截Touch事件,false就是可以攔截:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章