反弹效果的 ScrollView(FlexibleScrollView)

带有上下反弹效果的 scrollview,只是为了体验效果好一些,实际上用了这个控件后,复杂的滑动逻辑等可能会出现bug,但是简单布局用了后还挺不错的。

去除 scrollview 滑动到尽头时的阴影效果

android:overScrollMode=“never”

public class FlexibleScrollView extends ScrollView {

    private static final String TAG = "FLEXIBLESCROLLVIEW";
    
    // 移动因子,是一个百分比,比如手指移动了100px,那么view只移动50px,目的是达到一个延迟的效果。
    private static final float MOVE_FACTOR = 0.5f;
    
    // 手指松开时,界面回到原始位置动画所需的时间
    private static final int ANIM_TIME = 300;
    
    // ScrollView唯一的一个子view
    private View contentView;

	// 手指按下时的Y值,用于计算移动中的移动距离
    // 如果按下时不能上拉或者下拉,会在手指移动时更新为当前手指的Y值。
    private float startY;
    
    // 用于记录正常的布局位置
    private Rect originalRect = new Rect();

	// 记录手指按下时是否可以下拉
    private boolean canPullDown = false;
    
    // 记录手指按下时是否可以上拉
    private boolean canPullUp = false;

	// 在手指滑动时的过程中记录是否移动了布局
    private boolean isMoved = false;

    public FlexibleScrollView(Context context) {
        this(context, null);
    }

    public FlexibleScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    /**
     * 在加载完xml后获取唯一的一个childview
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() > 0) {
            // 获取第一个childview
            contentView = getChildAt(0);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (contentView == null)
            return;
        // scrollview唯一的一个子view的位置信息,这个位置信息在整个生命周期中保持不变
        originalRect.set(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom());

    }

    /**
     * 重写拦截事件,防止自动轮播时,关闭收藏按钮
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }

    // 在触摸事件中处理上拉和下拉的逻辑
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (contentView == null) {
            return super.dispatchTouchEvent(ev);
        }
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 判断是否可以上拉或者下拉
                canPullDown = isCanPullDown();
                canPullUp = isCanPullUp();
                // 记录按下时的Y值
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_UP:
                if (!isMoved)
                    break; // 如果没有移动布局,则跳过执行
                // 开启动画
                TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(), originalRect.top);
                // 设置动画时间
                anim.setDuration(ANIM_TIME);
                // 给view设置动画
                contentView.setAnimation(anim);
                // 设置回到正常的布局位置
                contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);
                // 将标志位重置
                canPullDown = false;
                canPullUp = false;
                isMoved = false;
                break;
            case MotionEvent.ACTION_MOVE:
                // 在移动过程既没有达到上拉的程度,又没有达到下拉的程度
                if (!canPullDown && !canPullUp) {
                    startY = ev.getY();
                    canPullDown = isCanPullDown();
                    canPullUp = isCanPullUp();
                    break;
                }
                // 计算手指移动的距离
                float nowY = ev.getY();
                int deltaY = (int) (nowY - startY);
                // 是否应该移动布局
                // 1.可以下拉,并且手指向下移动
                // 2.可以上拉,并且手指向上移动
                // 3.既可以上拉也可以下拉,当ScrollView包裹的控件比scrollView还小
                boolean shouldMove = (canPullDown && deltaY > 0) || (canPullUp && deltaY < 0) || (canPullDown && canPullUp);
                if (shouldMove) {
                    // 计算偏移量
                    int offset = (int) (deltaY * MOVE_FACTOR);
                    contentView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset);
                    isMoved = true;
                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 判断是否滚动到顶部
     *
     * @return
     */
    private boolean isCanPullDown() {
        return getScrollY() == 0 || contentView.getHeight() < getHeight() + getScrollY();
    }

    /**
     * 判断是否滚动到底部
     */
    private boolean isCanPullUp() {
        return contentView.getHeight() <= getScrollY() + getHeight();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值