View滚动
view的移动动有两个api方法:
1、scrollTo(int targetX,int targetY)——移动到坐标点(targetX,targetY)处;
2、scrollBy(int deltaX,int deltaY)——在x轴上移动deltaX距离,在y轴上移动deltaY距离。实际上是通过当前位置和deltaX、deltaY,计算出最终位置,调用scrollTo()方法;
scrollTo()源码如下:
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();
}
}
}
逻辑就是,记录最终位置,调用postInvalidateOnAnimation()请求重绘,然后就会回调draw()方法,draw()方法部分代码如下:
。。。。。。
。。。。。。
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
。。。。。。
。。。。。。
draw()方法中使用刚才存储的“最终位置”,计算出上下左右坐标值,在“最终位置”绘制view,达到移动的效果。
但是这样的移动是瞬时完成的,没有“滚动”效果,所以ScrollView、ListView等肯定有另一种机制,这种机制就是Scroller类。
Scroller
Scroller的源码非常简单,这个类只做两件事:计算坐标值、存储坐标值。在View类中,通过Scroller实例,拿到坐标值,在draw()方法中进行“移动”。
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;
}
// 根据设置的滑动时间、当前已经滑动的时间,计算接下来应该滑动多少距离
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
// 已经滑动的时间
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
// 通过时间,计算应该滑动的距离,计算出接下来的目标位置
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
final float t = (float) timePassed / mDuration;
。。。。。。
。。。。。。
。。。。。。
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
ScrollView中使用了Scroller的子类,滑动逻辑也比较复杂,就不以ScrollView源码来分析了,下面一个自定义View来分析、解读Scroller的用法。
public class MyView extends LinearLayout {
private Scroller mScroller;
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mScroller = new Scroller(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
public MyView(Context context) {
super(context);
mScroller = new Scroller(context);
}
@Override
// 为了更简单地说明Scroller类的用法,设定为触摸View时就向上滑动100距离,滑动时间为10000毫秒
public boolean onTouchEvent(MotionEvent ev) {
// getScrollY()是View类的方法,返回View当前的y坐标
mScroller.startScroll(0, getScrollY(), 0, 100, 10000);
// 进行重绘,回调draw()方法,draw()方法中会调用computeScroll()方法
invalidate();
return super.onTouchEvent(ev);
}
@Override
public void computeScroll() {
// 调用computeScrollOffset()计算已经滑动的时间、接下来该滑动的距离等等
if (mScroller.computeScrollOffset()) {
// 执行移动操作。getCurrX()就是在computeScrollOffset()中计算出来的
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// 继续请求重绘,再次回调computeScroll()方法,循环此过程,直到滑动时间10000毫秒用完,mScroller.computeScrollOffset()返回false,跳出循环
postInvalidate();
}
}
GestureDetector
先贴上使用方法:
public class MyView extends LinearLayout {
private GestureDetector mGesture;
// 还有OnDoubleTapListener、SimpleGestureListener等监听器
private GestureDetector.OnGestureListener mGesListener = new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
// 手指按下时触发
return false;
}
@Override
public void onShowPress(MotionEvent e) {
// 手指按下、没有松开时触发
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// 手指按下、快速松开时触发
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// 手指按下、滑动时触发
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// 长按时触发
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 快速滑动时触发
return false;
}
};
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mGesture = new GestureDetector(context,mGesListener);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mGesture = new GestureDetector(context,mGesListener);
}
public MyView(Context context) {
super(context);
mGesture = new GestureDetector(context,mGesListener);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mGesture.onTouchEvent(ev);
// 一定要返回true,GesListener里的方法才会全部回调
return true;
}
}
在onTouchEvent()方法中,让GestureDetector接管MotionEvent事件,GestureDetector通过分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件,判断出用户的滑动动作,从而回调相应的方法。
总结
1、自己在onTouchEvent()方法里对动作、时间进行计算,也可以判断出用户的动作,一般简单一点的动作就直接在onTouchEvent()方法里自己判断,复杂一点的动作就通过GestureDetector来判断;
2、Scroller一般和GestureDetector结合使用,在onScroll()、onFling()方法中进行控件移动操作;
3、Scroller类中mStartX、mCurrX、mFinalX分别是移动开始时的坐标、移动过程中的实时坐标(移动是耗时过程)、移动目标位置的坐标;
4、View类中的getScrollX()方法返回值是mScrollX,而mScrollX在scrollTo(int x ,int y)方法中直接赋值mScrollX=y,因为scrollTo()是瞬间完成的,所以getScrollX()得到的总是View的当前坐标;