在开发中下拉刷新实在是一种常见的不能再觉的需求了, 网上也有很多优秀的第三方框架。不过之前有一个项目要求刷新的布局从下面出现而不是上面,因为没有在网上找到合适的第三方库所以就自己写了一个demo,在这里与大家分享一下。
首先说实现思路:
- 我们需要一个头和身体且默认是叠加在一起的.
- 获取这两个View且最大滑动范围为头的高度.
- 给身体设置滑动事件监听,改变身体的高度,抬起时判断是否刷新.
- 提供一个方面,可以动态改变是否下拉刷新.
上面最为麻烦的就是动态改变是否显头部,因为要解决事件冲突,并不是每次手指下滑都代表用户想刷新数据,这在ListView中很了解决,只需要判断显示的条目是否为是第一个就行,但在实际需求中可能只是一个普通的布局需要刷新,而这里我们就无法得知下滑到底是显示下面的数据还是刷新。在这里我用到了ViewDragHelper
来解决这个问题。
效果实现及ViewDragHelper用法
第一步:写一个类继承FrameLayout
使头与身体叠加.
public class Luffy extends FrameLayout
然后获取头和身体及其高度
@Override
protected void onFinishInflate() {
if(getChildCount() != 2)
throw new IllegalStateException("只能有两个子View");
mContentView = getChildAt(1);
}
------------------------------------------------------------
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mHeight = getMeasuredHeight();
mMaxDrag = (int) (mHeight * 0.2);
}
onFinishInflate在布局文件加载完毕后调用,我们可以在这里获取到子类,onSizeChanged在测量完毕后会回调,我们在这里获取高度,并设置最大拖拽范围.
第二步:创建ViewDragHelper对象,该对象接收两个参数,分别为ViewGroup
和Callback
,注意Callback
是我们实现效果的关键类.并把触摸事件和拦截事件交给ViewDragHelper.
ViewDragHelper.create(this, new RefurbishCallBack());
-------------------------------------------------------------
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
-------------------------------------------------------------
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true; // 返回true触发后续事件.
}
Callback保有一个抽象方法tryCaptureView
,该返回值决定了我们是否触发拖拽事件,在这里我们触发拖拽事件的条件有两种,触摸View为Content和isRefurbish为true.当我们触摸的View是身体时并且isRefurbish为true时我们才允许刷新其他情况不触发.
/**
* ViewDragHelper的回调方法
*/
private class RefurbishCallBack extends ViewDragHelper.Callback{
// 捕获view
@Override
public boolean tryCaptureView(View child, int pointerId) {
if(child == mContentView && isRefurbish){
return true;
}
return isRefurbish;
}
// 限制view移动方式
// 这里我们重写的是clampViewPositionVertical方法,当我们纵向滑动时该方法调用
// 并返回触摸的View及移动大小
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
top = (int) (top * 0.93f); // 阻尼系数
if(child == mContentView){
if(top < 0) top = 0;
}
return top;
}
// 释放view时回调,即抬手时
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if(releasedChild != mContentView) return;
// 判断是否达到最大拖拽范围, 达到则调用回调方法
if(mContentView.getTop() >= mMaxDrag){
onRefurbishListener.onRefurbish();
scrollDown();
}else {
scrollUp();
}
}
// 这个方法比较重要,当子view也需要事件时,重写该方法并返回正数,我这里返回的是触发点
@Override
public int getViewVerticalDragRange(View child) {
return mMaxDrag;
}
}
核心代码写到这里就差不多了最后在提供一个方法改变isRefurbish值就可以达到动态刷新的目的.
public void isRefurbish(boolean isRefurbish){
this.isRefurbish = isRefurbish;
}
下面是Activity中的代码
luffy.setOnRefurbishListener(new RefurbishLayout.OnRefurbishListener() {
@Override
public void onRefurbish() {
//加载数据...完毕后调用方法隐藏头部
refurbishLayout.setRefurbish(true);
}
};
--------------------------------------------------
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 根据第一个条目是否可见判断开关刷新.
refurbishLayout.isRefurbish(firstVisibleItem == 0);
}