View移动、Scroller、GestureDetector详解

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,而mScrollXscrollTo(int x ,int y)方法中直接赋值mScrollX=y,因为scrollTo()是瞬间完成的,所以getScrollX()得到的总是View的当前坐标;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值