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

ListView上拉加載和下拉刷新多種實現方式

編輯:關於Android編程

該篇為ListView下拉刷新和上拉加載實現的各種方法大合集。可能在具體的細節邏輯上處理不太到位,但基本上完成邏輯的實現。細節方面,個人可以根據自己的需求進行完善。

該博客將以四種思路來完成下拉刷新和上拉加載

自定義View實現上拉加載和下拉刷新 使用PullToRefresh 實現上拉加載和下拉刷新 使用Ultra-Pull-To-Refresh實現上拉加載和下拉刷新 使用SwipeToRefreshLayout實現上拉加載和下拉刷新

如果你對ListView不夠熟悉,歡迎看我之前的博客 ListView使用總結

自定義View實現上拉加載和下拉刷新

該方法,我們通過完全自定義的方式實現,不添加任何依賴庫和jar包。純原生。

首先看一下我們實現的效果

這裡寫圖片描述

實現的關鍵點:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCs6qPGNvZGU+TGlzdFZpZXc8L2NvZGU+zO280823sry+1rrNtdeyvL7WoaMgzai5/bjEseTNt7K8vta1xDxjb2RlPnBhZGRpbmdUb3A8L2NvZGU+1rWjrMC0v9jWxr/YvP61xM/Uyr66zdL+stgguPm+3c7Sw8e7rLavtcTXtMyso6y2r8ys0N64xM23sr+yvL7Wus2117K/sry+1qGjDQo8cD6/tNK7z8K0+sLro7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> public class CustomRefreshListView extends ListView implements OnScrollListener{ /** * 頭布局 */ private View headerView; /** * 頭部布局的高度 */ private int headerViewHeight; /** * 頭部旋轉的圖片 */ private ImageView iv_arrow; /** * 頭部下拉刷新時狀態的描述 */ private TextView tv_state; /** * 下拉刷新時間的顯示控件 */ private TextView tv_time; /** * 底部布局 */ private View footerView; /** * 底部旋轉progressbar */ private ProgressBar pb_rotate; /** * 底部布局的高度 */ private int footerViewHeight; /** * 按下時的Y坐標 */ private int downY; private final int PULL_REFRESH = 0;//下拉刷新的狀態 private final int RELEASE_REFRESH = 1;//松開刷新的狀態 private final int REFRESHING = 2;//正在刷新的狀態 /** * 當前下拉刷新處於的狀態 */ private int currentState = PULL_REFRESH; /** * 頭部布局在下拉刷新改變時,圖標的動畫 */ private RotateAnimation upAnimation,downAnimation; /** * 當前是否在加載數據 */ private boolean isLoadingMore = false; public CustomRefreshListView(Context context) { this(context,null); } public CustomRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init(){ //設置滑動監聽 setOnScrollListener(this); //初始化頭布局 initHeaderView(); //初始化頭布局中圖標的旋轉動畫 initRotateAnimation(); //初始化為尾布局 initFooterView(); } /** * 初始化headerView */ private void initHeaderView() { headerView = View.inflate(getContext(), R.layout.head_custom_listview, null); iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow); pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate); tv_state = (TextView) headerView.findViewById(R.id.tv_state); tv_time = (TextView) headerView.findViewById(R.id.tv_time); //測量headView的高度 headerView.measure(0, 0); //獲取高度,並保存 headerViewHeight = headerView.getMeasuredHeight(); //設置paddingTop = -headerViewHeight;這樣,該控件被隱藏 headerView.setPadding(0, -headerViewHeight, 0, 0); //添加頭布局 addHeaderView(headerView); } /** * 初始化旋轉動畫 */ private void initRotateAnimation() { upAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); upAnimation.setDuration(300); upAnimation.setFillAfter(true); downAnimation = new RotateAnimation(-180, -360, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); downAnimation.setDuration(300); downAnimation.setFillAfter(true); } //初始化底布局,與頭布局同理 private void initFooterView() { footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null); footerView.measure(0, 0); footerViewHeight = footerView.getMeasuredHeight(); footerView.setPadding(0, -footerViewHeight, 0, 0); addFooterView(footerView); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //獲取按下時y坐標 downY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: if(currentState==REFRESHING){ //如果當前處在滑動狀態,則不做處理 break; } //手指滑動偏移量 int deltaY = (int) (ev.getY() - downY); //獲取新的padding值 int paddingTop = -headerViewHeight + deltaY; if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0){ //向下滑,且處於頂部,設置padding值,該方法實現了頂布局慢慢滑動顯現 headerView.setPadding(0, paddingTop, 0, 0); if(paddingTop>=0 && currentState==PULL_REFRESH){ //從下拉刷新進入松開刷新狀態 currentState = RELEASE_REFRESH; //刷新頭布局 refreshHeaderView(); }else if (paddingTop<0 && currentState==RELEASE_REFRESH) { //進入下拉刷新狀態 currentState = PULL_REFRESH; refreshHeaderView(); } return true;//攔截TouchMove,不讓listview處理該次move事件,會造成listview無法滑動 } break; case MotionEvent.ACTION_UP: if(currentState==PULL_REFRESH){ //仍處於下拉刷新狀態,未滑動一定距離,不加載數據,隱藏headView headerView.setPadding(0, -headerViewHeight, 0, 0); }else if (currentState==RELEASE_REFRESH) { //滑倒一定距離,顯示無padding值得headcView headerView.setPadding(0, 0, 0, 0); //設置狀態為刷新 currentState = REFRESHING; //刷新頭部布局 refreshHeaderView(); if(listener!=null){ //接口回調加載數據 listener.onPullRefresh(); } } break; } return super.onTouchEvent(ev); } /** * 根據currentState來更新headerView */ private void refreshHeaderView(){ switch (currentState) { case PULL_REFRESH: tv_state.setText("下拉刷新"); iv_arrow.startAnimation(downAnimation); break; case RELEASE_REFRESH: tv_state.setText("松開刷新"); iv_arrow.startAnimation(upAnimation); break; case REFRESHING: iv_arrow.clearAnimation();//因為向上的旋轉動畫有可能沒有執行完 iv_arrow.setVisibility(View.INVISIBLE); pb_rotate.setVisibility(View.VISIBLE); tv_state.setText("正在刷新..."); break; } } /** * 完成刷新操作,重置狀態,在你獲取完數據並更新完adater之後,去在UI線程中調用該方法 */ public void completeRefresh(){ if(isLoadingMore){ //重置footerView狀態 footerView.setPadding(0, -footerViewHeight, 0, 0); isLoadingMore = false; }else { //重置headerView狀態 headerView.setPadding(0, -headerViewHeight, 0, 0); currentState = PULL_REFRESH; pb_rotate.setVisibility(View.INVISIBLE); iv_arrow.setVisibility(View.VISIBLE); tv_state.setText("下拉刷新"); tv_time.setText("最後刷新:"+getCurrentTime()); } } /** * 獲取當前系統時間,並格式化 * @return */ private String getCurrentTime(){ SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); return format.format(new Date()); } private OnRefreshListener listener; public void setOnRefreshListener(OnRefreshListener listener){ this.listener = listener; } public interface OnRefreshListener{ void onPullRefresh(); void onLoadingMore(); } /** * SCROLL_STATE_IDLE:閒置狀態,就是手指松開 * SCROLL_STATE_TOUCH_SCROLL:手指觸摸滑動,就是按著來滑動 * SCROLL_STATE_FLING:快速滑動後松開 */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if(scrollState==OnScrollListener.SCROLL_STATE_IDLE && getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){ isLoadingMore = true; footerView.setPadding(0, 0, 0, 0);//顯示出footerView setSelection(getCount());//讓listview最後一條顯示出來,在頁面完全顯示出底布局 if(listener!=null){ listener.onLoadingMore(); } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }

代碼的注釋寫的比較詳細,在這裡只說明主要的邏輯。

下拉刷新

下拉刷新是通過設置setOnTouchListener()方法,監聽觸摸事件,通過手指滑動的不同處理實現相應邏輯。

實現比較復雜,分為了三個情況,初始狀態(顯示下拉刷新),釋放刷新狀態,刷新狀態。

其中下拉刷新狀態和釋放狀態的改變,是由於手指滑動的不同距離,是在MotionEvent.ACTION_MOVE中進行判斷,該判斷不處理任何數據邏輯,只是根據手指滑動的偏移量該表UI的顯示。

刷新狀態的判斷是在MotionEvent.ACTION_UP手指抬起時判斷的。這很好理解,因為最終下拉刷新是否加載數據的確定,是由我們手指離開屏幕時與初始值的偏移量確定的。如果我們的偏移量小於了頭布局的高度,代表不刷新,繼續隱藏頭布局。如果偏移量大於了頭布局的高度,代表刷新,修改UI,同時通過接口回調,讓其持有者進行加載數據。

上拉加載

上拉加載和下拉刷新不同,他的視線較為簡單,我們通過ListView的滾動監聽進行處理相應邏輯。即setOnScrollListener(this);

該方法需要實現兩個回調方法

public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount):滾動監聽的調用。 public void onScrollStateChanged(AbsListView view, int scrollState):滑動狀態改變的回調。其中scrollState為回調的狀態,可能值為
SCROLL_STATE_IDLE:閒置狀態,手指松開後的狀態回調 SCROLL_STATE_TOUCH_SCROLL:手指觸摸滑動的狀態回調 SCROLL_STATE_FLING:手指松開後慣性滑動的回調

我們在onScrollStateChanged中進行判斷,主要判斷一下條件;

是否是停止狀態 是否滑倒最後 是否正在加載數據

如果符合條件,則開始加載數據,通過接口回調。

使用PullToRefresh 實現上拉加載和下拉刷新

該類庫已經很久沒有更新過了,該類庫因為作者的不更新,導致局限性非常大。在這裡十分的不推薦,但還是說一下簡單的用法

Android-PullToRefresh">下載地址

添加控件
 
    

因為其繼承ListView,所以其加載數據用法和ListView一樣

代碼設置相應屬性
private void initPTR() {
        // TODO 初始化下拉刷新控件的相關設置
        //1. 設置刷新模式
        ptrListView.setMode(Mode.BOTH);

        //2. 設置下拉或上拉事件監聽
        ptrListView.setOnRefreshListener(new OnRefreshListener2() {

            @Override
            public void onPullDownToRefresh(PullToRefreshBase refreshView) {
                // TODO 下拉刷新數據
                refreshData(false);
            }

            @Override
            public void onPullUpToRefresh(PullToRefreshBase refreshView) {
                // TODO 上拉加載下頁數據
                loadData(true);
            }
        });

        //3. 定制下拉或上拉布局中相應的提示信息與加載圖標
        //獲取下拉布局的代理對象
        ILoadingLayout proxyPullDown = 
                ptrListView.getLoadingLayoutProxy(true,false);
        proxyPullDown.setPullLabel("下拉刷新");
        proxyPullDown.setReleaseLabel("放開以刷新");
        proxyPullDown.setRefreshingLabel("正在玩命地加載....");
        proxyPullDown.setLoadingDrawable(getResources().getDrawable(R.drawable.ic_launcher));


        ILoadingLayout proxyPullUp = 
                ptrListView.getLoadingLayoutProxy(false,true);
        proxyPullUp.setPullLabel("上拉加載更多");
        proxyPullUp.setReleaseLabel("放開以加載");
        proxyPullUp.setRefreshingLabel("正在玩命地加載....");
        proxyPullUp.setLoadingDrawable(getResources().getDrawable(android.R.drawable.ic_menu_camera));

    }

在其中分為了三部:

ptrListView.setMode(Mode.BOTH);Mode.BOTH表示上拉加載和下拉刷新都有 setOnRefreshListener():注意其傳入參數為OnRefreshListener2,如果傳入OnRefreshListener,表示只有刷新 getLoadingLayoutProxy()方法,傳入參數的不同分別獲得上拉刷新和下拉加載的布局代理對象 完成數據的加載,結束下拉刷新或上拉加載,調用ptrListView.onRefreshComplete();

這個刷新沒有例子,上面還是我以前總結的筆記。該三方庫已經有3年沒有更新了。真的不推薦使用啊。

使用Ultra-Pull-To-Refresh實現上拉加載和下拉刷新

Ultra-Pull-To-Refresh是一個功能非常強大的類庫,通過他,我們可以實現非常豐富的下拉刷新視圖,並且他支持幾乎所有的控件的下拉刷新(不僅僅是ListView),但該視圖不支持上拉加載,作者可能在考慮此庫設計時的想法與Google官方的SwipeRefreshLayout的理念符合。即刷新可能是許多控件都需要,而上拉加載只有列表視圖需要。

那麼我們就開始嘗試使用他吧。

Android Studio 導入Ultra

在工程的build.gradle中,加上如下代碼
allprojects {
    repositories {
        jcenter()
        mavenCentral();
        maven {
            url 'https://oss.sonatype.org/content/repositories/snapshots'
        }
    }
}
添加依賴包
compile 'in.srain.cube:ultra-ptr:1.0.11'
Clean一下工程即可

Eclipse 導入 Ultra

因為作者提供的只有AndroidStudio版本,所以,我把它代碼導出到了一個Eclipse工程中,直接添加依賴即可。

Ultra-Pull-To-Refresh Eclipse 版本下載地址

簡單使用

添加xml文件:activity_listview_ultra_refresh




    

        <framelayout android:background="#f00" android:id="@+id/ultra_refresh_frame" android:layout_height="match_parent" android:layout_width="match_parent" android:paddingtop="100dp">
        </framelayout>
    


 

添加了一個PtrClassicFrameLayout包含了一個FrameLayout.

看一下java代碼

public class UltraRefreshActivity extends Activity {


    private PtrClassicFrameLayout ptrFrame;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_listview_ultra_refresh);

        ptrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr_frame));

        ptrFrame.setLastUpdateTimeRelateObject(this);

        //下拉刷新的阻力,下拉時,下拉距離和顯示頭部的距離比例,值越大,則越不容易滑動
        ptrFrame.setRatioOfHeaderHeightToRefresh(1.2f);

        ptrFrame.setDurationToClose(200);//返回到刷新的位置(暫未找到)

        ptrFrame.setDurationToCloseHeader(1000);//關閉頭部的時間 // default is false

        ptrFrame.setPullToRefresh(false);//當下拉到一定距離時,自動刷新(true),顯示釋放以刷新(false)

        ptrFrame.setKeepHeaderWhenRefresh(true);//見名只意

        ptrFrame.setPtrHandler(new PtrHandler() {
            @Override
            public boolean checkCanDoRefresh(PtrFrameLayout frame,
                                             View content, View header) {
                return PtrDefaultHandler.checkContentCanBePulledDown(frame,
                        content, header);
            }

            @Override
            public void onRefreshBegin(PtrFrameLayout frame) {

                //數據刷新的回調

                ptrFrame.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        ptrFrame.refreshComplete();
                    }
                }, 1500);
            }
        });
    }
}

看一下效果

這裡寫圖片描述

通過實現過程,我們可以發現,其下拉刷新和其包含的控件沒有之間聯系:也就是說我們可以在PtrClassicFrameLayout包含任何子控件。

他可以設置的一些常量,代碼注釋中都有體現。而他有兩個關鍵性的方法:

ptrFrame.setPtrHandler(new PtrHandler()):數據刷新的接口回調。包含兩個回調方法
checkCanDoRefresh:是否能夠刷新。 onRefreshBegin:開始刷新的回調。 ptrFrame.addPtrUIHandler(new PtrUIHandler()):UI更新接口的回調。其中PtrUIHandler為一個接口,其定義如下
public interface PtrUIHandler {

    /**
     * When the content view has reached top and refresh has been completed, view will be reset.
     *
     * @param frame
     */
    public void onUIReset(PtrFrameLayout frame);

    /**
     * prepare for loading
     *
     * @param frame
     */
    public void onUIRefreshPrepare(PtrFrameLayout frame);

    /**
     * perform refreshing UI
     */
    public void onUIRefreshBegin(PtrFrameLayout frame);

    /**
     * perform UI after refresh
     */
    public void onUIRefreshComplete(PtrFrameLayout frame);

    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator);
}

含義如下:

onUIRefreshPrepare:開始下拉之前的接口回調。 onUIRefreshBegin:開始刷新的接口回調。 onUIRefreshComplete:刷新完成的接口回調。 onUIReset:刷新完成之後,UI消失之後的接口回調。 onUIPositionChange:下拉滑動的接口回調,多次調用。
isUnderTouch :手指是否觸摸 status:狀態值 ptrIndicator:滑動偏移量等值的封裝對象。

那麼對於onUIPositionChange中,都是返回了那些值呢,我寫了一個自定義的HeadView,並設置高位100,同時打印相應的Log。在這只貼onUIPositionChange中的Log代碼。

 @Override
    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
        int headerHeight = ptrIndicator.getHeaderHeight();//頭部的高度
        int lastPosY = ptrIndicator.getLastPosY();//上一次滑動的Y偏移值
        int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//刷新需要滑動的偏移量
        float offsetY = ptrIndicator.getOffsetY();//當前與上一次滑動處理的偏移值
        int currentPosY = ptrIndicator.getCurrentPosY();//當前系統偏移值


        Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY);
    }

看一下打印信息

onUIRefreshPrepare
isUnderTouchtrueheadHeight: 100 lastPosY 0 offsetToRefresh 120 offsetY 23.025852 currentPosY 23
............................................................
isUnderTouchtrueheadHeight: 100 lastPosY 299 offsetToRefresh 120 offsetY 2.1575928 currentPosY 301
onUIRefreshBegin
isUnderTouchfalseheadHeight: 100 lastPosY 301 offsetToRefresh 120 offsetY 2.1575928 currentPosY 278
............................................................
isUnderTouchfalseheadHeight: 100 lastPosY 101 offsetToRefresh 120 offsetY 2.1575928 currentPosY 100
onUIRefreshComplete
isUnderTouchfalseheadHeight: 100 lastPosY 100 offsetToRefresh 120 offsetY 2.1575928 currentPosY 96
............................................................
isUnderTouchtrueheadHeight: 100 lastPosY 239 offsetToRefresh 120 offsetY 1.324391 currentPosY 240
onUIRefreshComplete
isUnderTouchfalseheadHeight: 100 lastPosY 240 offsetToRefresh 120 offsetY 1.324391 currentPosY 223
............................................................
isUnderTouchfalseheadHeight: 100 lastPosY 2 offsetToRefresh 120 offsetY 1.324391 currentPosY 1
onUIReset
isUnderTouchfalseheadHeight: 100 lastPosY 1 offsetToRefresh 120 offsetY 1.324391 currentPosY 0

關於每個字段的含義,代碼注釋已經說的很清楚了,不在多述。

Ultra自定義刷新

看一下我們將要實現的效果:

這裡寫圖片描述

自定義頭部的代碼實現


public class CustomUltraRefreshHeader extends RelativeLayout implements PtrUIHandler {

    CircleView mCircleView;

    TextView mDescText;

    private ObjectAnimator anim;

    public CustomUltraRefreshHeader(Context context) {
        this(context,null);
    }

    public CustomUltraRefreshHeader(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomUltraRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initView();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec,100);
    }

    /**
     * 初始化布局
     */
    private void initView() {

        int circlewidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());

        mCircleView = new CircleView(getContext());

        LinearLayout.LayoutParams circleParams = new LinearLayout.LayoutParams(circlewidth,circlewidth);

        mCircleView.setLayoutParams(circleParams);

        mDescText = new TextView(getContext());

        LinearLayout.LayoutParams descParams = new LinearLayout.LayoutParams(circlewidth*3, ViewGroup.LayoutParams.WRAP_CONTENT);

        descParams.gravity = Gravity.CENTER;
        descParams.setMargins(circlewidth/2,0,0,0);
        mDescText.setLayoutParams(descParams);
        mDescText.setTextSize(12);
        mDescText.setTextColor(Color.GRAY);
        mDescText.setText("下拉刷新");

        //添加線性的父布局
        LinearLayout ll = new LinearLayout(getContext());
        RelativeLayout.LayoutParams llParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        llParams.addRule(CENTER_IN_PARENT);
        ll.setLayoutParams(llParams);
        ll.setPadding(10,10,10,10);

        ll.addView(mCircleView);
        ll.addView(mDescText);

        addView(ll);
    }

    @Override
    public void onUIReset(PtrFrameLayout frame) {
        //重置時,將動畫置為初始狀態
        mCircleView.setRotation(0f);
        Log.i("info","onUIReset");
    }

    @Override
    public void onUIRefreshPrepare(PtrFrameLayout frame) {
        mDescText.setText("下拉加載數據");
        Log.i("info","onUIRefreshPrepare");
    }

    @Override
    public void onUIRefreshBegin(PtrFrameLayout frame) {

        //開始刷新,啟動動畫
        anim = ObjectAnimator.ofFloat(mCircleView, "rotation", mCircleView.getRotation(), mCircleView.getRotation()+360f)
                .setDuration(500);
        anim.setRepeatCount(ValueAnimator.INFINITE);
        anim.setRepeatMode(ValueAnimator.RESTART);
        anim.start();

        mDescText.setText("正在加載數據");
        Log.i("info","onUIRefreshBegin");
    }

    @Override
    public void onUIRefreshComplete(PtrFrameLayout frame) {
        anim.cancel();
        mDescText.setText("加載完成");
        Log.i("info","onUIRefreshComplete");
    }

    @Override
    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
        int headerHeight = ptrIndicator.getHeaderHeight();//頭部的高度
        int lastPosY = ptrIndicator.getLastPosY();//上一次滑動的Y偏移值
        int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//舒心需要滑動的偏移量
        float offsetY = ptrIndicator.getOffsetY();//當前與上一次滑動處理的偏移值
        int currentPosY = ptrIndicator.getCurrentPosY();//當前系統偏移值

        if (isUnderTouch&&status== PtrFrameLayout.PTR_STATUS_PREPARE) {

            mCircleView.setRotation(currentPosY);
            if(currentPosY= offsetToRefresh){
                //表示不刷新了
                mDescText.setText("下拉加載數據");
            }else if(currentPosY>offsetToRefresh&&lastPosY<=offsetToRefresh){
                mDescText.setText("松開加載更多");
            }
        }

       /* if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
            if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {*//*
        Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY);*/
    }
}

在這裡我們實現了PtrUIHandler接口,用於下拉刷新時的接口會調,有兩點說明:

使用的CircleView為自定義控件,就是圖中的轉盤,在我以前的博客中有相應的實現。RecyclerView 下拉刷新和上拉加載

在動態改變現實視圖時,需要注意的便是onUIPositionChange,我們只有在手指觸摸且statusPtrFrameLayout.PTR_STATUS_PREPARE才修改我們的屬性,其他時候不能修改。因為該方法會一直回調。

封裝自定義的UltraRefreshListView繼承ListView,並實現了PtrHandler接口,用以數據更新回調。

public class UltraRefreshListView extends ListView implements PtrHandler,AbsListView.OnScrollListener {

    private UltraRefreshListener mUltraRefreshListener;

    /**
     * 根布局
     */
    private View footView;


    /**
     * 是否正在加載數據
     */
    private boolean isLoadData = false;

    /**
     * 是否是下拉刷新,主要在處理結果時使用
     */
    private boolean isRefresh = false;

    public UltraRefreshListView(Context context) {
        this(context,null);
    }

    public UltraRefreshListView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public UltraRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化布局,及添加一個跟布局
        initView();
    }

    private void initView() {
        footView = LayoutInflater.from(getContext()).inflate(R.layout.foot_ultra_refresh_listview, null);

        setOnScrollListener(this);
    }



    @Override
    public void onRefreshBegin(PtrFrameLayout frame) {

        isLoadData  =true;
        isRefresh =true;
        //下拉刷新的回調
        if(mUltraRefreshListener!=null){

            mUltraRefreshListener.onRefresh();
        }
    }


    @Override
    public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
        //  PtrHandler 的接口回調,是否能夠加載數據的判斷
        return !isLoadData&&checkContentCanBePulledDown(frame, content, header);
    }

    // 從PtrHandler的默認實現類 PtrDefaultHandler中找到的,用以判斷是否可以下拉刷新
    public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
        return !canChildScrollUp(content);

    }

    // 從PtrHandler的默認實現類 PtrDefaultHandler中找到的,用以判斷是否可以下拉刷新
    public static boolean canChildScrollUp(View view) {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (view instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) view;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return view.getScrollY() > 0;
            }
        } else {
            return view.canScrollVertically(-1);
        }
    }


    /**
     * 設置ListView的監聽回調
     */
    public void setUltraRefreshListener(UltraRefreshListener mUltraRefreshListener) {
        this.mUltraRefreshListener = mUltraRefreshListener;
    }

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

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        /*Log.i("info","isLoadData:"+isLoadData+" totalItemCount "+totalItemCount+" firstVisibleItem "+
                firstVisibleItem+" visibleItemCount "+ visibleItemCount);
*/
        //加載更多的判斷
        if(totalItemCount>1&&!isLoadData&&totalItemCount==firstVisibleItem+visibleItemCount){
            isRefresh =false;
            isLoadData = true;
            addFooterView(footView);
            mUltraRefreshListener.addMore();
        }
}



    //刷新完成的後調用此方法還原布局
    public void refreshComplete(){
        isLoadData = false;
        if(isRefresh){
            //獲取其父控件,刷新
            ViewParent parent = getParent();
            if(parent instanceof PtrClassicFrameLayout){
                ((PtrClassicFrameLayout) parent).refreshComplete();
            }
        }else{
            removeFooterView(footView);
        }
    }
}

其中有以下幾個關鍵點需要注意:

isRefresh:標清當前是正在下拉刷新還是在正在加載更多,這樣就不用其調用者在使用refreshComplete()時,傳入相應參數。 checkCanDoRefresh()方法中,在判斷是否可以刷新時,加入了!isLoadData,該目的是,當正在加載更多時,不允許下拉刷新。 checkContentCanBePulledDown()canChildScrollUp()方法的實現,是從其源碼PtrDefaultHandler中找到的,作者已經給出了比較靠譜的實現方式。

該方法,將下拉刷新和上拉加載整合到了一起,定義一個共同的接口以便進行相應操作的回調

/**
 * 數據接口的回調
 * Created by Alex_MaHao on 2016/5/18.
 */
public interface UltraRefreshListener {

    //下拉刷新
    void onRefresh();

    //上拉加載
    void addMore();
}

最後我們看一下如何使用




    

        

    

/**
 * Created by Alex_MaHao on 2016/5/18.
 */
public class UltraRefreshListActivity extends AppCompatActivity implements UltraRefreshListener {

    private PtrClassicFrameLayout mPtrFrame;

    private List datas = new ArrayList<>();

    private SimpleBaseAdapter mAdapter;

    private UltraRefreshListView mLv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_listview_ultra);

        //查找控件
        mPtrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr));

        mLv = ((UltraRefreshListView) findViewById(R.id.ultra_lv));

        //創建我們的自定義頭部視圖
        CustomUltraRefreshHeader header = new CustomUltraRefreshHeader(this);

        //設置頭部視圖
        mPtrFrame.setHeaderView(header);

        //設置視圖修改的回調,因為我們的CustomUltraRefreshHeader實現了PtrUIHandler
        mPtrFrame.addPtrUIHandler(header);

        //設置數據刷新的會回調,因為UltraRefreshListView實現了PtrHandler
        mPtrFrame.setPtrHandler(mLv);

        mAdapter = new SimpleBaseAdapter(datas);

        mLv.setAdapter(mAdapter);

        //設置數據刷新回調接口
        mLv.setUltraRefreshListener(this);
    }

    @Override
    public void onRefresh() {
        mPtrFrame.postDelayed(new Runnable() {
            @Override
            public void run() {
                datas.clear();
                for(int i = 0;i<20;i++){
                    datas.add("添加了數據~~"+i);
                }
                //刷新完成
                mLv.refreshComplete();
                mAdapter.notifyDataSetChanged();
            }
        },1000);

    }

    @Override
    public void addMore() {
        mPtrFrame.postDelayed(new Runnable() {
            @Override
            public void run() {

                int count = mAdapter.getCount();
                for(int i = count; i< count +10; i++){
                    datas.add("添加了數據~~"+i);
                }
                mAdapter.notifyDataSetChanged();
                //刷新完成
                mLv.refreshComplete();
            }
        },1000);

    }
}

OK,注釋你要看不懂,那我也很糾結該怎麼辦。

該控件非常好用,推薦。

使用SwipeToRefreshLayout實現上拉加載和下拉刷新

雖然之前的博客中說的是RecyclerView的使用,但其使用方法完全相同,這裡不再重復了。

該項目源碼已經共享到我的github,在模塊systemwidgetdemo中。

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