一.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事件傳遞
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 —— 上面兩種情況的嵌套
2.攔截方法: 外部攔截 內(nèi)部攔截
外部攔截:
在這里揭措,首先down事件父容器必須返回false ,因為若是返回true刻蚯,也就是攔截了down事件绊含,那么后續(xù)的move和up事件就都會傳遞給父容器,子元素就沒有機會處理事件了炊汹。
其次是up事件也返回了false躬充,一是因為up事件對父容器沒什么意義,其次是因為若事件是子元素處理的讨便,卻沒有收到up事件會讓子元素的onClick事件無法觸發(fā)充甚。
內(nèi)部攔截:
修改父類的攔截方法:
總結(jié):外部優(yōu)先于內(nèi)部攔截