View 滑动的实现

View 滑动实现

复习一下view滑动的几种实现方式

1.通过layout实现

通过不断重新layout view 达到滑动的效果。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 记录触摸点坐标
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                // 计算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                // 在当前left、top、right、bottom的基础上加上偏移量
                layout(getLeft() + offsetX,getTop() + offsetY,getRight() + offsetX,getBottom() + offsetY);

                //与上面的方法是等效,直接设置偏移量
                //offsetLeftAndRight(offsetX);
                //offsetTopAndBottom(offsetY);
                break;
        }
        return true;
    }

这里需要注意,我们使用的getX 和getY 方法,如果使用 getRawX 和getRawY 方法,那么代码就要做些许改变。就是在重新layout后,我们需要重新赋值 lastX和lastY的值。 否则就会出现意想不到的结果哦。 有兴趣的朋友可以试一试,可以加深对于这两个方法的认识。

2.通过改变LayoutParams

layoutparams 保存了view的布局参数,我们可以通过不断修改其参数,重新设置,达到滑动效果。需要修改的参数就是自己与父view的边距。注意通过getLayotParams 方法获取layoutparams时类型要根据其父view的类型来确定。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 记录触摸点坐标
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                // 计算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                // 转换成这个类型,就不用关心父view的类型了
                ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
                //LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);
                break;
        }
        return true;
    }

3.通过view自己的scrollTo和scrollBy 来实现

srollTo(x,y) 表示移动到位置(x,y)处。
srollBy(dx,dy) 表示在当前位置的基础上移动 dx和dy的距离。dx和dy为偏移量。

 public void scrollBy(int x, int y) {  
       scrollTo(mScrollX + x, mScrollY + y);  
   }  

同时需要注意,他们移动的都是view自己的内容。对应 viewgroup移动的就是自己的子view。所以父viewgroup中的素有view都会一起移动哦。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //这里要注意,offsetx为我们相对于父viewgroup移动的距离,但是,这里我们调用的是让父viewgroup移动的方法,所以参考系,相对于反转了,所以要在前面加上负号。
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
        }
        return true;
    }

4.scroller

与上面的不同,scroller主要是用于当没有用户手动操作的时候,我们用来连续移动 view 的方式。需要配合view 的computeScroll 和scrollTo方法配合使用。该方法会在view 的draw方法中被调用。

    @Override
    public void computeScroll() {
        super.computeScroll();
        // 判断Scroller是否执行完毕
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            // 通过重绘来不断调用computeScroll
            invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
            case MotionEvent.ACTION_UP:
                // 手指离开时,执行滑动过程
                View viewGroup = ((View) getParent());
                mScroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY());
                invalidate();
                break;
        }
        return true;
    }

流程如下:

流程图

说的通俗一点就是:scroller 每次提供一个值 mScroller.getCurrX(),然后view scrollTo这个位置,然后view 重新绘制,然后又调用 computeScroll 方法,然后scroler 又提供一个值mScroller.getCurrX() 又重绘。 相当于一个死循环,不停地移动。 循环的出口就是mScroller.computeScrollOffset() 方法。 这个方法有两个作用,一个是返回是否滑动到了指定的位置,二是每次调用时会不断的改变 mScroller.getCurrX()的值,让这个越来越接近滑动结束的值,知道达到滑动结束的值时,该方法的返回值也就变成了false。

看看scroller的部分代码,首先是其构造函数,其实也就是初始化了一些滑动的位置和时间值:

    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);  
    }  

然后是其 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;  
}  

5.ViewDragHelper

ViewDragHelperv4包中Google 为我们封装了Scroller的一个工具类。为我们实现不同的滑动等效果提供的一种方案。
其一般使用在ViewGroup中,通过其工厂方法创建其实例,通过其实例来处理View的事件和移动。 相当于我们把View(子View)都交给她来处理了。

  • 首先当然是先创建其实例对象
mViewDragHelper = ViewDragHelper.create(this, callback);
  • ViewGroup的事件交给mViewDragHelper处理
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }
  • 因为同样是 通过Scroller实现滑动的,所以我们也需要重写computeScroll方法
    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

这些也都是固定写法了。

  • 最为关键的就是 在创建mViewDragHelper时我们传入的callback对象,这个是我们要自己去实现的。通过这个回调,我们可以实现我们自己需要的滑动效果。
    private ViewDragHelper.Callback callback =
            new ViewDragHelper.Callback() {

                //是否处理 该view也就是参数 child。返回 true 处理,否则这忽略噶子view。
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    return mchild == child;
                }

                // 触摸到View后回调
                @Override
                public void onViewCaptured(View capturedChild,
                                           int activePointerId) {
                    super.onViewCaptured(capturedChild, activePointerId);
                }

                // 当拖拽状态改变时回调 如:STATE_IDLE、STATE_DRAGGING、STATE_SETTLING
                @Override
                public void onViewDragStateChanged(int state) {
                    super.onViewDragStateChanged(state);
                }

                // 当位置改变的时候调用,常用与滑动时更改scale等
                @Override
                public void onViewPositionChanged(View changedView,
                                                  int left, int top, int dx, int dy) {
                    super.onViewPositionChanged(changedView, left, top, dx, dy);
                }

                // 处理垂直滑动
                @Override
                public int clampViewPositionVertical(View child, int top, int dy) {
                    return 0;
                }

                // 处理水平滑动
                @Override
                public int clampViewPositionHorizontal(View child, int left, int dx) {
                    return left;
                }

                // 拖动结束后调用
                @Override
                public void onViewReleased(View releasedChild, float xvel, float yvel) {
                    super.onViewReleased(releasedChild, xvel, yvel);
                    //手指抬起后缓慢移动到指定位置
                    //相当于Scroller的startScroll方法
                     mViewDragHelper.smoothSlideViewTo(mchild, 0, 0);
                     ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                }
            };

这样,我们就完成了想要的滑动了。之后,会分析一下它的源码,看看到底如何实现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值