ScrollView上拉

说明

        带有上拉阻尼的ScrollView。

思路

        ScrollView下只能有一个子View,因此只需要将ScrollView下嵌套一个LinearLayout,将ScrollView原本的子View移植到线性布局中,并在线性布局中添加一个空白的view。在上拉的过程中,根据移动的位置,修改最后一个view的高度,这样就相对于当原内容给拉上去了。

        用sum记录本次总的位移,根据位移是正是负,决定底部空白view是否有高度。只有底部view有高度,就可以将上拉的部分给顶上去。

核心

        最关键的地方在于判断ScrollView是否滚动到最底部。代码如下:

    private boolean isBottom() {
        int i = getChildAt(0).getHeight() - getHeight();
        return getScrollY() >= i;
    }

代码

public class UploadScrollView extends ScrollView {
    private static final String TAG = "CustomScrollView";
    private int sum = 0;//本次移动过程中,总的位移(有阻尼运动)
    private static int MAX_OVER_Y;//上拉超过该距离后不再移动
    private static int LIMIT_Y;//上拉的距离超过该值后会进行回调
    private OnUploadedListener l;
    private boolean isFirst = true;
    private boolean isShouldOver = false;
    private int slop;
    private TextView bottom;
    private float lastY;

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

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

    private void init() {
        slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        slop = (int) Math.sqrt(slop);
        post(new Runnable() {
            @Override
            public void run() {
                MAX_OVER_Y = getHeight() / 3;
                LIMIT_Y = getHeight() / 4;
            }
        });
    }


    @Override
    protected void onFinishInflate() {//加载成功后,将原布局移动了线性布局中,并添加一个空白view
        super.onFinishInflate();
        View view = getChildAt(0);
        removeViewAt(0);
        LinearLayout ll = new LinearLayout(getContext());
        ll.setOrientation(LinearLayout.VERTICAL);
        ScrollView.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        addView(ll, lp);
        bottom = new TextView(getContext());
        bottom.setBackgroundColor(Color.TRANSPARENT);
        bottom.setHeight(0);
        LinearLayout.LayoutParams scP = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        ll.addView(view, scP);

        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        ll.addView(bottom, layoutParams);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastY = ev.getY();
                if (getScrollY() >= getScrollRangeY()) {
                    isShouldOver = true;
                    isFirst = true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    private int getScrollRangeY() {//得到ScrollView最大的滚动距离,子控件的高度-ScrollView本身的高度
        return getChildAt(0).getHeight() - getHeight();
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float curY = ev.getY();
                float deltaY = curY - lastY;
                if (Math.abs(deltaY) < slop) {//防手抖
                    return true;
                }
                if (isFirst && isShouldOver) {
                    isShouldOver = deltaY < 0;//点击时已到边界,并且往上拉,才能滚出边界
                    isFirst = false;
                }
                if (sum < 0)//sum<0,说明总体已经上拉过一段距离,这里重新进行赋值,是为了纠正sum可能出现的偏差
                    sum = -bottom.getHeight();
                float tempY = deltaY * (MAX_OVER_Y + sum) / MAX_OVER_Y;
                if (isShouldOver && (sum < 0 || (sum == 0 && tempY < 0))) {//已经上拉过,或者没有上拉但即将上拉
                    if (tempY < 0) {//上拉。之所以+tempY,是为了阻尼效果
                        sum += tempY;
                        if (Math.abs(sum) > MAX_OVER_Y) {
                            sum = -MAX_OVER_Y;
                        }
                    } else {//deltaY > 0,说明是下滑,缩回去的速度应该跟下滑速度一样
                        sum += deltaY;
                    }
                    upload(sum);
                    if(sum < 0)
                        scrollTo(0,getScrollRangeY());
                    // 内部增高,但并未滚动,所以看不见下面的内容.也就导致了上拉无效果。所以需要scrollTo一下
                    lastY = curY;
                    return true;
                }
                sum += deltaY;
                lastY = curY;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (sum < 0 && isShouldOver) {
                    upload(0);
                    if (Math.abs(sum) > LIMIT_Y && l != null) {//回调
                        l.onUpload();
                    }
                }
                sum = 0;
                isShouldOver = false;
                break;
        }
        return super.onTouchEvent(ev);
    }

    public void setOnUploadedListener(OnUploadedListener l) {
        this.l = l;
    }

    public interface OnUploadedListener {
        void onUpload();
    }

    private void upload(int location) {
        if (location < 0) {//上拉过一段距离,将底层view的高度设置成上拉的高度
            bottom.setHeight(-location);
        } else {//总体没有上拉,那就将底层view设置成高度为0,即不可见
            bottom.setHeight(0);
        }
    }
}

总结

        所有的上拉下拉都可以总结成:

        1,判断当前是否应该进行上拉或者下拉——即:是否需要将上拉部分或者下拉部分显露出来。比如scrollView只需要判断scrollY是否到最低或者最上面就行;ListView参考

        2,根据本次总的位移(有一个变量记住本次的位移),判断上拉部分和下拉部分显示部分。而且,在Move事件中,大部分代码也一样。

        3,根据总的位移量,判断当前是否应该进行回调。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值