一、View基礎知識
1、view的位置參數
view的位置由四個頂點決定的,分別是對應view的四個屬性:
top:左上角縱座標
left:左上角橫座標
right:右下角橫座標
bottom:右下角縱座標
android中的X軸和Y軸的正方向分別是右邊和下邊
從android3.0開始,View增加了幾個額外的參數:x、y、translationX、translationY
x和y是View左上角的座標
translationX、translationY是View左上角相對於父容器的偏移量
2、MotionEvent和TouchSlop
(1)MotionEvent在手指接觸屏幕後產生的一系列事件中,有如下幾種:
ACTION_DOWN:手指剛接觸屏幕
ACTION_MOVE:手指在屏幕上移動
ACTION_UP:手指從屏幕上擡起的一瞬間
通過getX和getY我們可以獲取x和y軸的座標
通過getRowX和getRowY我們可以獲取相對於手機屏幕左上角的x和y座標
(2)TouchSlop是系統所能辨別的滑動的最小距離
通過ViewConfiguration.get(context).getScaledTouchSlop()方法獲取這個常量
3、GestureDetector和Scroller
(1)GestureDetector:手勢檢測,用於檢測用戶的單擊、滑動、長按、雙擊的行爲
使用方式
創建GestureDetector對象並實現OnGesturelistener接口
OnGesturelistener接口中的方法
onDown:手指觸摸屏幕的一瞬間,由1個ACTION_DOWN觸發
onShowPress:手指觸摸屏幕尚未鬆開或拖動,由1個ACTION_DOWN觸發
onSingleTapUp:手指觸摸屏幕鬆開,由1個ACTION_UP觸發,單擊事件
onScroll:手指按下屏幕並拖動,由1個ACTION_DOWN,多個ACTION_MOVE觸發,拖動事件
onLongPress:用於長按屏幕
onFling:用戶按下屏幕、快速滑動後鬆開,由1個ACTION_DOWN,多個ACTION_MOVE和1個ACTION_UP觸發,快速滑動事件
(2)Scroller:彈性滑動對象,用於實現View的彈性滑動
使用方式:
Scroller scroller = new Scroller(context)
//緩慢滑動到指定位置
private void smoothScrollTo(int destX,int destY){
int scrollX = getScrollX()
int delta = destX - scrollX
scroller.startScroll(scrollX,0,delta,0,1000)
invalidate()
}
@Override
public void computeScroll(){
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY())
postInvalidate()
}
}
二:View的滑動
通過三種方式可以實現View的滑動:
第一種通過View本身提供的scrollTo/scrollBy方法實現滑動
第二種通過動畫給View添加平移效果實現滑動
第三種通過改變View的LayoutParams使得View重新佈局實現滑動
1、使用scrollTo/scrollBy方法實現滑動
我們先來看看源碼
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
從源碼可以看出,scrollBy也是調用了scrollTo方法,他實現了基於當前位置的相對滑動,scrollTo實現了基於所傳遞參數的絕對滑動
mScrollX和mScrollY的單位爲像素,從左向右滑動,mScrollX爲負值,反之爲正值,如果從上往下滑動,那麼mScrollY爲負值,反之爲正值
2、使用動畫滑動
通過動畫讓一個View進行平移,主要操作View的translationX和translationY屬性,既可以採用傳統View動畫,也可以使用屬性動畫
使用方式:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:zAdjustment="normal">
<translate
android:duration="100"
android:fromXDelta="0"
android:formYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100"/>
</set>
使用屬性動畫將View在100ms內向右移動100像素
ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();
3、改變佈局參數
這種方式很簡單,直接上代碼了
MarginLayoutParams params = (MarginLayoutParams)textview.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
textview.setLayoutParams(params)
4、各種滑動方式的對比
scrollTo/scrollBy
特點:操作簡單,適合對View內容進行滑動。只能滑動View內容,不能滑動View本身
動畫方式
特點:操作簡單,適用於沒有交互的View和實現複雜的動畫效果
佈局方式
特點:操作略微複雜,適用於有交互的View
三:View的時間分發機制
1、點擊事件的傳遞規則
點擊事件分發過程:
dispatchTouchEvent:進行事件分發,如果事件能夠傳遞跟當前View,就會被調用,返回結果受當前View的onTouchEvent和下級的dispatchTouchEvent方法的影響,表示是否消費當前事件
onInterceptTouchEvent:用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列中,此方法不會被再次調用,返回結果表示是否攔截當前事件
onTouchEvent:在dispatchTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消費當前事件,不消費則在同一個事件序列中,當前View無法再次受到事件
2、點擊事件流程圖
點擊事件到達頂級View後,會調用ViewGroup的dispatchTouchEvent方法,如果頂級ViewGroup dispatchTouchEvent方法返回true,則事件由ViewGroup處理,這時如果ViewGroup的OnTouchListener被設置,則onTouch會被調用,否則onTouchEvent會被調用,同時設置,onTouch會屏蔽掉onTouchEvent。在onTouchEvent中,如果設置了OnClickListener,則onClick會被調用。如果頂級的ViewGroup不攔截事件,則事件會傳遞給他所在的點擊事件鏈上的子View,這時子View的dispatchTouchEvent會被調用。
四:View的滑動衝突
1、常見的滑動衝突
外部滑動方向和內部滑動方向不一致
外部滑動方向和內部滑動方向一致
上面兩種情況的嵌套
2、滑動衝突的處理規則
(1)外部和內部滑動不一致:當用戶左右滑動時,需要讓外部的View攔截事件,當用戶上下滑動時,讓內部View攔截事件(根據滑動是水平滑動還是豎直滑動來判斷是誰來攔截)
(2)外部和內部滑動方向一致:根據業務需要,當處於某種狀態時外部View滑動,而處於另一種狀態時需要內部View滑動
3、滑動衝突的解決方式
(1)外部攔截:事件通過父容器攔截處理,如果負容器需要此事件就攔截,不需要就不攔截。需要重寫onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent event){
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted = false
break;
case MotionEvent.ACTION_MOVE:
if(父容器需要當前點擊事件){
intercepted = true
}else{
intercepted = false
}
break;
case MotionEvent.ACTION_UP:
intercepted = fasle;
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
(2)內部攔截法:指父容器不攔截事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消費,否則就交給父容器處理。這種方式和android中的事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent方法,使用起來比外部攔截稍複雜,需要重寫子元素的dispatchTouchEvent方法。僞代碼如下:
public boolean dispatchTouchEvent(MotionEvent event){
int x = (int) event.getX();
int y = (int) event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true)
break;
case MotionEvent.ACTION_MOVE:
int deltax = x - mLastX;
int deltay = y - mLastY;
if(父容器需要此類點擊事件){
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = x
mLastY = y
return super.dispatchTouchEvent(event)
}