Android 怎么实现newbility的下拉刷新和加载更多的ListView

在上一篇博文的最后,我说要写一个下拉刷新的ListView和RecyclerView,并且可以直接使用QQheader
先说声对不起了,上一篇博文的那种设计绝对有问题,会出现很多重复代码!我的脸被自己打了,好疼 /(ㄒoㄒ)/~~
上一篇博文:Android 怎么实现支持所有View的通用的下拉刷新控件

本篇博文准备讲如何实现下拉刷新和上拉加载,写的比较烂,篇幅比较长。
先预览下最终效果吧,不然我怕你们看不下去=。=
这里写图片描述

一、下拉刷新的实现方式

在上一篇博文中,我们通过移动header的位置来实现下拉刷新。
但是这种方式在ListView中并不是十分合适,所以我需要修改一下实现方式。

这里写图片描述
通过修改header的高度的方式来实现下拉刷新。

二、加载更多的实现方式

加载更多一般情况下有三种方式:
1. 滑动到最底部的时候显示footer,上拉加载更多
2. 滑动到底部的时候,自动加载更多
3. 快要到底部的时候(还没到底部,比如bilibili客户端),自动加载更多
而博主我是用第二种, 虽然三种都可以同时实现,但是博主表示懒癌发作了=。=

关于监听滚动事件也有两种方式:
一是监听onScrollStateChange,二是监听onScroll。
这两者有什么区别呢,为什么我会选择onScroll抛弃onScrollStateChange:

在一次滚动事件中,onScrollStateChange只会回调两次或三次,分别是scroll、fling、idle。那么,第三种方式就不合适了,你并不能知道你当前滑动到哪个位置了

在界面初始化时, onScroll会执行, 而ScrollState不会。
有人可能会问,这有什么用,不会执行不是更好么?
如果你分页加载,首次加载的时候很奇葩的数据没有满屏怎么办,这时候是不是应该再加载一页数据。而onScroll因为在初始化时会执行,所以不需要做任何事情就能自适应了。

三、ListView中的事件和滚动的处理

下面代码以ListView为例,而RecyclerView代码稍微会多一些,等以后我完成整个框架的时候直接上传到Github再说。
我们先继承ListView,处理一下事件
因为打算将逻辑都交给header和footer处理,所以代码出奇的少,干干净净。

也因为将逻辑交给header/footer处理,ListView并不知道什么时候调用刷新方法。所以当header进入刷新状态时,就要回调ListView的刷新方法。所以我们需要定义一个接口。

/**
 * Created by AItsuki on 2016/6/22.
 * 需要使用刷新header的控件必须实现此接口
 */
public interface Refreshing {

    void onRefreshCallBack();
}
/**
 * Created by AItsuki on 2016/6/24.
 * 同Refreshing
 */
public interface Loading {
    void onLoadMoreCallBack();
}
/**
 * Created by AItsuki on 2016/6/21.
 */
public class AListView extends ListView implements AbsListView.OnScrollListener, Refreshing, Loading {

    private int activePointerId;
    private float lastY;

    public AListView(Context context) {
        super(context);
        initView();
    }

    public AListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public AListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {

    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int actionMasked = ev.getActionMasked();

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN:
                activePointerId = ev.getPointerId(0);
                lastY = ev.getY(0);
                // header和footer的按下处理在这里!
                break;
            case MotionEvent.ACTION_MOVE:
                float y = ev.getY(MotionEventCompat.findPointerIndex(ev, activePointerId));
                float diffY = y - lastY;
                lastY = y;

                // --------------header----------------------
                if (getFirstVisiblePosition() == 0 && diffY > 0) {
                    // header的onMove在这处理

                } else if (diffY < 0 && refreshHeader.getVisibleHeight() > 0) {
                    // 往上滑动,header回去的过程中,listView也会跟着滚动,导致滑动致header消失后,其实header高度还没有到0
                    // setSelection可以保证header一直处于顶部
                    setSelection(0);
                    // header的onMove在这处理
                }

                // --------------footer--------------------
                int lastVisible = getLastVisiblePosition();
                int lastPosition = getAdapter().getCount() - 1;
                if (getFirstVisiblePosition() != 0 && lastVisible == lastPosition) {
                    // footer的onMove也在这里,让footer也能拉动,弹弹弹!
                }

                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                onSecondaryPointerDown(ev);
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                // header和footer的手指松开处理在这里!
                break;
        }
        return super.onTouchEvent(ev);
    }

    private void onSecondaryPointerDown(MotionEvent ev) {
        int pointerIndex = MotionEventCompat.getActionIndex(ev);
        lastY = ev.getY(pointerIndex);
        activePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
    }

    private void onSecondaryPointerUp(MotionEvent ev) {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
        if (pointerId == activePointerId) {
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            lastY = ev.getY(newPointerIndex);
            activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
    }
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // footer的onScroll事件在此~
    }

    /**
     * Refreshing接口的方法, 当header进入刷新状态时会回调此方法
     */
    @Override
    public void onRefreshCallBack() {

    }

    /**
     * Loading接口的方法,当footer进入加载状态时会回调此方法
     */
    @Override
    public void onLoadMoreCallBack() {

    }
}

header和footer需要处理的地方都已经注释过了,接下来我们就需要在header和footer中处理事件了。

四、Header基类

相对于上一篇博客来说,header多了一个Failure状态,不然无论刷新成功还是失败,header都显示刷新成功不太好。

package com.aitsuki.library;

import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;

/**
 * Created by AItsuki on 2016/6/21.
 * RefreshHeader相当于一个容器,装着我们自定义的Header
 * child通过getContentView方法将header传进来。
 */
public abstract class RefreshHeader extends LinearLayout {

    private static final String TAG = "RefreshHeader";

    private static final float DRAG_RATE = 0.5f;
    // scroller duration
    private static final int SCROLL_TO_TOP_DURATION = 800;
    private static final int SCROLL_TO_REFRESH_DURATION = 250;
    private static final long SHOW_COMPLETED_TIME = 500;
    private static final long SHOW_FAILURE_TIME = 500;

    private long showCompletedTime = SHOW_COMPLETED_TIME;
    private long showFailureTime = SHOW_FAILURE_TIME;

    private View contentView;   // header
    private int refreshHeight;  // 刷新高度
    private int maxDragDistance;

    private boolean isTouch;
    private State state = State.RESET;
    private final AutoScroll autoScroll;
    private Refreshing refreshing;

    public RefreshHeader(Context context) {
        super(context);
        maxDragDistance = Utils.dip2px(context, 500); // 默认500dp

        autoScroll = new AutoScroll();
        // content由子类创建
        contentView = getContentView();
        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
        addView(contentView, lp);

        measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        refreshHeight = getMeasuredHeight();
        Log.e(TAG, "refreshHeight = " + refreshHeight);
    }

    protected void bindRefreshing(Refreshing refreshing) {
        this.refreshing = refreshing;
    }

    protected void setVisibleHeight(int height) {
        height = Math.max(0, height);
        LayoutParams lp = (LayoutParams) contentView.getLayoutParams();
        lp.height = height;
        contentView.setLayoutParams(lp);
    }

    /**
     * 设置成功状态时,header的停留时间
     */
    public void setShowCompletedTime(long time) {
        if (time >= 0) {
            showCompletedTime = time;
        }
    }

    /**
     * 设置失败状态时,header的停留时间
     */
    public void setShowFailureTime(long time) {
        if (time >= 0) {
            showFailureTime = time;
        }
    }

    /**
     * 设置最大下拉高度(默认300dp)
     *
     * @param distance 最大下拉高度, 单位px
     */
    public void setMaxDragDistance(int distance) {
        if (distance > 0) {
            maxDragDistance = distance;
        }
    }


    public int getVisibleHeight() {
        return contentView.getLayoutParams().height;
    }

    public int getRefreshHeight() {
        return refreshHeight;
    }

    public State getState() {
        return state;
    }

    public boolean isTouch() {
        return isTouch;
    }

    public void onPress() {
        isTouch = true;
        removeCallbacks(delayToScrollTopRunnable);
        autoScroll.stop();
    }

    public void onMove(float offset) {
        if (offset == 0) {
            return;
        }

        float dragRate = DRAG_RATE;

        if (offset > 0) {
            offset = offset * dragRate + 0.5f;
            if(getVisibleHeight() > refreshHeight) {
                // 因为忽略了refreshHeight,所以这里会超出最大高度,直接价格判断省事。
                if(getVisibleHeight() >= maxDragDistance) {
                    offset = 0;
                } else {
                    float extra = getVisibleHeight() - refreshHeight;
                    float extraPercent = Math.min(1, extra / maxDragDistance);
                    dragRate = Utils.calcDragRate(extraPercent);
                    offset = offset * dragRate + 0.5f;
                }
            }
        }

        int target = (int) Math.max(0, getVisibleHeight() + offset);

        // 1. 在RESET状态时,第一次下拉出现header的时候,设置状态变成PULL
        if (state == State.RESET && getVisibleHeight() == 0 && target > 0) {
            changeState(State.PULL);
        }

        // 2. 在PULL或者COMPLETE状态时,header回到顶部的时候,状态变回RESET
        if (getVisibleHeight() > 0 && target <= 0) {
            if (state == State.PULL || state == State.COMPLETE || state == State.FAILURE) {
                changeState(State.RESET);
            }
        }

        // 3. 如果是从底部回到顶部的过程(往上滚动),并且手指是松开状态, 并且当前是PULL状态,状态变成LOADING
        if (state == State.PULL && !isTouch && getVisibleHeight() > refreshHeight && target <= refreshHeight) {
            // 这时候我们需要强制停止autoScroll
            autoScroll.stop();
            changeState(State.REFRESHING);
            target = refreshHeight;
        }
        setVisibleHeight(target);
        Log.e("123123", "onMove: height = "+ getVisibleHeight());
        onPositionChange();
    }

    /**
     * 松开手指,滚动到刷新高度或者滚动回顶部
     */
    public void onRelease() {
        isTouch = false;
        if (state == State.REFRESHING) {
            if (getVisibleHeight() > refreshHeight) {
                autoScroll.scrollTo(refreshHeight, SCROLL_TO_REFRESH_DURATION);
            }
        } else {
            autoScroll.scrollTo(0, SCROLL_TO_TOP_DURATION);
        }
    }

    public void setRefreshComplete(boolean success) {
        if (success) {
            changeState(State.COMPLETE);
        } else {
            changeState(State.FAILURE);
        }

        if (getVisibleHeight() == 0) {
            changeState(State.RESET);
            return;
        }

        if (!isTouch && getVisibleHeight() > 0) {
            if (success) {
                postDelayed(delayToScrollTopRunnable, showCompletedTime);
            } else {
                postDelayed(delayToScrollTopRunnable, showFailureTime);
            }
        }
    }

    private void changeState(State state) {
        Log.e(TAG, "changeState: " + state.name());
        this.state = state;
        switch (state) {
            case RESET:
                onReset();
                break;
            case PULL:
                onPullStart();
                break;
            case REFRESHING:
                onRefreshing();
                if (refreshing != null) {
                    refreshing.onRefreshCallBack();
                }
                break;
            case COMPLETE:
                onComplete();
                break;
            case FAILURE:
                onFailure();
                break;
        }
    }

    /**
     * header的内容
     *
     * @return view
     */
    public abstract View getContentView();

    /**
     * header回到顶部了
     */
    public abstract void onReset();

    /**
     * header被下拉了
     */
    public abstract void onPullStart();

    /**
     * header高度变化的回调
     */
    public abstract void onPositionChange();

    /**
     * header正在刷新
     */
    public abstract void onRefreshing();

    /**
     * header刷新成功
     */
    public abstract void onComplete();

    /**
     * header刷新失败
     */
    public abstract void onFailure();

    /**
     * 自动刷新
     */
    public void autoRefresh() {
        if (state != State.RESET) {
            return;
        }
        // post可以保证View已经测量完毕
        post(new Runnable() {
            @Override
            public void run() {
                setVisibleHeight(refreshHeight);
                changeState(State.REFRESHING);
            }
        });
    }

    private class AutoScroll implements Runnable {
        private Scroller scroller;
        private int lastY;

        public AutoScroll() {
            scroller = new Scroller(getContext());
        }

        @Override
        public void run() {
            boolean finished = !scroller.computeScrollOffset() || scroller.isFinished();
            if (!finished) {
                int currY = scroller.getCurrY();
                int offset = currY - lastY;
                lastY = currY;
                onMove(offset);
                post(this);
            } else {
                stop();
            }
        }

        public void scrollTo(int to, int duration) {
            int from = getVisibleHeight();
            int distance = to - from;
            stop();
            if (distance == 0) {
                return;
            }
            scroller.startScroll(0, 0, 0, distance, duration);
            post(this);
        }

        private void stop() {
            removeCallbacks(this);
            if (!scroller.isFinished()) {
                scroller.forceFinished(true);
            }
            lastY = 0;
        }
    }

    public enum State {
        RESET, PULL, REFRESHING, FAILURE, COMPLETE
    }

    // 刷新成功,显示500ms成功状态再滚动回顶部
    private Runnable delayToScrollTopRunnable = new Runnable() {
        @Override
        public void run() {
            autoScroll.scrollTo(0, SCROLL_TO_TOP_DURATION);
        }
    };

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (autoScroll != null) {
            autoScroll.stop();
        }

        if (delayToScrollTopRunnable != null) {
            removeCallbacks(delayToScrollTopRunnable);
            delayToScrollTopRunnable = null;
        }

    }
}

具体的逻辑不多解释,因为上一篇博客中已经将事件处理的逻辑说的很清楚了,其实是懒_(:з」∠)_

五、Footer基类

footer的状态有四个RESET, LOADING, FAILURE, NO_MORE

reset:因为加载数据后,footer已经看不见了,所以没必要success状态,直接回到reset就行了
failure: 刷新失败,显示failure状态,用户点击footer就可以重新加载。
no_more:没有更多数据的时候进入这个状态,footer高度设置为0,当下拉刷新或者手动设置后会重新显示。

在这里特别说明一点,footer的contentView不能设置点击事件,事件应该在ListView中有onItemClick处理。
因为如果footer设置了点击事件,那么事件处理会出现问题。因为多点触控的原因,是通过getY() getX()获取坐标的,当手指在footer中滑动时,获取到的坐标位置是相对于footer的。

package com.aitsuki.library;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;

/**
 * Created by AItsuki on 2016/6/23.
 * 逻辑:
 * 拉到最底部的时候显示footer -- 从reset进入loading状态
 * 加载成功后,隐藏footer
 * 加载失败,点击footer可以重新加载
 * <p/>
 * 1. loading失败 -- 到failure状态
 * 2. loading成功 -- 到Reset状态,隐藏footer
 */
public abstract class LoadMoreFooter extends LinearLayout {


    private static final float DRAG_RATE = 0.5f;
    private final AutoScroll autoScroll;
    private final int maxDragDistance;

    private View contentView;   // footer
    private State state = State.RESET;
    private final int footerHeight;
    private Loading loading;
    private boolean canLoadMore = true;

    public LoadMoreFooter(Context context) {
        super(context);
        maxDragDistance = Utils.dip2px(context, 500); // 默认500dp
        autoScroll = new AutoScroll();
        contentView = getContentView();
        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        addView(contentView, lp);
        measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        footerHeight = getMeasuredHeight();
    }

    protected void bindLoading(Loading loading) {
        this.loading = loading;
    }

    protected void setVisibleHeight(int height) {
        LayoutParams lp = (LayoutParams) contentView.getLayoutParams();
        lp.height = height;
        contentView.setLayoutParams(lp);
    }

    public int getVisibleHeight() {
        return contentView.getLayoutParams().height;
    }

    protected void setLoadMoreComplete(boolean successful) {
        if (successful) {
            changeState(State.RESET);
        } else {
            changeState(State.FAILURE);
        }
    }

    protected void onScroll(int lastVisiblePosition, int count) {
        // 当没有数据的时候,不显示footer
        if(count <= 0) {
            contentView.setVisibility(GONE);
        } else if(count > 0 && contentView.getVisibility() == GONE) {
            contentView.setVisibility(VISIBLE);
        }

        if (count > 0 && lastVisiblePosition >= count && state == State.RESET && canLoadMore) {
            changeState(LoadMoreFooter.State.LOADING);
        }
    }

    protected void onPress() {
        autoScroll.stop();
    }

    protected void onRelease() {
        int height = canLoadMore ? footerHeight : 0;
        if(getVisibleHeight() > height) {
            autoScroll.scrollTo(height, 250);
        }
    }

    public State getState() {
        return state;
    }

    protected void changeState(State state) {
        this.state = state;
        switch (state) {
            case RESET:
                onReset();
                break;
            case LOADING:
                onLoading();
                if (loading != null) {
                    loading.onLoadMoreCallBack();
                }
                break;
            case FAILURE:
                onFailure();
                break;
            case NO_MORE:
                onNoMore();
                break;
        }
    }

    protected void onMove(float offset) {
        if (offset == 0) {
            return;
        }

        float dragRate = DRAG_RATE;
        if (offset < 0) {
            offset = offset * dragRate - 0.5f;
            if(getVisibleHeight() > footerHeight) {
                // 因为忽略了refreshHeight,所以这里会超出最大高度,直接价格判断省事。
                if(getVisibleHeight() >= maxDragDistance) {
                    offset = 0;
                } else {
                    float extra = getVisibleHeight() - footerHeight;
                    float extraPercent = Math.min(1, extra / maxDragDistance);
                    dragRate = Utils.calcDragRate(extraPercent);
                    offset = offset * dragRate - 0.5f;
                }
            }
        }

        // 往上滑动的时候才增加footer的高度,所以offset为负数。
        int height = canLoadMore ? footerHeight : 0;
        int target = (int) Math.max(height, getVisibleHeight() - offset);
        setVisibleHeight(target);
    }

    protected void setCanLoadMore(boolean canLoadMore) {
        // 设置不能加载更多
        if(!canLoadMore && state != State.NO_MORE) {
            changeState(State.NO_MORE);
            setVisibleHeight(0);
            this.canLoadMore = false;
        } else if(canLoadMore && state == State.NO_MORE) {
            changeState(State.RESET);
            setVisibleHeight(footerHeight);
            this.canLoadMore = true;
        }
    }

    private class AutoScroll implements Runnable {
        private Scroller scroller;
        private int lastY;

        public AutoScroll() {
            scroller = new Scroller(getContext());
        }

        @Override
        public void run() {
            boolean finished = !scroller.computeScrollOffset() || scroller.isFinished();
            if (!finished) {
                int currY = scroller.getCurrY();
                int offset = currY - lastY;
                lastY = currY;
                onMove(offset);
                post(this);
            } else {
                stop();
            }
        }

        public void scrollTo(int to, int duration) {
            int from = getVisibleHeight();
            int distance =  to - from;
            stop();
            if (distance == 0) {
                return;
            }
            scroller.startScroll(0, 0, 0, -distance, duration);
            post(this);
        }

        private void stop() {
            removeCallbacks(this);
            if (!scroller.isFinished()) {
                scroller.forceFinished(true);
            }
            lastY = 0;
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (autoScroll != null) {
            autoScroll.stop();
        }
    }

    protected void onItemClick() {
        if(state == State.FAILURE) {
            changeState(State.LOADING);
        }
    }

    public enum State {
        RESET, LOADING, FAILURE, NO_MORE
    }

    protected abstract View getContentView();

    protected abstract void onReset();

    protected abstract void onNoMore();

    protected abstract void onLoading();

    protected abstract void onFailure();
}

六、 AListView的完整代码

package com.aitsuki.library;

import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ListView;

/**
 * Created by AItsuki on 2016/6/21.
 * 下拉刷新:支持多点触控,支持自定义header,完美体验!
 * 加载更多:在这里有两种方式
 * 1. 监听scrollState,在idle状态下并且到达listView底部,显示加载更多
 * 2. 监听scrollState,滚动到某个位置(比如倒数第3个item时就开始loadmore)
 */
public class AListView extends ListView implements AbsListView.OnScrollListener, AdapterView.OnItemClickListener,Refreshing, Loading {

    private int activePointerId;
    private float lastY;
    private OnRefreshListener onRefreshListener;
    private OnLoadMoreListener onLoadMoreListener;
    private RefreshHeader refreshHeader;
    private LoadMoreFooter loadMoreFooter;

    private OnScrollListener scrollListener; // user's scroll listener
    private OnItemClickListener onItemClickListener;    // user's itemClick listener

    private boolean waitingLoadingCompletedToRefresh;   // 等待加载更多成功后去调用刷新
    private boolean waitingRefreshCompletedToLoadMore;  // 等待刷新成功后去调用加载更多

    public enum CallBackMode {
        NORMAL, ENQUEUE
    }

    private CallBackMode callBackMode = CallBackMode.ENQUEUE;

    public AListView(Context context) {
        super(context);
        initView();
    }

    public AListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public AListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    public void setCanLoadMore(boolean canLoadMore) {
        loadMoreFooter.setCanLoadMore(canLoadMore);
    }

    private void initView() {
        super.setOnScrollListener(this);
        super.setOnItemClickListener(this);

        setOverScrollMode(OVER_SCROLL_NEVER);

        QQHeader QQHeader = new QQHeader(getContext());
        setRefreshHeader(QQHeader);

        QQFooter footer = new QQFooter(getContext());
        setLoadMoreFooter(footer);

    }

    /**
     * 设置刷新头部
     * @param refreshHeader header
     */
    public void setRefreshHeader(RefreshHeader refreshHeader) {
        if (refreshHeader != null && this.refreshHeader != refreshHeader) {
            removeHeaderView(this.refreshHeader);
            LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            refreshHeader.setLayoutParams(lp);
            refreshHeader.bindRefreshing(this);
            addHeaderView(refreshHeader, null, false);
            this.refreshHeader = refreshHeader;
        }
    }

    /**
     * 设置加载更多的footer
     * @param footer footer
     */
    public void setLoadMoreFooter(LoadMoreFooter footer) {
        if (footer != null && loadMoreFooter != footer) {
            removeFooterView(footer);
            LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            footer.setLayoutParams(lp);
            footer.bindLoading(this);
            addFooterView(footer);
            this.loadMoreFooter = footer;
        }
    }

    /**
     * 刷新侦听
     */
    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
        this.onRefreshListener = onRefreshListener;
    }

    /**
     * 加载更多侦听
     */
    public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
    }

    /**
     * 当数据加载完毕后,需要调用此方法让header回到原位
     * @param success 刷新成功,刷新失败(header的状态不一致:complete或者failure)
     */
    public void refreshComplete(boolean success) {
        refreshHeader.setRefreshComplete(success);

        // 刷新成功后,将failure状态重置
        if (success && loadMoreFooter.getState() == LoadMoreFooter.State.FAILURE) {
            loadMoreFooter.changeState(LoadMoreFooter.State.RESET);
        }

        // 刷新成功后,如果需要加载更多,那么回调onLoadMore
        if (waitingRefreshCompletedToLoadMore) {
            onLoadMoreCallBack();
            waitingRefreshCompletedToLoadMore = false;
        }
    }

    /**
     * 当数据加载完毕后,需要调用此方法重置footer的状态
     * @param success 刷新成功:隐藏了。  刷新失败,会显示失败状态(点击重试)
     */
    public void loadMoreComplete(boolean success) {
        loadMoreFooter.setLoadMoreComplete(success);
        Log.e("123123", "loadMoreComplete");
        // 加载成功后,如果需要刷新,那么回调onRefresh
        if (waitingLoadingCompletedToRefresh) {
            onRefreshCallBack();
            waitingLoadingCompletedToRefresh = false;
        }
    }

    /**
     * 自动刷新
     */
    public void autoRefresh() {
        refreshHeader.autoRefresh();
    }

    /**
     * 当刷新和加载更多同时进行时,可能会导致分页加载时的页面错误。<br/>
     * 可以通过此方法设置处理方式:<br/>
     * NORMAL: 不做处理,但是开发者必须自己处理。<br/>
     * ENQUEUE: 队列模式,刷新和加载更多同时进行时,只会回调其中一个,成功后再回调另一个(默认ENQUEUE)
     */
    public void setCallBackMode(CallBackMode callBackMode) {
        this.callBackMode = callBackMode;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int actionMasked = ev.getActionMasked();

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN:
                activePointerId = ev.getPointerId(0);
                lastY = ev.getY(0);
                refreshHeader.onPress();
                loadMoreFooter.onPress();
                break;
            case MotionEvent.ACTION_MOVE:
                float y = ev.getY(MotionEventCompat.findPointerIndex(ev, activePointerId));
                float diffY = y - lastY;
                lastY = y;

                // --------------header----------------------
                if (getFirstVisiblePosition() == 0 && diffY > 0) {
                    refreshHeader.onMove(diffY);
                } else if (diffY < 0 && refreshHeader.getVisibleHeight() > 0) {
                    // 往上滑动,header回去的过程中,listView也会跟着滚动,导致滑动致header消失后,其实header高度还没有到0
                    // setSelection可以保证header一直处于顶部
                    refreshHeader.onMove(diffY);
                    setSelection(0);
                }

                // --------------footer--------------------
                int lastVisible = getLastVisiblePosition();
                int lastPosition = getAdapter().getCount() - 1;
                if (getFirstVisiblePosition() != 0 && lastVisible == lastPosition) {
                    loadMoreFooter.onMove(diffY);
                }


                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                onSecondaryPointerDown(ev);
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                refreshHeader.onRelease();
                loadMoreFooter.onRelease();
                break;
        }
        return super.onTouchEvent(ev);
    }

    private void onSecondaryPointerDown(MotionEvent ev) {
        int pointerIndex = MotionEventCompat.getActionIndex(ev);
        lastY = ev.getY(pointerIndex);
        activePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
    }

    private void onSecondaryPointerUp(MotionEvent ev) {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
        if (pointerId == activePointerId) {
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            lastY = ev.getY(newPointerIndex);
            activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
    }

    /**
     * Refreshing接口的方法, 当header进入刷新状态时会回调此方法
     */
    @Override
    public void onRefreshCallBack() {
        if (onRefreshListener != null) {
            if (loadMoreFooter.getState() == LoadMoreFooter.State.LOADING
                    && callBackMode == CallBackMode.ENQUEUE) {
                // 等待加载更多完毕后回调onRefresh方法
                waitingLoadingCompletedToRefresh = true;
            } else {
                onRefreshListener.onRefresh();
            }
        }
    }

    /**
     * Loading接口的方法,当footer进入加载状态时会回调此方法
     */
    @Override
    public void onLoadMoreCallBack() {
        if (onLoadMoreListener != null) {
            if (refreshHeader.getState() == RefreshHeader.State.REFRESHING
                    && callBackMode == CallBackMode.ENQUEUE) {
                // 等待刷新完毕后回调onLoadMore方法
                waitingRefreshCompletedToLoadMore = true;
            } else {
                onLoadMoreListener.onLoadMore();
            }
        }
    }

    @Override
    public void setOnScrollListener(OnScrollListener l) {
        this.scrollListener = l;
    }

    /**
     * onScrollStateChanged和onScroll的区别:
     * <p/>
     * ListView滚动时,会一直执行onScroll。 而scrollState只会执行三次
     * <p/>
     * 在初始化时, onScroll会执行, 而ScrollState不会
     * <p/>
     * 在顶部或者底部滑动时,onScroll不会执行, 而ScrollState执行
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollListener != null) {
            scrollListener.onScrollStateChanged(view, scrollState);
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (scrollListener != null) {
            scrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }
        int lastVisiblePosition = getLastVisiblePosition();
        int count = totalItemCount - getHeaderViewsCount() - getFooterViewsCount();
        if (loadMoreFooter != null) {
            loadMoreFooter.onScroll(lastVisiblePosition, count);
        }
    }

    @Override
    public void setOnItemClickListener(OnItemClickListener listener) {
        onItemClickListener = listener;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

        /*
         * 在加载更多成功的那一刻点击item,可能会报空指针
         * 因为此时loadMoreFooter这个item已经从listView中暂时移除
         * 所以在这里做了parent判断
         */
        int footerPosition = -1;
        ViewParent viewParent = loadMoreFooter.getParent();
        if(viewParent != null) {
            footerPosition = getPositionForView(loadMoreFooter);
            if(position == footerPosition) {
                loadMoreFooter.onItemClick();
            }
        }

        if(onItemClickListener != null && position != footerPosition) {
            onItemClickListener.onItemClick(parent, view, position, id);
        }
    }
}

七、Demo地址

https://github.com/AItsuki/AListView

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页