view 事件體系
什麼是view
繼承於object.view group繼承與view 兩者相互嵌套
view的位置確定
view 的位置有四個屬性確定top,left,right,bottom.(top,left) (bottom,right)分別是view的左上角右下角座標,Android系統x軸座標向右y軸座標正方向向小,大部分系統試用
固定位置之外還有x,y(前位置左上角),translationX,translationY(位移距離)
來輔助作確定控件移動時的位置
x = translationX + left
y = translationY + top
view事件
motionEvent 手勢事件
view處理觸摸事件,view的父view沒有消費掉事件把事件傳給view可以通過觸摸監聽獲取事件
手勢事件內容包括觸摸點,觸摸位置,動作,速度,手勢
觸摸點,觸摸位置,動作
//獲取觸摸位置當前view 的左上角原點
event.getX();
event.getY();
//獲取觸摸位置屏幕的左上角原點
event.getRawX();
event.getRawY();
//獲取出發事件的觸摸點
int index = event.getActionIndex();
mActivePointerId = event.getPointerId(index);
//獲取觸摸點的其他事件
index = event.findPointerIndex(mActivePointerId);
int secondaryIndex = MotionEventCompat.findPointerIndex(event,mSecondaryPointerId);
final float x = MotionEventCompat.getX(event,index);
final float y = MotionEventCompat.getY(event,index);
public boolean onTouchEvent(MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case MotionEvent.ACTION_DOWN:
int index = event.getActionIndex();
mActivePointerId = event.getPointerId(index);
mPrimaryLastX = MotionEventCompat.getX(event,index);
mPrimaryLastY = MotionEventCompat.getY(event,index);
break;
case MotionEvent.ACTION_POINTER_DOWN:
//觸發時存在其他觸摸點
index = event.getActionIndex();
mSecondaryPointerId = event.getPointerId(index);
mSecondaryLastX = event.getX(index);
mSecondaryLastY = event.getY(index);
break;
case MotionEvent.ACTION_MOVE:
index = event.findPointerIndex(mActivePointerId);
int secondaryIndex = MotionEventCompat.findPointerIndex(event,mSecondaryPointerId);
final float x = MotionEventCompat.getX(event,index);
final float y = MotionEventCompat.getY(event,index);
final float secondX = MotionEventCompat.getX(event,secondaryIndex);
final float secondY = MotionEventCompat.getY(event,secondaryIndex);
break;
case MotionEvent.ACTION_POINTER_UP:
xxxxxx(涉及pointer id的轉換,之後的文章會講解)
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//觸摸點移動到view之外,事件被父view消費時會觸發
mActivePointerId = INVALID_ID;
mPrimaryLastX =-1;
mPrimaryLastY = -1;
break;
}
return true;
}
觸摸移動存在臨界值判斷小於最小移動距離touchSlop的不認爲移動,來排除手指自然抖動ViewConfiguration.get(getContext()).getScaledTouchSlop()
速度,手勢
//獲得速度追蹤
VelocityTracker velocityTracker = VelocityTracker.obtain();
//配置追蹤事件
velocityTracker.addMovement(motionEvent);
//配置時間段
velocityTracker.computeCurrentVelocity(1000);
int xV = (int) velocityTracker.getXVelocity();
int yV = (int) velocityTracker.getYVelocity();
Log.d("MainActivity","xV +"+xV+ "yV +"+yV);
//重置
velocityTracker.clear();
velocityTracker.recycle();
GestureDetector
onTouch 可以判斷手勢GestureDetector是在ontouch上面的封裝識別複雜動作
具體GestureDetector
Scroller
view滾動時通過scrollTo/scrollBy實現快速滾動到位置內容移動 view位置不會移動
郭霖scroller
/**
* Created by iuzuan on 17/3/16.
*/
public class ScrollerLayout extends ViewGroup {
//滾動實例
private Scroller mScroller;
private int mTouchSlop;
// 手機按下的座標
private float mXDown;
// 手機移動時座標
private float mXMove;
// 上次移動時座標
private float mXLastMove;
//界面可滾動的左邊界
private int leftBorder;
//界面可滾動的右邊界
private int rightBorder;
public ScrollerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// 創建scroller實例
mScroller = new Scroller(context);
ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
}
//view大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for(int i = 0 ; i< childCount;i++){
View childView = getChildAt(i);
//爲ScrollerLayout中的每一個子控件測量大小
// 設置子view的大小,爲一個view的大小
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
//view位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 爲ScrollerLayout中的每一個子控件在水平方向上進行佈局
// 子view分別是在00wh,w02wh就是h高度內橫向排列
childView.layout(i * childView.getMeasuredWidth(), 0,
(i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
// 初始化左右邊界值
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
mXDown = ev.getRawX();
mXLastMove= mXDown;
break;
case MotionEvent.ACTION_MOVE:
mXMove = ev.getRawX();
float diff = Math.abs(mXMove -mXDown);
mXLastMove = mXMove;
// 當手指拖動值大於TouchSlop值時,認爲應該進行滾動,攔截子控件的事件
if(diff>mTouchSlop){
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mXMove = event.getRawX();
int scrolledX = (int) (mXLastMove - mXMove);
if (getScrollX() + scrolledX < leftBorder) {
scrollTo(leftBorder, 0);
return true;
} else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
scrollTo(rightBorder - getWidth(), 0);
return true;
}
scrollBy(scrolledX, 0);
mXLastMove = mXMove;
break;
case MotionEvent.ACTION_UP:
// 當手指擡起時,根據當前的滾動值來判定應該滾動到哪個子控件的界面
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
int dx = targetIndex * getWidth() - getScrollX();
// 第二步,調用startScroll()方法來初始化滾動數據並刷新界面
// getScrollX+dx來移動到目標位置
mScroller.startScroll(getScrollX(), 0, dx, 0);
invalidate();
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
// 第三步,重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
}
view移動
使用scrollTo/By
scrollBy對參數處理之後交給scrollTo處理
view 邊緣指由兩點座標確定的位置,view內容邊緣指view裏面的內容,在滑動過程中
mScrollX = view左邊緣和view內容的左邊緣水平距離 mScrollX 一樣
使用動畫
改變佈局
改變自己的 marginLeft 或者改旁邊的view來移動目標 view
view 事件的分發機制
事件傳遞規則
事件傳遞由三個方法控制僞代碼瞭解大概過程
//返回值代表 view 及其子 view 是否消耗事件
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
//view 是否處理事件,還是交給子 view 處理事件
if(onInterceptTouchEvent(ev)){
//事件處理後是否消耗可以理解爲是否處理完成事件
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
view 處理過程中涉及 onTouch() 和 onTouchEvent()兩者的區別
onTouch()是 OnTouchListener() 接口的方法需要外部實現set 給 view 如果返回 false 則 view 自身內部實現的 onTouchEvent()被調用,之後纔是 OnClickListener中的 onClick 再被調用
1、 自己消費,終結傳遞。——->return true ;
2、 給自己的onTouchEvent處理——-> 調用super.dispatchTouchEvent()系統默認會去調用 onInterceptTouchEvent,在onInterceptTouchEvent return true就會去把事件分給自己的onTouchEvent處理。
3、 傳給子View——>調用super.dispatchTouchEvent()默認實現會去調用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就會把事件傳給子類。
4、 不傳給子View,事件終止往下傳遞,事件開始回溯,從父View的onTouchEvent開始事件從下到上回歸執行每個控件的onTouchEvent——->return false;
注: 由於View沒有子View所以不需要onInterceptTouchEvent 來控件是否把事件傳遞給子View還是攔截,所以View的事件分發調用super.dispatchTouchEvent()的時候默認把事件傳給自己的onTouchEvent處理(相當於攔截),對比ViewGroup的dispatchTouchEvent 事件分發,View的事件分發沒有上面提到的4個目標的第3點。
action_down 找到消費者之後所有 move up 都直接傳遞到消費者 不在執行onInterceptTouchEvent
onInterceptTouchEvent只攔截不消費事件
view 只消費 down 事件其他事件返回 false則事件消失不在由上級處理
viewGroup 默認不攔截事件 只有 viewGroup 有攔截器
view 沒有下層 view不用判定是否攔截 ontouch 返回 ture 時 onTouchEvent 不會被調用否則調用 view 的 clickable||longClickable爲真返回 ture
如果在onInterceptTouchEvent返回true,onInterceptTouchEvent方法中將不會收到後續的任何事件,目標子控件中除了ACTION_CANCEL外也不會接收所有這些後續事件,所有的後續事件將會被交付到你自己的onTouchEvent()方法中。
滑動衝突解決
- 外部滑動方向和內部方向不一致
- 外部和內部方向一致
- 以上兩種嵌套
第一種場景
判斷滑動動作屬於上下滑動還是左右滑動可以根據動作路徑和水平線夾角,速度差,滑動方向的距離來判斷這些都是滑動規則
解決方案:
場景二
只能根據業務來對 view 響應來做出處理