ListView 的下拉刷新和点击加载更多

ListView 在日常工作中使用还是非常频繁的,由于它的缓存机制保证加载显示庞大的数据却不会造成 OOM 或者是卡顿。有的时候我们的数据是随时更新的,抑或者我们的数据十分庞大,需要分批加载,我们就需要给 ListView 来加上上拉或者下拉刷新功能。

官方控件:SwipeRefreshLayout

Google 为了规范化提供了 SwipeRefreshLayout 来帮助我们完成这个操作,使用效果如下图:

新浪微博国际版安卓客户端使用的就是这个。

这里是该控件的官方介绍:SwipeRefreshLayout

这个控件使用起来也十分简单,在官方文档中也明确说明了,只需要将我们需要刷新的 View 作为该控件的子 View 即可,并且 SwipeRefreshLayout 只允许有一个子 View。

就像这样:

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <ListView
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </android.support.v4.widget.SwipeRefreshLayout>

然后我们为 SwipeRefreshLayout 控件添加一个监听器,就可以监听我们的下拉刷新了:

        SwipeRefreshLayout refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_layout); 
        refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //更新数据...
            }
        });

这样刷新之后,就会显示一个进度条,如果我们刷新完了,则只需要调用 setRefreshing(false) 方法就可以将进度条隐藏。同理,也可以调用 setRefreshing(true) 来显示进度条。

大致使用就是这么简单,还有几个比较常用的方法:

  • setColorSchemeColors(int... colors)/setColorSchemeResources(int... colorResIds):设置进度条颜色的,可以设置多个参数,可以直接设置 Color,也可以设置为资源 ID。
  • setEnabled (boolean enabled):设置 下拉刷新是否启用,如果参数为 false,则该组件不起作用。

ListView 的 Header 和 Footer

ListView 为我们提供了 Header 和 Footer,并且提供了相应的添加方法 addHeaderView(View view)/addFooterView(View view)。

我们可以在这两个东西上面做做文章,来达到我们的目的。

先看一下最简单的 Header 和 Footer:

        View headView = LayoutInflater.from(this).inflate(R.layout.listview_head, null);
        View footView = LayoutInflater.from(this).inflate(R.layout.listview_foot, null);
        listview.addHeaderView(headView);
        listview.addFooterView(footView);

通过上面的代码,给 ListView 添加一个头部和底部,显示效果如下:

需要注意的是,在 XML 文件中无法给 ListView 的头和脚设置宽高(设置了也不起作用),所以我们需要在代码中设置

在代码中设置快高和设置其他 View 的宽高一样,都是使用 setLayoutParams 方法,但是需要注意的是,HeaderView 和 FooterView 都是包含在 ListView 当中的,所以其参数应该是:AbsListView.LayoutParams.MATCH_PARENT 或者 AbsListView.LayoutParams.WRAP_CONTENT 或者固定值,例如:

View headView = LayoutInflater.from(this).inflate(R.layout.listview_head, null);
headView.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
                AbsListView.LayoutParams.WRAP_CONTENT));

现在我们知道了 HeaderView 和 FooterView 的存在,就可以利用他们来添加帮助我们做到下拉刷新或者点击加载之类的操作了。

我们需要做的就是自定义一个 ListView,然后再结合手势操作,来完成我们想要的效果。

分析一下,下拉刷新的时候,我们需要确定几点:

  • 目前 ListView 上第一个 Item 的序列号是 0,并且处于最顶端(也就是说,下拉没有 Item 可显示了)
  • 下拉到一定距离才会显示“下拉刷新”的提示

如何判断当前显示的是第一个 Item?

OnScrollListener 有一个方法 onScroll,该方法有一个参数:firstVisibleItem,表示 ListView 中显示的第一个 Item,该值会随着滑动随时改变,当该值为 0 的时候,不就正好是第一个 Item 了么。

ListView 还有一个 getChildAt(int index) 方法,可以根据坐标来获取 ListView 当中的 ItemView,获取到该 Item 之后,通过调用它的 getTop 方法,就可以获取到该 Item 距离父容器顶部的距离。

两者结合,不就可以判断出来了么?!

如何确定是下拉操作?

重写 ListView 的 onTouchEvent 方法,判断 ACTION_MOVE 在 Y 轴的距离是否大于一定的距离,如果大于,则判定为滑动。

那么究竟滑动多长才算是滑动呢??
Android 提供了一个常量:TouchSlop 来作为滑动的“临界点”,每个手机厂商修改过后的系统,该常量也会不同。
可以通过 ViewConfiguration.get(this).getScaledTouchSlop() 来获取该常量。

现在这两点需求都已经满足了,接下来我们需要做的就是将 ListView 的 Header 隐藏起来,待下拉的时候随着下拉手势逐渐显示出来。

如何隐藏 ListView 的 Header 呢?

直接使用 setVisibility 方法?答案是否定的,使用这个方法来隐藏 ListView 的 Header,不管参数是 INVISIBLE 还是 GONE,效果都不好,都仅仅是将 HeaderView 隐藏起来了,但是位置变成空白(ListView 并没有顶上去),如图:

网上搜了一圈,找到两个方法:

方法一:使用 setPadding 方法,将 Top Padding 设置为 -Header高度
这个很好理解吧,Padding 嘛,通俗的理解成内边距嘛,设置为负的 HeaderView 高度,自然不就不显示了么!

需要注意的是,获取 HeaderView 的高度需要在 onMeasure 方法当中或者该方法执行之后去获取

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setPadding(0, -headView.getHeight(), 0, 0);
    }

方法二:使用一个布局作为 HeadView 的父布局,然后 addHeaderView 的时候将父布局作为参数传入,然后调用 HeaderView 的 setVisibility 方法,参数设置为 GONE

View headerView = LayoutInflater.from(context).inflate(R.layout.listview_head, null);
RelativeLayout headerViewParentLayout = new RelativeLayout(context);
layout.addView(headerView);
headerView.setVisibility(View.GONE);

至此,HeaderView 已经隐藏起来了,接下来需要做的就是让 HeaderView 随着手指滑动来显示。

我们知道,我们手指在屏幕上操作,可以分为三个动作:ACTION_DOWNACIONT_MOVEACTION_UP,我们可以在这三个操作当中处理我们的需求:

  • ACTION_DOWN:手指按下,获取到按下的坐标
  • ACTION_MOVE:当 ListView 处于第一条 Item 在最顶部时候,持续监测手指坐标,当移动距离达到一定长度时,HeaderView 开始随着手指的移动逐渐显示
  • ACTION_UP:提示用户开始刷新,HeaderView 逐渐隐藏,刷新数据

还有一些细节需要处理,这样可以让我们的 ListView 更加的人性化一些:

  • 假如 HeaderView 在显示了一半的时候用户松手,那么则取消刷新,直接隐藏 HeaderView
  • HeaderView 当中给用户的提示信息(提示文字,提示图片)
  • 在松开手指一段时间后,隐藏 HeaderView

对于一些属性,我们还需要定义一下:

  • HeaderView 的背景色
  • HeaderView 的高度
  • 松开手指隐藏 HeaderView 的时间
  • 其他一些杂七杂八的属性

自定义属性

    <!--自定义 ListView 属性-->
    <declare-styleable name="CustomListView">
        <!--ListView HeaderView 的高度-->
        <attr name="header_height" format="dimension" />
        <!--ListView HeaderView 背景色-->
        <attr name="header_background_color" format="color" />
        <!--ListView HeaderView 隐藏时间-->
        <attr name="hide_header_time" format="integer" />
        <!--下拉时,ListView HeaderView 的提示文本-->
        <attr name="down_hint_text" format="string" />
        <!--下拉时,ListView HeaderView 的提示文本颜色-->
        <attr name="down_hint_text_color" format="color" />
        <!--松手时,ListView HeaderView 的提示文字-->
        <attr name="up_hint_text" format="string" />
        <!--松手时,ListView HeaderView 的提示文字颜色-->
        <attr name="up_hint_text_color" format="color" />
        <!--刷新时,进度条图片-->
        <attr name="refresh_progress_image" format="integer" />
    </declare-styleable>

CustomListView.java

public class CustomListView extends ListView implements AbsListView.OnScrollListener {

    private Context mContext;
    /**
     * 系统设定 ACTION_MOVE 常量
     */
    private int slop;
    /**
     * ListView Header View
     */
    private View headerView;
    /**
     * ListView Footer View
     */
    private View footView;
    /**
     * ListView HeaderView 的高度
     */
    private int headerViewHeight;
    /**
     * ListView HeaderView 的高度的一半(纯粹是因为避免魔法值,蛋疼)
     */
    private int halfHeaderViewHeight;
    /**
     * 手指按下时的 Y 坐标
     */
    private float startY = 0;
    /**
     * 首个显示的 Item
     */
    private int firstVisibleItem = 0;
    /**
     * ListView HeaderView 当中的文本
     */
    private TextView headerViewText;
    /**
     * ListView HeaderView 当中的进度条
     */
    private ImageView refreshProgressImage;
    /**
     * ListView HeaderView 当中的向上向下图片
     */
    private ImageView image;
    /**
     * 边距长度 - 用于隐藏 HeaderView
     */
    private int paddingSize = 0;
    /**
     * ListView 头颜色
     */
    private int headerViewBackgroundColor;
    /**
     * 隐藏 ListView HeaderView 的时间
     */
    private int hideHeaderTime;
    /**
     * ListView HeaderView 下拉时的提示文字
     */
    private String downHintText;
    /**
     * ListView HeaderView 下拉时提示文字的颜色
     */
    private int downHintTextColor;
    /**
     * 松开刷新提示文字
     */
    private String upHintText;
    /**
     * 松开刷新提示文字颜色
     */
    private int upHintTextColor;
    /**
     * 刷新时,进度条图片
     */
    private int refreshProgressImageId;
    /**
     * HeaderView 指示箭头是否向上
     */
    private boolean arrowUp = true;

    public CustomListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        //当手指滑动距离为系统设定 ACTION_MOVE 的三倍时,ListView HeaderView 开始显示
        slop = ViewConfiguration.get(mContext).getScaledTouchSlop() * 3;
        setOnScrollListener(this);

        //获取 HeaderViewHeight 属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomListView);
        headerViewHeight = (int) typedArray.getDimension(R.styleable.CustomListView_header_height, 0);
        halfHeaderViewHeight = headerViewHeight / 2;
        headerViewBackgroundColor = typedArray.getColor(R.styleable.CustomListView_header_background_color, Color.WHITE);
        hideHeaderTime = typedArray.getInteger(R.styleable.CustomListView_hide_header_time, 5000);
        downHintText = typedArray.getString(R.styleable.CustomListView_down_hint_text);
        downHintTextColor = typedArray.getColor(R.styleable.CustomListView_down_hint_text_color, Color.BLACK);
        upHintText = typedArray.getString(R.styleable.CustomListView_up_hint_text);
        upHintTextColor = typedArray.getColor(R.styleable.CustomListView_up_hint_text_color, Color.BLACK);
        refreshProgressImageId = typedArray.getResourceId(R.styleable.CustomListView_refresh_progress_image, R.drawable.refresh_progress);

        //解析ListView HeaderView
        LayoutInflater inflater = LayoutInflater.from(context);
        headerView = inflater.inflate(R.layout.listview_head, null);
        headerView.setBackgroundColor(headerViewBackgroundColor);


        //HeaderView当中的组件
        headerViewText = (TextView) headerView.findViewById(R.id.text);
        refreshProgressImage = (ImageView) headerView.findViewById(R.id.refresh_image);
        refreshProgressImage.setImageResource(refreshProgressImageId);
        image = (ImageView) headerView.findViewById(R.id.image);

        //设置 HeaderView 的高度
        headerView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, headerViewHeight));
        //添加 HeaderView
        addHeaderView(headerView);
        // 设置 PaddingTop 为 -高度,从而达到初始化时隐藏Header 的目的
        setPadding(0, -headerViewHeight, 0, 0);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //手指按下时,获取手指 Y 坐标
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //判断 ListView 处于最顶端
                if (firstVisibleItem == 0) {
                    View view = getChildAt(0);
                    if (view.getTop() < 0) {
                        //移动距离大于一定长度,这里长度常量设置为系统 slop*3
                        float moveY = ev.getY();
                        //当手指移动距离大于设定常量,并且不大于HeaderView 的高度时,逐渐显示 HeaderView
                        if ((moveY - startY) > slop && paddingSize <= headerViewHeight) {
                            //手指超过常量之后,移动的距离,该距离就是HeaderView显示的部分
                            float moveLength = ev.getY() - startY - slop;
                            paddingSize = headerView.getHeight() - (int) moveLength;
                            if (paddingSize >= 0) {
                                //显示HeaderView,并设置提示文字
                                setPadding(0, -paddingSize, 0, 0);
                                headerViewText.setText(downHintText);
                                headerViewText.setTextColor(downHintTextColor);
                            } else {
                                //HeaderView 完全显示之后,HeaderView 的提示文字
                                headerViewText.setText(upHintText);
                                headerViewText.setTextColor(upHintTextColor);
                                //旋转HeaderView 的指示箭头
                                if (arrowUp) {
                                    RotateAnimation animation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                                    animation.setDuration(500);
                                    animation.setRepeatCount(0);
                                    animation.setFillAfter(true);
                                    image.setPivotX(image.getWidth() / 2);
                                    image.setPivotY(image.getHeight() / 2);
                                    image.startAnimation(animation);
                                    arrowUp = false;
                                }
                            }
                        }
                    }
                }
                break;
            //松开手指
            case MotionEvent.ACTION_UP:
                //如果HeaderView 没有完全显示,则继续隐藏
                float upY = ev.getY() - startY - slop;
                if (upY < headerViewHeight) {
                    setPadding(0, -headerViewHeight, 0, 0);
                } else {
                    //隐藏提示文字及只是箭头,显示加载进度条
                    headerViewText.setVisibility(View.GONE);
                    image.clearAnimation();
                    image.setVisibility(View.GONE);
                    refreshProgressImage.setVisibility(View.VISIBLE);
                    arrowUp = true;
                    //加载进度条动画
                    RotateAnimation animation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                    animation.setDuration(1000);
                    animation.setRepeatCount(-1);
                    animation.setInterpolator(new LinearInterpolator());
                    refreshProgressImage.startAnimation(animation);
                    //进度条显示5秒
                    handler.sendEmptyMessageDelayed(1, hideHeaderTime);
                }

                break;
            default:
        }
        return super.onTouchEvent(ev);
    }


    @SuppressLint("HandlerLeak")
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            setPadding(0, -headerViewHeight, 0, 0);
            refreshProgressImage.setVisibility(View.GONE);
            refreshProgressImage.clearAnimation();
            headerViewText.setVisibility(View.VISIBLE);
            headerViewText.setText("下拉刷新");
            image.setImageResource(R.drawable.move_down);
            image.setVisibility(View.VISIBLE);
        }
    };


    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (firstVisibleItem == 0) {
            this.firstVisibleItem = firstVisibleItem;
        }
    }
}

注释已经写的很清楚了,代码也很简单,就不一一解释了,看代码就好。

至此,下拉刷新就做好了,下面开始做点击加载


点击加载

这个功能就更加简单了,因为“点击加载更多”一般是位于 ListView 的底部,直接添加 FooterView 即可。

当然,你也可以在 ListView 滑动到最底下的时候再添加,只要判断界面显示的最后一条 Item 就是该 ListView 的最后一条 Item,并且该 item 完全显示,就显示 FooterView 就好啦!

如何判断 ListView 已经滑动到底部呢?还得借助 OnScrollListener 中的 onScroll 方法,看代码:

    boolean footerShow = true;

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (firstVisibleItem == 0) {
            this.firstVisibleItem = firstVisibleItem;
        }

        View lastItemView = getChildAt(getChildCount() - 1);
        if (lastItemView != null && lastItemView.getBottom() == getHeight() && footerShow) {
            View footerView = LayoutInflater.from(mContext).inflate(R.layout.listview_foot, null);
            footerView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100));
            addFooterView(footerView);
            footerShow = false;
        }
    }

剩下的就是设置一些 FooterView 的显示文字以及动画之类的了,和 HeaderView 一样,不赘言。


完整代码如下:
属性配置

    <!--自定义 ListView 属性-->
    <declare-styleable name="CustomListView">
        <!--ListView HeaderView 的高度-->
        <attr name="header_height" format="dimension" />
        <!--ListView HeaderView 背景色-->
        <attr name="header_background_color" format="color" />
        <!--ListView HeaderView 隐藏时间-->
        <attr name="hide_header_time" format="integer" />
        <!--下拉时,ListView HeaderView 的提示文本-->
        <attr name="down_hint_text" format="string" />
        <!--下拉时,ListView HeaderView 的提示文本颜色-->
        <attr name="down_hint_text_color" format="color" />
        <!--松手时,ListView HeaderView 的提示文字-->
        <attr name="up_hint_text" format="string" />
        <!--松手时,ListView HeaderView 的提示文字颜色-->
        <attr name="up_hint_text_color" format="color" />
        <!--刷新时,进度条图片-->
        <attr name="refresh_progress_image" format="integer" />
    </declare-styleable>

CustomListView

public class CustomListView extends ListView implements AbsListView.OnScrollListener {

    private Context mContext;
    /**
     * 系统设定 ACTION_MOVE 常量
     */
    private int slop;
    /**
     * ListView Header View
     */
    private View headerView;
    /**
     * ListView Footer View
     */
    private View footerView;
    /**
     * ListView HeaderView 的高度
     */
    private int headerViewHeight;
    /**
     * ListView HeaderView 的高度的一半(纯粹是因为避免魔法值,蛋疼)
     */
    private int halfHeaderViewHeight;
    /**
     * 手指按下时的 Y 坐标
     */
    private float startY = 0;
    /**
     * 首个显示的 Item
     */
    private int firstVisibleItem = 0;
    /**
     * ListView HeaderView 当中的文本
     */
    private TextView headerViewText;
    /**
     * ListView HeaderView 当中的进度条
     */
    private ImageView refreshProgressImage;
    /**
     * ListView HeaderView 当中的向上向下图片
     */
    private ImageView image;
    /**
     * 边距长度 - 用于隐藏 HeaderView
     */
    private int paddingSize = 0;
    /**
     * ListView 头颜色
     */
    private int headerViewBackgroundColor;
    /**
     * 隐藏 ListView HeaderView 的时间
     */
    private int hideHeaderTime;
    /**
     * ListView HeaderView 下拉时的提示文字
     */
    private String downHintText;
    /**
     * ListView HeaderView 下拉时提示文字的颜色
     */
    private int downHintTextColor;
    /**
     * 松开刷新提示文字
     */
    private String upHintText;
    /**
     * 松开刷新提示文字颜色
     */
    private int upHintTextColor;
    /**
     * 刷新时,进度条图片
     */
    private int refreshProgressImageId;
    /**
     * HeaderView 指示箭头是否向上
     */
    private boolean arrowUp = true;

    public CustomListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        //当手指滑动距离为系统设定 ACTION_MOVE 的三倍时,ListView HeaderView 开始显示
        slop = ViewConfiguration.get(mContext).getScaledTouchSlop() * 3;
        setOnScrollListener(this);

        //获取 HeaderViewHeight 属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomListView);
        headerViewHeight = (int) typedArray.getDimension(R.styleable.CustomListView_header_height, 0);
        halfHeaderViewHeight = headerViewHeight / 2;
        headerViewBackgroundColor = typedArray.getColor(R.styleable.CustomListView_header_background_color, Color.WHITE);
        hideHeaderTime = typedArray.getInteger(R.styleable.CustomListView_hide_header_time, 5000);
        downHintText = typedArray.getString(R.styleable.CustomListView_down_hint_text);
        downHintTextColor = typedArray.getColor(R.styleable.CustomListView_down_hint_text_color, Color.BLACK);
        upHintText = typedArray.getString(R.styleable.CustomListView_up_hint_text);
        upHintTextColor = typedArray.getColor(R.styleable.CustomListView_up_hint_text_color, Color.BLACK);
        refreshProgressImageId = typedArray.getResourceId(R.styleable.CustomListView_refresh_progress_image, R.drawable.refresh_progress);

        //解析ListView HeaderView
        LayoutInflater inflater = LayoutInflater.from(context);
        headerView = inflater.inflate(R.layout.listview_head, null);
        headerView.setBackgroundColor(headerViewBackgroundColor);


        //HeaderView当中的组件
        headerViewText = (TextView) headerView.findViewById(R.id.text);
        refreshProgressImage = (ImageView) headerView.findViewById(R.id.refresh_image);
        refreshProgressImage.setImageResource(refreshProgressImageId);
        image = (ImageView) headerView.findViewById(R.id.image);

        //设置 HeaderView 的高度
        headerView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, headerViewHeight));
        //添加 HeaderView
        addHeaderView(headerView);
        // 设置 PaddingTop 为 -高度,从而达到初始化时隐藏Header 的目的
        setPadding(0, -headerViewHeight, 0, 0);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //手指按下时,获取手指 Y 坐标
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //判断 ListView 处于最顶端
                if (firstVisibleItem == 0) {
                    View view = getChildAt(0);
                    if (view.getTop() < 0) {
                        //移动距离大于一定长度,这里长度常量设置为系统 slop*3
                        float moveY = ev.getY();
                        //当手指移动距离大于设定常量,并且不大于HeaderView 的高度时,逐渐显示 HeaderView
                        if ((moveY - startY) > slop && paddingSize <= headerViewHeight) {
                            //手指超过常量之后,移动的距离,该距离就是HeaderView显示的部分
                            float moveLength = ev.getY() - startY - slop;
                            paddingSize = headerView.getHeight() - (int) moveLength;
                            if (paddingSize >= 0) {
                                //显示HeaderView,并设置提示文字
                                setPadding(0, -paddingSize, 0, 0);
                                headerViewText.setText(downHintText);
                                headerViewText.setTextColor(downHintTextColor);
                            } else {
                                //HeaderView 完全显示之后,HeaderView 的提示文字
                                headerViewText.setText(upHintText);
                                headerViewText.setTextColor(upHintTextColor);
                                //旋转HeaderView 的指示箭头
                                if (arrowUp) {
                                    RotateAnimation animation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                                    animation.setDuration(500);
                                    animation.setRepeatCount(0);
                                    animation.setFillAfter(true);
                                    image.setPivotX(image.getWidth() / 2);
                                    image.setPivotY(image.getHeight() / 2);
                                    image.startAnimation(animation);
                                    arrowUp = false;
                                }
                            }
                        }
                    }
                }
                break;
            //松开手指
            case MotionEvent.ACTION_UP:
                //如果HeaderView 没有完全显示,则继续隐藏
                float upY = ev.getY() - startY - slop;
                if (upY < headerViewHeight) {
                    setPadding(0, -headerViewHeight, 0, 0);
                } else {
                    //隐藏提示文字及只是箭头,显示加载进度条
                    headerViewText.setVisibility(View.GONE);
                    image.clearAnimation();
                    image.setVisibility(View.GONE);
                    refreshProgressImage.setVisibility(View.VISIBLE);
                    arrowUp = true;
                    //加载进度条动画
                    RotateAnimation animation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                    animation.setDuration(1000);
                    animation.setRepeatCount(-1);
                    animation.setInterpolator(new LinearInterpolator());
                    refreshProgressImage.startAnimation(animation);
                    //进度条显示5秒
                    handler.sendEmptyMessageDelayed(1, hideHeaderTime);
                }

                break;
            default:
        }
        return super.onTouchEvent(ev);
    }


    @SuppressLint("HandlerLeak")
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            setPadding(0, -headerViewHeight, 0, 0);
            refreshProgressImage.setVisibility(View.GONE);
            refreshProgressImage.clearAnimation();
            headerViewText.setVisibility(View.VISIBLE);
            headerViewText.setText("下拉刷新");
            image.setImageResource(R.drawable.move_down);
            image.setVisibility(View.VISIBLE);
        }
    };


    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    boolean footerShow = true;

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (firstVisibleItem == 0) {
            this.firstVisibleItem = firstVisibleItem;
        }

        View lastItemView = getChildAt(getChildCount() - 1);
        if (lastItemView != null && lastItemView.getBottom() == getHeight() && footerShow) {
            footerView = LayoutInflater.from(mContext).inflate(R.layout.listview_foot, null);
            footerView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100));
            addFooterView(footerView);
            footerShow = false;
        }
    }
}