1.View的位置参数
2.MotionEvent 手指触摸屏幕后的一系列事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP
3.TouchSlop 系统所能识别的被认为是滑动的最小距离,获取方式为:
ViewConfiguration.get(getContext()).getScaledTouchSlop()
4.VelocityTracker速度追踪 VelocityTracker的使用方式
//初始化
VelocityTracker mVelocityTracker = VelocityTracker.obtain();
//在onTouchEvent方法中
mVelocityTracker.addMovement(event);
//获取速度
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
//重置和回收
mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的时候调用
mVelocityTracker.recycle(); //一般在onDetachedFromWindow中调用
5.GestureDetector GestureDetector用于辅助检测用户的单击、滑动、长按、双击等行为 使用方法:
先创建GestureDetector 并实现onGestureListener接口,需要时还可以实现OnDoubleTapListener实现双击行为
GestureDetector mGestureDetector = new GestureDetector(this);
//解决长按屏幕后无法拖动现象
mGestureDetector .setIsLongpressEnabled(false);
然后接管view的onTouchEvent方法,添加如下实现
return mGestureDetector.onTouchEvent(event);
做完以上步骤就可以有选择的实现onGestureListener和OnDoubleTapListener的方法(具体实现这里不详细说明) 建议:如果只是监听滑动相关的事件在onTouchEvent中实现;如果要监听双击这种行为的话,那么就使用GestureDetector。
6.Scroller 当使用View的scrollBy和scrollTo的时候,过程是瞬间完成的,用户体验差。因此,可以使用scroller来实现有过渡效果的滑动 使用方法
Scroller scroller = new Scroller(getContext());
//缓慢移动到指定位置
private void smoothScrollBy(int dx, int dy) {
int delta = dx - getScrollX();
//500ms内划向dx
scroller.startScroll(getScrollX(), 0, delta, 0, 500);
invalidate();
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
7.View的滑动 (1)使用scrollTo/scrollBy
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
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();
}
}
}
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
可以看出,scrollBy其实调用了scrollTo函数。scrollTo滑动到绝对位置,scrollBy是相对位置。两者只能改变view内容的位置,不能改变在布局中的位置。 (2)使用动画 网上例子很多,不再说明 (3)改变布局参数 比如让一个button向右平移100px可以用如下代码
MarginLayoutParams params = (MarginLayoutParams)button.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
button.requestLayout();
//或者button。setLayoutParams(params)
(4)各种滑动方式对比 使用scrollTo/scrollBy:操作简单,适合对View内容的滑动 动画:操作简单,主要适合于没有交互的view和实现复杂的动画效果 改变布局参数:操作稍微复杂,适用于有交互的view
8.弹性滑动 (1)使用scroller 使用方法之前已经讲过,现在看一下如何实现的。主要是startScroll和computeScrollOffset两个函数
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
// This controls the viscous fluid effect (how much of it)
mViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid())
mViscousFluidNormalize = 1.0f;
mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
}
在这个方法中我们只看到了对一些滚动的基本设置动作,比如设置滚动模式,开始时间,持续时间等等,并没有任何对View的滚动操作 其实整个过程是这样的:View重绘后会在draw方法中调用computeScroll,这是个空方法,要自己实现
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
在我们自己实现的方法中先调用scrollTo再调用postInvalidate进行第二次重绘,然后调用draw函数,这样如此反复知道滑动结束 再看一下computeScrollOffset方法
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = (float)timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
float timePassedSeconds = timePassed / 1000.0f;
float distance = (mVelocity * timePassedSeconds)
- (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
mCurrX = mStartX + Math.round(distance * mCoeffX);
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distance * mCoeffY);
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
它根据时间的流逝来计算当前的scrollX和scrollY的值 (2)动画 (3)延时策略 通过handler的sendEmptyMessageDelayed方法
8.事件分发机制 具体解释我之前已经讲过了,地址在这里 http://blog.csdn.net/lxj1137800599/article/details/51034566
9.滑动冲突 解决方法 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: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (父容器需要拦截当前点击事件的条件) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
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: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要点击事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
当然,不能拦截父容器的ACTION_DOWN事件
public boolean onInterceptTouchEvent(MotionEvent ev){
if(ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}
为什么不能拦截呢?因为拦截了ACTION_DOWN,会导致所有时间都无法传递到子元素
了解到了这里,我想来解决一下半年前的困惑 http://blog.csdn.net/lxj1137800599/article/details/51007062 看该文的最后,scrollview和listview的滑动冲突 我决定采用外部拦截法。因此,我需要自定义MyScrollView来继承ScrollView并重写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:
Log.e("ACTION_DOWN", "ACTION_DOWN");
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
Log.e("ACTION_MOVE", "ACTION_MOVE");
//getChildAt(0)得到的就是listview
if (y > getChildAt(0).getBottom()) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
Log.e("ACTION_UP", "ACTION_UP");
intercepted = false;
break;
}
Log.e("intercepted", "" + intercepted);
return intercepted;
}
这样,完美解决 代码地址:http://download.csdn.net/detail/lxj1137800599/9611947