Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 實現newbility的下拉刷新和加載更多的ListView

Android 實現newbility的下拉刷新和加載更多的ListView

編輯:關於Android編程

在上一篇博文的最後,我說要寫一個下拉刷新的ListView和RecyclerView,並且可以直接使用QQheader

本篇博文准備講如何實現下拉刷新和上拉加載,寫的比較爛,篇幅比較長。
先預覽下最終效果吧,不然我怕你們看不下去=。=
這裡寫圖片描述

一、下拉刷新的實現方式

在上一篇博文中,我們通過移動header的位置來實現下拉刷新。
但是這種方式在ListView中並不是十分合適,所以我需要修改一下實現方式。

這裡寫圖片描述vcq9wLTKtc/Wz8LArcui0MKhozwvcD4NCjxoMSBpZD0="二加載更多的實現方式">二、加載更多的實現方式

加載更多一般情況下有三種方式:
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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved