下拉刷新的功能,想必很多人闭着眼睛能写出来(仅限于功能实现)。而支付宝的首页下拉刷新和普通的下拉刷新有一点不一样。一般的下拉刷新是从刷新控件开始的位置向下,这个区域内响应手势事件,支付宝的是从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, 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); } } } }
2.重写ScrollView:
public class RefreshScrollView extends ScrollView {
/**
* 1.如果listview滑出屏幕的距离为0时,下拉时header下面的view慢慢显示出来
* 2.如果listview滑出屏幕的距离不为0时,执行其自身的滑动
* 3.如果正在向下显示header下面的view时,上滑view也向上隐藏,隐藏完继续listview自身的滑动
*/
private RelativeLayout mHeader;
private View mRefreshView;
private LinearLayout mContainer;
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 RefreshScrollView(Context context) {
this(context, null);
}
public RefreshScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RefreshScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
int count = getChildCount();
if (count != 1 || !(getChildAt(0) instanceof LinearLayout)) {
throw new IllegalArgumentException("scrollview must be one LinearLayout");
}
mContainer = (LinearLayout) getChildAt(0);
}
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);
}
mContainer.addView(mHeader, 0);
}
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 || getScrollY() > 0) {
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 || getScrollY() > 0) {
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 (getScrollY() > 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:
mIsDrag = false;
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.util.Log; 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; import android.widget.ScrollView; /** * Created by wh on 17/7/24. */ public class RefreshScrollView extends ScrollView { private RelativeLayout mHeader; private View mRefreshView; private LinearLayout mContainer; 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 RefreshScrollView(Context context) { this(context, null); } public RefreshScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RefreshScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override protected void onFinishInflate() { super.onFinishInflate(); int count = getChildCount(); if (count != 1 || !(getChildAt(0) instanceof LinearLayout)) { throw new IllegalArgumentException("scrollview must be one LinearLayout"); } mContainer = (LinearLayout) getChildAt(0); } 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); } mContainer.addView(mHeader, 0); } 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 || getScrollY() > 0) { 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 || getScrollY() > 0) { 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 (getScrollY() > 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: mIsDrag = false; 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); } } } }
上面就是基本的demo代码了。这个和其他不一样的地方是:
1.重写ListView的用了header,header是一个相对布局,上面一层是要显示的view,比如一个九宫格的功能菜单选项;下面一层是下拉刷新的view;
2.onTouchEvent中,下拉的过程中刷新控件慢慢的从header中露出来,知道up的时候,要么回弹要么回调下拉刷新的接口;
3.重写Scrollview和listview是一个思路,只不过是把这个header放到了scrollview的子view的第一个位置;
4.这是一个简易的刷新,主要目的是实现这种不是从刷新控件顶部响应事件的场景,刷新的view完全可以换成自己的;
5.这个有个大问题是,没有扩展性,下拉刷新库要提供的move事件的移动或者动画的接口。
具体测试的代码就不上了,有错误的地方,还请指正!
另外,一个同事用了更简单的方法,利用CoordinatorLayout的Behavior来处理,也就是重写Behavior,让Behavior来拦截和处理下拉的事件,不清楚的可以学习Behavior的自定义。