Android開發藝術探索讀書筆記(第三章)
嘗試使用Markdown語法編寫
View的滑動三種方法
- 使用View自身提供的 scrollTo/scrollBy方法
- 通過動畫給View平移效果
- 通過改變View的LayoutParams使得View重新佈局
scrollTo/scrollBy方法
只能改變內容位置,而不能改變View的佈局位置 。
!注意
原始狀態mScrollY 與mScrollX 都是0,然後View左滑動(View的左邊緣在父控件的左邊緣的右邊),則mScrollX 爲正值,同理右滑動mScrollX 爲負值。View上滑動(View的上邊緣在父控件的上邊緣上邊)mScrollY 則爲正,同理,View下滑,mScrollY 爲負。
總結:左正右負、上正下負
使用動畫
使用傳統屬性動畫,Android3.0以上版本能夠真正將控件移動(而3.0以下版本,則只能將View的影像移動,需要通過0一些小手段:在新的位置預先設置一個相同的控件,然後當木目標控件動畫結束時候,將目標控件隱藏,同時把預設的控件顯示出來 )
改變佈局參數
例子:將如想把某個控件往右移動100px,則在修改該控件的marginLeft參數值即可,另一種方法則是,在該控件左邊放置一個空View,然後增加空View的寬度。
三個方法的對比?
- scrollTo/scrollBy實現 比較方便實現滑動效果不影響內部元素單擊事件。 只能滑動View的內容,而不能滑動View的本身。
- 動畫實現 3.0版本以上沒有明顯缺點,而3.0以下版本,則在View不需要交互的情況下使用比較合適。還有個優點就是能夠實現較爲複雜的動畫,其他方式很難或者不能做到。
- 改變佈局參數實現只是使用起來比較麻煩,適用需要交互的View。
彈性滑動
- 使用Scroller,內部保存了幾個參數(滑動起點:startX,startY;滑動距離:dx,dy;滑動時間:duartion),最後通過invalidate 方法導致View重繪,重繪後,在View的draw方法中會調用computeScroll,而computeScroll 會去Scroller獲取當前的scrollX 和scrollY,最後通過scrollTo 方法實現滑動,緊接着又調用postInvalidate方法進行第二次重繪,如此反覆。
總結:Scroller本身並不能實現View的滑動,它配合了View的computeScroll方法,不斷讓View重繪,而每次重繪距離滑動起始時間有一段時間間隔,間隔裏,Scroller得出了View當前的滑動位置,再通過scrollTo方法進行小幅度滑動,反覆進行。
View的事件分發機制
點擊事件傳遞規則
**ViewGroup中的三個重要方法**- dispatchTouchEvent 進行事件分發
- onInterceptTouchEvent 內部調用,判斷是否攔截某個事件(View中沒有這個方法)
- onTouchEvent dispatchTouchEvent方法中的調用,處理點擊事件,返回結果是否消耗當前事件
dispatchTouchEvent 邏輯概述:先調用onInterceptTouchEvent 方法判斷是否攔截當前事件,若攔截,則調用onTouchEvent 判斷是否消耗當前時間;若不攔截,則調用子控件的dispatchTouchEvent 方法
補充:如果View需要處理事件,又設置了onClickListener,那麼其中的onTouch 方法會被回調,返回false,則當前View的onTouchEvent 方法仍會被調用,返回true,那麼onTouchEvent方法則不會被調用。由此可見,onTouchListener 優先級比onTouchEvent 高,另外,如果onTouchListener 中設置有onClickListener ,則onClick 方法會被調用。
優先級: onTouchListener > onTouchEvent > onClickListener
**重要結論** 1. 一個事件序列只能被一個View攔截並且消耗,但可以通過特殊手段做到讓兩個View同時處理,在一個View中通過*onTouchEvent* 強行傳遞給其他View處理 2. 某個View攔截之後,它的*onInterceptTouchEvent* 就不會再被調用,所以一旦攔截,該事件序列都只能由攔截它的控件調用了 3. 某個View如果不消耗*ACTION_DOWN* 事件,則同一事件序列的其他事件都不會再交給它處理,將重新將事件交給它的父元素處理 4. 某個View如果不消耗除了*ACTION_DOWN* 之外的事件,那麼,該點擊事件將會無效,後續的事件還是會接收,並且父元素的*onTouch* 方法也不會被調用,最後,事件會傳遞給Activity處理 5. ViewGroup默認不會攔截任何事件 6. View沒有*onInterceptTouchEvent* 方法 7. View的*onTouchEvent* 默認會消耗事件(返回true),除非該View不可點擊(clickable與longClickable同時爲false) 8. View的enable屬性不影響*onTouchEvent* 默認返回值,兩者無關 9. *onClick* 被調用的前提是,View可點擊,並且收到down、up的事件 10. 事件傳遞過程是由外向內的,先傳給父元素、再由父元素分發給子View,通過*requestDisallowInterceptTouchEvent* 可以在子元素中敢於父元素的事件分發過程(*ACTION_DOWN除外*)View的滑動衝突
只要界面中內外兩層同時可以滑動,則會產生滑動衝突
**三種滑動衝突場景**- 外部滑動方向與內部滑動方向不一致
- 外部滑動方向與內部滑動方向一致
- 以上兩種情況的嵌套
外內不一致的場景
例子:ViewPager 與 Fragment 配合使用組成頁面滑動,Fragment 裏面有ListView ,然而這種情況是沒有滑動衝突的,因爲ViewPager 內部已經解決了滑動衝突。但如果是ScrollView 就要手動解決衝突了。
處理規則:判斷滑動是水平還是垂直的,滑動是水平時候,讓外部View攔截滑動事件,而滑動是垂直的時候則是讓內部View來攔截滑動事件。注意,斜向滑動時候,則根據水平滑動距離與垂直滑動距離來判斷,算距離大的
內外一致的場景
處理規則:該場景較爲特殊,一般需要從業務上找到突破點,明確什麼時候需要外部滑動,什麼時候需要內部滑動,制定相應的處理規則。
以上兩種情況的嵌套
處理規則:該場景更爲特殊,處理方式與內外一致的場景 相同,根據業務需求制定相應的處理規則。
解決方法
外部攔截法
點擊事件都經由父控件的攔截處理,若父控件需要則攔截,不需要則不攔截,比較符合點擊事件的分發機制,需要重寫父容器的onInterceptTouchEvent 方法,並在內部做相應的攔截。
內部攔截法
父控件不攔截任何事件,所有的事件都傳遞給子元素,若子元素需要則消耗掉該事件,否則交給父控件處理,該方法與Android事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent 方法才能正常工作,需要重寫子元素的dispatchTouchEvent 方法
注意:爲什麼父容器不能攔截ACTION_DOWN 事件,因爲攔截之後,所有事件都無法傳遞給子控件了。