ReboundScrollView 仿IOS 拖拽回弹

初衷:
其实github上有很多这种ScrollView的项目,但是不得不说功能太多太乱了,我就只是想要一个简单效果的ScrollView,另外监听下滑动距离而已,想想还是自己写了个。


这里先说下思路吧,如果不愿意看的朋友可以直接跳过这一步,看下面的代码:
Android 原生的ScrollView是不支持拉出屏幕外,并且也没有回弹效果的,用户友好度却不不太好,不知道为什么不那么设计。
我想做的事情正如上面所述:
1.希望能拉出屏幕外
2.松手后希望控件回弹

我的思路是对ScrollView的子View进行操作

所有View的滑动控制肯定都受着onTouchEvent控制,所以,理所应当的,我要关注的重点,也就是onTouchEvent这个方法。

回弹的效果,就牵涉到位置的计算,这里我的想法就用简单的TranslateAnimation来实现。

大体思路就是这样了,先把思路确定了,然后代码总是磨出来的。

贴代码:

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;

public class ReboundScrollView extends ScrollView {

    private static final float MOVE_DELAY = 0.3f;//当拉出屏幕时的拖拽系数
    private static final int ANIM_TIME = 300;//回弹耗时
    private static final int FLING = 2;//fling 系数

    private View childView;
    private boolean havaMoved;

    private Rect originalRect = new Rect();

    private float startY;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() > 0) {
            childView = getChildAt(0);
        }
    }

    @Override
    public void fling(int velocityY) {
        super.fling(velocityY / 2);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if (childView == null)
            return;

        originalRect.set(childView.getLeft(), childView.getTop(),
                childView.getRight(), childView.getBottom());
    }


    public ReboundScrollView(Context context, AttributeSet attrs,
                             int defStyle) {
        super(context, attrs, defStyle);
    }

    public ReboundScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ReboundScrollView(Context context) {
        super(context);
    }


    /**
     * 在触摸事件中, 处理上拉和下拉的逻辑
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (childView == null) {
            return super.dispatchTouchEvent(ev);
        }

        int action = ev.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getY();
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (!havaMoved)
                    break;

                TranslateAnimation anim = new TranslateAnimation(0, 0,
                        childView.getTop(), originalRect.top);
                anim.setDuration(ANIM_TIME);

                childView.startAnimation(anim);
                // 将标志位设回false
                havaMoved = false;
                resetViewLayout();

                break;
            case MotionEvent.ACTION_MOVE:

                float nowY = ev.getY();
                int deltaY = (int) (nowY - startY);
                int offset = (int) (deltaY * MOVE_DELAY);
                childView.layout(originalRect.left, originalRect.top + offset,
                        originalRect.right, originalRect.bottom + offset);

                havaMoved = true;

                break;
            default:
                break;
        }

        return super.dispatchTouchEvent(ev);
    }

    public void resetViewLayout() {
        childView.layout(originalRect.left, originalRect.top,
                originalRect.right, originalRect.bottom);
    }

}

把代码贴出来后,再来分析具体的实现:
首先是拉出屏幕,在MOVE的过程中,对于超出部分代码,我使用layout重置子View的位置。

第二个要实现的就是回弹了,拖出去总是要回来的:
这里我定义了一个Rect,在onLayout(boolean changed, int l, int t, int r, int b)方法中,记录下了ScrollView的初始位置,以便于重置回弹。


说了许多,看一下代码里的关键代码
MOVE的代码:

 float nowY = ev.getY();
                int deltaY = (int) (nowY - startY);
                int offset = (int) (deltaY * MOVE_DELAY);
                childView.layout(originalRect.left, originalRect.top + offset,
                        originalRect.right, originalRect.bottom + offset);

                havaMoved = true;

这是MotionEvent.ACTION_MOVE的时候要做的,对chlidView的位置重新设置也就是lauout方法,这是基于originalRect的值来的,设定了相应的滑动系数,不然感觉实在是太灵敏了。

回弹的代码:

 if (!havaMoved)
                    break;

                TranslateAnimation anim = new TranslateAnimation(0, 0,
                        childView.getTop(), originalRect.top);
                anim.setDuration(ANIM_TIME);

                childView.startAnimation(anim);
                // 将标志位设回false
                havaMoved = false;
                resetViewLayout();
...
...
  public void resetViewLayout() {
        childView.layout(originalRect.left, originalRect.top,
                originalRect.right, originalRect.bottom);
    }

当手指 MotionEvent.ACTION_UP或者MotionEvent.ACTION_CANCEL的时候把clildView置于原位,然后设置动画,这就是回弹了。


补充:
这是个非常简单的回弹ScrollView,需要注意的是,我这里没有对在屏幕内正常移动和屏幕外移动作区分,那样就需要判断当前的移动是否需要我们重写了,也就是是否处于上拉,下拉的部分。没有做判断,我现在的代码就会导致你手势移动的距离和控件滚动的距离是不一样的。我是感觉如果你正常使用是不会注意到这一点的。但是不排除有这种需求。

下拉其实不太好判断,因为我这里操作的是ChildView,ScrollView在上拉过程中getScrollY()肯定是0,所以这一点,我也还没想好。不过上拉倒是比较简单。当childView.height <= ScrollView.height + getScrollY的时候就是上拉出屏幕的点了,这个应该能想通吧。

git地址:https://github.com/cjhandroid/ReboundScrollView
CSDN : http://download.csdn.net/detail/cjh_android/9574318

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值