Android藝術探索學習筆記:第3章View的事件體系

一.View的基礎知識

1.View的位置參數(shù)

View的位置主要由它的四個定點來決定,分別對應View的四個屬性:top、left炮障、right、bottom梢为,   
這下坐標都是相對父容器而言的褂傀。從3.0開始View增加了x、y引颈、translationX诵冒、translationY抓狭;  
x和y是View左上角的坐標,translationX和translationY是View左上方相對父容器的偏移量造烁。
x = left + translationX否过;  
y = top + translationY;
View平移的過程中惭蟋,top和left表示的是原始左上角的位置信息苗桂;其值并不會改變,  
此時發(fā)生改變的是x告组、y煤伟、translationX和translationY。

2.MotionEvent

點擊屏幕后松開木缝,事件序列            DOWN->UP  
點擊屏幕滑動一會再松開便锨,事件序列為   DOWN->MOVE->...->MOVE->UP。

getX/getY  獲取相對當前View左上角的x和y坐標  
getRawX/getRawY  獲取相對手機屏幕左上角的x和y坐標我碟。

3.TouchSlop

是系統(tǒng)能識別滑動的最小距離
ViewConfiguration.get(getContext()).getScaledTouchSlop()

4.VelocityTracker

用于追蹤手指在滑動過程中的速度放案。使用前在View的onTouchEvent方法中追蹤當前單擊事件的速度。

VelocityTracker velocityTracker = VelocityTracker.obtain(); 
velocityTracker.addMovement(event); 
//想知道滑動速度時
//獲取速度前需計算速度  參數(shù) 時間間隔 單位ms
velocityTracker.computeCurrentVelocity(1000);  
//獲取速度
int xVelocity = (int)velocityTracker.getXVelocity(); 
int yVelocity = (int)velocityTracker.getYVelocity();

5.GestureDetector

用于輔助檢測用戶的單擊矫俺、滑動吱殉、長按、雙擊等行為厘托;建議:如果只是監(jiān)聽滑動相關的推薦  
在onTouchEvent中實現(xiàn)友雳,如果需要監(jiān)聽雙擊,使用GeststureDetector铅匹。

eg: GestureDetector mGes = new GestureDetecotr(this);
    在View的OnTouchEvent方法中傳遞給mGes對象:
    return mGes.onTouchEvent(event);

6.Scroller

用來實現(xiàn)View的彈性滑動押赊,View的scrollTo/scrollBy是瞬間完成的,  
使用Scroller配合View的computeScroll方法配合使用達到彈性滑動的效果

二.View的滑動

3種滑動方式:
1.scrollTo和scrollBy
2.動畫來移動View
3.改變控件位置

1.ScrollTo 和 ScrollBy

scrollTo和scrollBy只能改變View內(nèi)容而不能改變View本身的位置包斑。scrollBy內(nèi)部 
也是調(diào)用了scrollTo流礁,它是基于當前位置的相對滑動涕俗,scrollTo是基于所傳遞參數(shù) 
的絕對滑動。在滑動過程中mScrollX/mScrollY總是等于View邊緣與View內(nèi)容邊緣  
的距離崇棠,這兩個屬性用getScrollX/getScrollY方法獲取

2.使用動畫

幀動畫 View動畫 屬性動畫

使用動畫來移動View,主要是操作View的translationX和translationY屬性。需要  
注意的是丸卷,View動畫只是對View的影像做操作枕稀,它并不能真正改變View的位置參  
數(shù),如果這個View設置了點擊事件谜嫉,點擊動畫后的新位置無法觸發(fā)點擊事件的萎坷,  
使用屬性動畫沒有此問題,但3.0之前系統(tǒng)無屬性動畫沐兰。

eg: xml設置
  <scale  
        android:fromXScale="float"  
        android:toXScale="float"  
        android:fromYScale="float"  
        android:toYScale="float"  
        android:pivotX="float"  
        android:pivotY="float" />  
    代碼中設置
       ScaleAnimation sAnima = new ScaleAnimation(0, 5, 0, 5);
       scale.startAnimation(sAnima); 
       
屬性動畫:
        ObjectAnimator//  
         .ofFloat(view, "rotationX", 0.0F, 360.0F)//  
         .setDuration(500)//  
         .start();  

3.改變布局 參數(shù)

改變布局參數(shù)實現(xiàn)滑動哆档,即改變LayoutParams,如想將一個View右平移100px,  
只需要將該View的LayoutParams里的marginLeft增加100px即可

ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mBtn.getLayoutParams();
params.leftMargin += 100;
mBtn.requestLayout();
//或者mBtn.setLayoutParams(params);

==總結(jié):==
scrollTo/scrollBy: 操作簡單住闯,適合對View內(nèi)容的滑動
動畫: 操作簡單瓜浸,適合沒有交互的View和實現(xiàn)負責的動畫效果
改變布局參數(shù):操作稍微復雜,適合有交互的View

三.彈性滑動

1.使用Scroller原理: startScroll()保存了我們傳遞的幾個參數(shù) ——> invalidate()會導致View重繪 ——> draw() ——> computeScroll()該方法為空實現(xiàn)比原,我們內(nèi)部調(diào)用scrollTo(x,y)實現(xiàn)滑動 和 postInvalidate() 繼續(xù)重繪插佛,反復下去完成彈性滑動。

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

2.通過動畫可以直接實現(xiàn)彈性滑動

3.使用延時策略完成滑動量窘,核心思想 就是通過發(fā)送一系列延時消息從而達到一種漸進式的效果雇寇。用Handler或View的postDelayed方法,postDelayed發(fā)送延時消息蚌铜,然后消息中進行View滑動锨侯,接連不斷的發(fā)送這種延時消息,達到彈性滑動的效果冬殃。也可以使用線程的sleep方法來實現(xiàn)囚痴。

四.View的事件分發(fā)機制

1. 點擊事件的傳遞規(guī)則

dispatchTouchEvent(MotionEvent event) 用來處理事件的分發(fā),返回結(jié)果受當前View的onTouchEvent和下級View的  
dispatchTouchEvent方法影響审葬,表示是否消耗該事件渡讼。  

onInterceptTouchEvent(MotionEvent event)
在dispatchTouchEvent方法內(nèi)部調(diào)用,用來判斷是否攔截某個事件耳璧,如果  
當前View攔截了某個事件成箫,那在同一個事件序列中,此方法不會再次調(diào)用旨枯,  
返回結(jié)果表示是否攔截當前事件  

onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中調(diào)用蹬昌,用來處理點擊事件,返回結(jié)果表示是否消耗當前事件攀隔,如果不消耗  
皂贩,在同一事件序列里栖榨,當前View無法再次接收到事件三者關系可以用如下偽代碼表示

==原理==:dispatchTouchEvent方法中判斷是否需要攔截,需要的話明刷,就不再調(diào)用onInterceptTouchEvent婴栽,并調(diào)用onTouchEvent方法,如果不攔截辈末,就會調(diào)用子VIew的dispatchTouchEvent方法愚争。

public boolean dispatchTouchEvent(MotionEvent ev){    
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);   
    }
    return consume;
}

事件順序: OnTouListener的OnTouch > onTouchEvent > onClickListener
如果OnTouch中返回true,就不會調(diào)用onTouchEvent方法挤聘,返回false就會調(diào)用轰枝,如果onTouchEvent中存在onClickListener,onClick一定會被調(diào)用组去。除非設置該VIew的onClickible 和 onLongClickible都為false鞍陨。

1.對于一個根ViewGroup,點擊事件產(chǎn)生后从隆,首先會傳遞給它诚撵,這時他的dispatchTouchEvent會調(diào)用,
如果它的onInterceptTouchEvent返回true表示要攔截當前事件键闺,接下來事件會交給這個ViewGroup  
處理砾脑,它的onTouchEvent就會被調(diào)用,如果這個ViewGroup的onInterceptTouchEvent返回false,  
則事件會繼續(xù)傳遞給子元素艾杏,子元素的dispatchTouchEvent會調(diào)用韧衣,如此反復直到事件被處理。  

view事件傳遞


image
2.當一個View需要處理事件時购桑,如果設置了OnTouchListener,那么OnTouchListener的onTouch方法  
會回調(diào)畅铭,如果onTouch返回false,則當前View的onTouchEvent方法會被調(diào)用;如果返回true,那么  
onTouchEvent方法將不會調(diào)用勃蜘。由此可見硕噩,OnTouchListener優(yōu)先級高于onTouchEvent。  
OnClickListener優(yōu)先級處在事件傳遞的尾端缭贡。

3.一個點擊事件產(chǎn)生后炉擅,傳遞順序:Activity->Window->View;如果一個View的onTouchEvent返回false,  
那么它的父容器的onTouchEvent會被調(diào)用阳惹,以此類推谍失,所有元素都不處理該事件,最終將傳遞給Activity  
處理莹汤,即Activity的onTouchEvent會被調(diào)用快鱼。

4.同一個事件序列是指從手指觸摸屏幕那一刻開始,到手指離開屏幕那一刻(down->move...move->up)

5.一個事件序列只能被一個View攔截且消耗,同一個事件序列所有事件都會直接交給它處理抹竹,  
并且它的onInterceptTouchEvent不會再被調(diào)用线罕。


6.某個View一旦開始處理事件,如果它不消耗ACTION_DOWN(onTouchEvent返回了false)窃判,那么同一事件  
序列中其他事件都不會再交給它來處理钞楼,事件將重新交給他的父元素處理,即父元素的onTouchEvent會被調(diào)用袄琳。  

7.如果某個View不消耗除ACTION_DOWN以外的其他事件询件,那么這個點擊事件會消失,此時父元素的onTouchEvent  
并不會被調(diào)用跨蟹,并且當前View可以收到后續(xù)事件雳殊,最終這些消失的點擊事件會傳遞給Activity處理

8.ViewGroup默認不攔截任何事件橘沥,ViewGroup的onInterceptTouchEvent方法默認返回false窗轩。  

9.View沒有onInterceptTouchEvent方法,一旦有事件傳遞給它座咆,那么它的onTouchEvent方法就會被調(diào)用痢艺。  

10.View的onTouchEvent方法默認消耗事件(返回true),除非他是不可點擊的(clickable和longClickable同時為false)。  
View的longClickable屬性默認都為false,clickable屬性分情況介陶,Button默認為true堤舒,TextView默認為false。
onClick發(fā)生的前提是View可點擊哺呜,并且它收到了down和up事件舌缤。

11.事件傳遞過程是由內(nèi)而外,事件總是先傳遞給父元素某残,然后在由父元素分發(fā)給子View国撵,通過  
requestDisallowInterceptTouchEvent方法可以在子元素干預父元素的事件分發(fā)過程,但ACTION_DOWN事件除外玻墅。

2.頂級View對點擊事件的分發(fā)過程

①.ViewGroup對事件攔截的處理介牙。

final boolean intercepted;
if(actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null){
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT)!=0;
    if(!disallowIntercept){
        intercepted = onInterceptTouchEvent(Ev);
        ev.setAction(action);
    }else{
        intercepted = false;
    }
    
}else{
    intercepted = true;
}

原理:如果事件類型是MotionEvent.ACTION_DOWN 或者 已經(jīng)由子元素成功處理時,既mFirstTouchTarget指向子元素澳厢,不為null环础,當ViewGroup攔截事件時,當ACTION_MOVE 和 ACTION_UP來時剩拢, 將不會再去調(diào)用onInterceptTouchEvent线得。并且其他的事件都會交給它處理。

1.ViewGroup會在down事件到來時重置狀態(tài)徐伐,會將FLAG_DISALLOW_INTERCEPT重置框都,所以這個標志不能攔截down事件。
2.FLAG_DISALLOW_INTERCEPT可以請求不要攔截事件,(move 和  up)魏保,前提是ViewGroup不攔截down事件熬尺。

②.ViewGroup不攔截事件,分發(fā)給子View的處理
子View接受點擊事件的標準:
1子View在播放動畫 2.點擊事件坐標落在子View內(nèi)

子View如果設置了onClick事件谓罗,則會默認設置onClickable為true 長按事件一樣粱哼,

3.View的滑動沖突

1.常見滑動沖突場景

  • 場景1 —— 外部滑動方向與內(nèi)部滑動方向不一致,比如ViewPager中包含ListView;
  • 場景2 —— 外部滑動方向與內(nèi)部滑動方向一致檩咱,比如ScrollView中包含ListView;
  • 場景3 —— 上面兩種情況的嵌套
image

2.攔截方法: 外部攔截 內(nèi)部攔截

外部攔截:

q.png

在這里揭措,首先down事件父容器必須返回false ,因為若是返回true刻蚯,也就是攔截了down事件绊含,那么后續(xù)的move和up事件就都會傳遞給父容器,子元素就沒有機會處理事件了炊汹。

其次是up事件也返回了false躬充,一是因為up事件對父容器沒什么意義,其次是因為若事件是子元素處理的讨便,卻沒有收到up事件會讓子元素的onClick事件無法觸發(fā)充甚。

內(nèi)部攔截:

w.png

修改父類的攔截方法:

e.png

總結(jié):外部優(yōu)先于內(nèi)部攔截

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市霸褒,隨后出現(xiàn)的幾起案子伴找,更是在濱河造成了極大的恐慌,老刑警劉巖废菱,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件技矮,死亡現(xiàn)場離奇詭異,居然都是意外死亡殊轴,警方通過查閱死者的電腦和手機衰倦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梳凛,“玉大人耿币,你說我怎么就攤上這事∪途埽” “怎么了淹接?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叛溢。 經(jīng)常有香客問我塑悼,道長,這世上最難降的妖魔是什么楷掉? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任厢蒜,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斑鸦。我一直安慰自己愕贡,他們只是感情好,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布巷屿。 她就那樣靜靜地躺著固以,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘱巾。 梳的紋絲不亂的頭發(fā)上憨琳,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音旬昭,去河邊找鬼篙螟。 笑死,一個胖子當著我的面吹牛问拘,可吹牛的內(nèi)容都是我干的遍略。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼场梆,長吁一口氣:“原來是場噩夢啊……” “哼墅冷!你這毒婦竟也來了纯路?” 一聲冷哼從身側(cè)響起或油,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驰唬,沒想到半個月后顶岸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡叫编,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年辖佣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搓逾。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡卷谈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霞篡,到底是詐尸還是另有隱情世蔗,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布朗兵,位于F島的核電站污淋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏余掖。R本人自食惡果不足惜寸爆,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赁豆,春花似錦仅醇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至务嫡,卻和暖如春甲抖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背心铃。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工准谚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人去扣。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓柱衔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親愉棱。 傳聞我的和親對象是個殘疾皇子唆铐,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容