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 响应来做出处理