Android 下拉回弹BounceScrollView,同时去除EdgeEffect

接上一篇文章,
ScrollView有了下拉和上拉的弹性回弹,这个时候发现有时候下拉的时候界面UI会变得很丑,不想让ScrollView下拉怎么办?
简单啊,只需要在下拉到顶部的时候判定一下,然后maxOverScrollY不改动的直接传给super.overScrollBy就可以了。

 @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
                                   int scrollY, int scrollRangeX, int scrollRangeY,
                                   int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        //增加阻尼效果,使滑动有吃力感
        double ratio = 1d;
        //在顶部并且是向下拖动
        if (deltaY < 0 && scrollY + deltaY < 0) {
            return super.overScrollBy(deltaX, (int) (deltaY * ratio), scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY,
                isTouchEvent);
        } else if (deltaY > 0 && scrollY + deltaY > scrollRangeY) { //滑动到底部并且向下滑动
            ratio = 1.05d + (scrollRangeY - scrollY) / (newMaxOverScrollY * 1.2);
        }
        return super.overScrollBy(deltaX, (int) (deltaY * ratio), scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, newMaxOverScrollY,
                isTouchEvent);
    }

但是这个时候有个问题,在顶部的时候下拉是不会向下拉了,但是在顶部会出现一个非常令人不爽的EdgeEffect的透明波纹阴影!我就是看这个阴影不爽,想去掉怎么办?好办啊,设置overScrollMode = OVER_SCROLL_NEVER就OK啦,这样就没阴影啦!别急着高兴,是不是觉得有哪些不对?没错,如果你这样设置了,你就会发现,好像不会滑动了?卧槽,那我之前的功夫不是白做了?!难道要我重新去重写onTouchEvent?!不行,这太可怕了。为了偷懒,我们还是看源码吧,感觉还是看源码方便一些XD。
要想了解设置overScrollMode = OVER_SCROLL_NEVER为什么就不能再越出边界的原因,就要从overScrollBy的源码看起,上代码!

protected boolean overScrollBy(int deltaX, int deltaY,
            int scrollX, int scrollY,
            int scrollRangeX, int scrollRangeY,
            int maxOverScrollX, int maxOverScrollY,
            boolean isTouchEvent) {
        final int overScrollMode = mOverScrollMode;
        final boolean canScrollHorizontal =
                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
        final boolean canScrollVertical =
                computeVerticalScrollRange() > computeVerticalScrollExtent();
        final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS ||
                (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
        /**看这里*/    
        'final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
                (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);'
        int newScrollX = scrollX + deltaX;
        if (!overScrollHorizontal) {
            maxOverScrollX = 0;
        }
        int newScrollY = scrollY + deltaY;
        if (!overScrollVertical) {
            maxOverScrollY = 0;
        }
        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;
        boolean clampedX = false;
        if (newScrollX > right) {
            newScrollX = right;
            clampedX = true;
        } else if (newScrollX < left) {
            newScrollX = left;
            clampedX = true;
        }
        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }
        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
        return clampedX || clampedY;
    }

为什么设置OVER_SCROLL_NEVER就不能越界了,关键就在这里final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
(overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
,设置了overScrollMode = OVER_SCROLL_NEVER,也就代表着overScrollVertical = false,然后maxOverScrollY = 0;, 也即不能再越界了。弄明白了为什么OVER_SCROLL_NEVER就不能越界的原因了,而且也看到了overScrollBy的源码了,下面就好办了,自己写一个overScrollBy方法不就可以了吗。没错就这么简单。再BounceScrollView中再加一个函数:

     /**
     * 这里在ScrollView中就不用关心横向滑动问题,可以把原来横向逻辑的代码统统删掉,
     * 并且设置默认是可以竖向滑动的
     */
    @SuppressWarnings({"UnusedParameters"})
    protected boolean customOverScrollBy(int deltaX, int deltaY,
                                         int scrollX, int scrollY,
                                         int scrollRangeX, int scrollRangeY,
                                         int maxOverScrollX, int maxOverScrollY,
                                         boolean isTouchEvent) {
        int newScrollX = scrollX + deltaX;
        int newScrollY = scrollY + deltaY;
        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;
        boolean clampedX = false;
        if (newScrollX > right) {
            newScrollX = right;
            clampedX = true;
        } else if (newScrollX < left) {
            newScrollX = left;
            clampedX = true;
        }
        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }
        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
        return clampedX || clampedY;
    }

然后再修改一下之前的dispatchNestedFling的逻辑,最后完整的BounceScrollView代码奉上!

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.ScrollView;

public class BounceScrollView extends ScrollView {
    // 这个值控制可以把ScrollView包裹的控件拉出偏离顶部或底部的距离。
    private static final int MAX_OVER_SCROLL_Y = 100;
    private int newMaxOverScrollY;

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

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

    public BounceScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        float density = metrics.density;
        newMaxOverScrollY = (int) (density * MAX_OVER_SCROLL_Y);
        //false:隐藏ScrollView的滚动条。
        this.setVerticalScrollBarEnabled(false);
        //去掉拉到底部或者顶部的半透明波纹阴影效果。
        this.setOverScrollMode(ScrollView.OVER_SCROLL_NEVER);
    }

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
                                   int scrollY, int scrollRangeX, int scrollRangeY,
                                   int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        //增加阻尼效果,使滑动有吃力感
        double ratio = 1d;
        if (deltaY > 0 && scrollY + deltaY > scrollRangeY) { //滑动到底部并且向下滑动,使用自己的customOverScrollBy
            ratio = 1.05d + (scrollRangeY - scrollY) / (newMaxOverScrollY * 1.2);
            return customOverScrollBy(deltaX, (int) (deltaY * ratio), scrollX, scrollY,
                    scrollRangeX, scrollRangeY, maxOverScrollX, newMaxOverScrollY, isTouchEvent);
        }
        //其他情况还是直接走super的流程
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        // Not consumed means it wasn't handled because ScrollView
        // doesn't take over scrolling bounds into scroll range,
        // so we fling it ourselves to get it bounce back
        if (!consumed) {
            fling((int) velocityY);
            return true;
        } else {
            return super.dispatchNestedFling(velocityX, velocityY, consumed);
        }
    }

    /**
     * 这里在ScrollView中就不用关心横向滑动问题,可以把原来横向逻辑的代码统统删掉,并且设置默认是可以竖向滑动的
     */
    @SuppressWarnings({"UnusedParameters"})
    protected boolean customOverScrollBy(int deltaX, int deltaY,
                                         int scrollX, int scrollY,
                                         int scrollRangeX, int scrollRangeY,
                                         int maxOverScrollX, int maxOverScrollY,
                                         boolean isTouchEvent) {
        int newScrollX = scrollX + deltaX;
        int newScrollY = scrollY + deltaY;
        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;
        boolean clampedX = false;
        if (newScrollX > right) {
            newScrollX = right;
            clampedX = true;
        } else if (newScrollX < left) {
            newScrollX = left;
            clampedX = true;
        }
        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }
        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
        return clampedX || clampedY;
    }
}

其实你还可以自己,定义两个变量mCanBounceTop,mCanBounceBottom来控制是否能在顶部和底部实现弹性回缩效果,有兴趣的可以自己实现下。~

你可能感兴趣的:(android学习)