仿支付宝首页的简易下拉刷新(下拉事件从顶部响应)

本文介绍如何通过重写父控件的触摸事件来实现仿支付宝首页的下拉刷新效果,主要涉及ListView和ScrollView的事件拦截与处理,以实现下拉事件从顶部响应的功能。
摘要由CSDN通过智能技术生成

下拉刷新的功能,想必很多人闭着眼睛能写出来(仅限于功能实现)。而支付宝的首页下拉刷新和普通的下拉刷新有一点不一样。一般的下拉刷新是从刷新控件开始的位置向下,这个区域内响应手势事件,支付宝的是从ActionBar下面的view开始响应的。如图区域:


显然常规的下拉刷新的控件是需要改动才能实现这个功能的。怎么改动呢?既然是下拉事件的区域发生的变化,那就是手势的拦截咯。最容易想到的就是在下拉刷新控件的父控件中进行事件拦截,然后重写touch事件,在ACTION_MOVE中去处理,或者叫做指派刷新控件做相应的变化(对应刷新控件的ACTION_MOVE事件)。好了,现在捋一下思路:

1.重写父控件的onInterceptTouchEvent方法,对滑动或者点击事件区别处理;是滑动就拦截,反之不拦截。

2.重写父控件的onTouchEvent方法,处理touch事件,ACTION_MOVE的事件对应刷新控件的ACTION_MOVE事件(交给刷新控件做)。

那就开始干吧!今天就模拟一下这个场景下拉刷新,写了一个demo,分别重写ListView和ScrollView,这两个其实是一样的,仅有一点区别。

1.重写ListView:

public class RefreshListView extends ListView {

    /**
     * 1.如果listview滑出屏幕的距离为0时,下拉时header下面的view慢慢显示出来
     * 2.如果listview滑出屏幕的距离不为0时,执行其自身的滑动
     * 3.如果正在向下显示header下面的view时,上滑view也向上隐藏,隐藏完继续listview自身的滑动
     */
    private RelativeLayout mHeader;
    private View mRefreshView;
    private int mTouchSlop;
    private int mRefreshViewHeight, mHeaderHeight, mTargetHeight;
    private float mLastX, mLastY;
    private boolean mIsRefresh, mIsDrag;
    private ViewGroup.LayoutParams params;
    private BackRunnable mRunnable;
    private RefreshListener mListener;

    public interface RefreshListener {
        void start();
    }

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

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

    public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public void setRefreshListener(RefreshListener listener) {
        mListener = listener;
    }

    public void initHeader(View header, View refreshHeader) {
        mHeader = new RelativeLayout(getContext());
        if (refreshHeader == null) {
            LinearLayout linearLayout = new LinearLayout(getContext());
            linearLayout.setOrientation(LinearLayout.VERTICAL);
            linearLayout.setGravity(Gravity.CENTER);
            linearLayout.setPadding(0, 30, 0, 10);
            linearLayout.addView(new ProgressBar(getContext()));

            mRefreshView = new ProgressBar(getContext());
        } else {
            mRefreshView = refreshHeader;
        }
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
        mHeader.addView(mRefreshView, lp);
        if (header != null) {
            lp = new RelativeLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            mHeader.addView(header, lp);
        }
        addHeaderView(mHeader);

    }

    private boolean isValidDrag(MotionEvent ev) {
        if (ev == null) {
            return false;
        }
        return Math.abs(mLastX - ev.getX()) < Math.abs(mLastY - ev.getY())
                && Math.abs(mLastY - ev.getY()) > mTouchSlop;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mRefreshView == null || mHeader == null) {
            return super.onInterceptTouchEvent(ev);
        }
        int action = ev.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mLastX = ev.getX();
                mLastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (isValidDrag(ev)) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //需要处理header view里面的显示的话,要设置param,否则给父类自己来处理
        if (mRefreshView == null || mHeader == null) {
            return super.onTouchEvent(ev);
        }

        if (mRefreshViewHeight <= 0) {
            mHeaderHeight = mHeader.getMeasuredHeight();
            mRefreshViewHeight = mRefreshView.getMeasuredHeight();
            mTargetHeight = mHeaderHeight + mRefreshViewHeight;
        }

        int action = ev.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mLastX = ev.getX();
                mLastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mHeader.getTop() < 0 || mIsRefresh) {
                    break;
                }
                if (!mIsDrag && isValidDrag(ev)) {
                    mIsDrag = true;
                }
                if (mIsDrag) {
                    if (ev.getY() - mLastY > 0 && !mIsRefresh) {
                        moveLayout(ev.getY() - mLastY);
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (params == null) {
                    params = mHeader.getLayoutParams();
                }
                if (params.height <= mHeaderHeight) {
                    break;
                }
                mIsRefresh = params.height >= mTargetHeight;
                if (!mIsRefresh) {
                    completeRefresh();
                } else {
                    if (mListener != null) {
                        mListener.start();
                    }
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    public void completeRefresh() {
        if (mRunnable == null) {
            mRunnable = new BackRunnable(500);
        }
        mRunnable.start();
    }

    private void moveLayout(float y) {
        params = mHeader.getLayoutParams();
        float changeHeight = mHeaderHeight + 0.2f * y;
        if (changeHeight >= mTargetHeight && params.height == mTargetHeight) {
            return;
        }
        if (changeHeight > mTargetHeight) {
            params.height = mTargetHeight;
        } else {
            params.height = (int) changeHeight;
        }
        mHeader.requestLayout();
    }

    private class BackRunnable implements Runnable {

        private int mDuration;
        private boolean mIsFinish;
        private long mStartTime;

        private Interpolator mInterpolator = new Interpolator() {

            @Override
            public float getInterpolation(float input) {
                return (float) Math.pow(input, 5);
            }
        };

        public BackRunnable(int duration) {
            mDuration = duration;
            mIsFinish = true;
        }

        public void start() {
            if (mIsFinish) {
                mStartTime = System.currentTimeMillis();
                mIsFinish = false;
                post(this);
            }
        }

        public void cancel() {
            this.mIsFinish = true;
        }

        @Override
        public void run() {
            if (!mIsFinish) {
                long delta = System.currentTimeMillis() - mStartTime;
                if (delta > mDuration) {
                    delta = mDuration;
                }
                params.height = (int) (mHeaderHeight + mRefreshViewHeight * mInterpolator.getInterpolation(1f - delta * 1f / mDuration));
                mHeader.requestLayout();
                if (params.height == mHeaderHeight) {
                    this.mIsFinish = true;
                    mIsRefresh = false;
                    return;
                }
                post(this);
            }
        }
    }
}

import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;

/**
 * Created by wh on 17/7/24.
 */

public class RefreshListView extends ListView {

    /**
     * 1.如果listview滑出屏幕的距离为0时,下拉时header下面的view慢慢显示出来
     * 2.如果listview滑出屏幕的距离不为0时,执行其自身的滑动
     * 3.如果正在向下显示header下面的view时,上滑view也向上隐藏,隐藏完继续listview自身的滑动
     */
    private RelativeLayout mHeader;
    private View mRefreshView;
    private int mTouchSlop;
    private int mRefreshViewHeight, mHeaderHeight, mTargetHeight;
    private float mLastX, mLastY;
    private boolean mIsRefresh, mIsDrag;
    private ViewGroup.LayoutParams params;
    private BackRunnable mRunnable;
    private RefreshListener mListener;

    public interface RefreshListener {
        void start();
    }

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

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

    public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, </
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值