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