Android 下拉刷新, 头在下方.

1,012 阅读3分钟

在开发中下拉刷新实在是一种常见的不能再觉的需求了, 网上也有很多优秀的第三方框架。不过之前有一个项目要求刷新的布局从下面出现而不是上面,因为没有在网上找到合适的第三方库所以就自己写了一个demo,在这里与大家分享一下。


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对象,该对象接收两个参数,分别为ViewGroupCallback,注意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);
}

点击查看完成代码