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本身提供的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动画,也可以使用属性动画
使用方式:
使用属性动画将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
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会被调用。
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)
}