listview下拉刷新和上拉載入更多的多種實現方案

来源:http://www.cnblogs.com/huangjie123/archive/2016/12/25/6220134.html
-Advertisement-
Play Games

listview經常結合下來刷新和上拉載入更多使用,本文總結了三種常用到的方案分別作出說明。 ...


     listview經常結合下來刷新和上拉載入更多使用,本文總結了三種常用到的方案分別作出說明。

     方案一:添加頭佈局和腳佈局
        android系統為listview提供了addfootview和addheadview兩個API。這樣可以直接自定義一個View,以添加視圖的形式實現下來刷新和上拉載入。
     實現步驟
       1、創建一個類繼承ListView:class PullToRefreshListView extends ListView;
       2、在構造方法中添加HeadView:addHeaderView(headView);
       3、獲取HeadView的高。測量控制項的高可以有兩方法getMeasuredHeight和getHeight,getMeasuredHeight()在onMeasure方法執行之後才能獲取到;getHeight()  在onLayout方法執行之後才能獲取到值;
       4、顯示和隱藏headView,通過setpadding實現,當向下滑,且第一條可見item是第0條的時候才需要設置HeadView的paddingTop來顯示HeadView。
              顯示:headView.setPadding(0,0,0,0);
              隱藏:headView.setPadding(0,-headViewHeight,0,0);
       5、下拉刷新三種狀態的判斷,移動的時候,當paddingTop < 0 的時候,說明HeadView沒有完全顯示出來,進入下拉刷新狀態;移動的時候,當paddingTop >= 0 的時候,   說明HeadView已經完全顯示出來了,進入鬆開以新狀態;手指抬起的時候,且當前狀態是鬆開刷新狀態的時候,進入正在刷新狀態; 當已經是“正在刷新”狀態時,   則不允許再做”下拉刷新”和”鬆開刷新”的操作了,在Move事件中加入判斷,如果已經是正在刷新狀態了,則不處理下拉的操作了。
       6、下拉箭頭的轉動。下拉刷新是向下,鬆開刷新時向上。旋轉動畫通過屬性動畫實現。隱藏箭頭的時候要清除動畫:iv_arrow.clearAnimation();  如果不隱藏動畫效果,設置View.GONE之後還是看得見的。
       7、HeadView顯示時,當手指鬆開時的處理,鬆開時如果是“正在刷新”狀態,則把headVie完全顯示;鬆開時如果是“下拉刷新”狀態,則把HeadView完全隱藏。
       8、增加FooterView:addFooterView(footerView)。當ListView處於空閑狀態,並且最後一條可見item是ListView中的最後一條數據時顯示footview,   footerView顯示出來後,ListView不會自動上滑把FooterView顯示出來的,所以需要手動設置:setSelection(getCount() - 1);即選中最後一條。
       9、增加回調監聽器。當ListView處於刷新狀態的時候會調用onRefreshing()方法;當ListView處於載入更多的時候會調用onLoadMore()。載入完成後通知控制項載入完成。

       具體實現:

import com.itheima.pulltorefreshlistview.R;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

public class PullToRefreshListView extends ListView {

    private View headerView;
    private float downY;
    private int headerViewHeight;
    /** 狀態:下拉刷新 */
    private static final int STATE_PULL_TO_REFRESH = 0;
    /** 狀態:鬆開刷新 */
    private static final int STATE_RELEASE_REFRESH = 1;
    /** 狀態:正在刷新 */
    private static final int STATE_REFRESHING = 2;
    /** 當前狀態 */
    private int currentState = STATE_PULL_TO_REFRESH;    // 預設是下拉刷新狀態
    private ImageView iv_arrow;
    private ProgressBar progress_bar;
    private TextView tv_state;
    private RotateAnimation upAnim;
    private RotateAnimation downAnim;
    private OnRefreshingListener mOnRefreshingListener;
    private View footerView;
    private int footerViewHeight;
    /** 正在載入更多 */
    private boolean loadingMore;

    public PullToRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initHeaderView();
        initFooterView();
    }
    
    private void initHeaderView() {
        headerView = View.inflate(getContext(), R.layout.header_view, null);
        iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
        progress_bar = (ProgressBar) headerView.findViewById(R.id.progress_bar);
        showRefreshingProgressBar(false);
        tv_state = (TextView) headerView.findViewById(R.id.tv_state);
        headerView.measure(0, 0);    // 主動觸發測量,mesure內部會調用onMeasure
        headerViewHeight = headerView.getMeasuredHeight();
        hideHeaderView();
        super.addHeaderView(headerView);
        upAnim = createRotateAnim(0f, -180f);
        downAnim = createRotateAnim(-180f, -360f);
    }
    
    private void initFooterView() {
        footerView = View.inflate(getContext(), R.layout.footer_view, null);
        footerView.measure(0, 0);// 主動觸發測量,mesure內部會調用onMeasure
        footerViewHeight = footerView.getMeasuredHeight();
        hideFooterView();
        super.addFooterView(footerView);
        
        super.setOnScrollListener(new OnScrollListener() {
            
            // 當ListView滾動的狀態發生改變的時候會調用這個方法
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (scrollState == OnScrollListener.SCROLL_STATE_IDLE    // ListView處於空閑狀態
                        && getLastVisiblePosition() == getCount() - 1    // 界面上可見的最後一條item是ListView中最後的一條item
                        && loadingMore == false                            // 如果當前沒有去做正在載入更多的事情
                        ) {
                    loadingMore = true;
                    showFooterView();
                    setSelection(getCount() - 1);
                    
                    if (mOnRefreshingListener != null) {
                        mOnRefreshingListener.onLoadMore();
                    }
                }
            }
            
            // 當ListView滾動的時候會調用這個方法
            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                
            }
        });
    }

    private void hideFooterView() {
        int paddingTop = -footerViewHeight;
        setFooterViewPaddingTop(paddingTop);
    }
    
    private void showFooterView() {
        int paddingTop = 0;
        setFooterViewPaddingTop(paddingTop);
    }

    private void setFooterViewPaddingTop(int paddingTop) {
        footerView.setPadding(0, paddingTop, 0, 0);
    }

    /**
     * 設置顯示進度的圈圈
     * @param showProgressBar 如果是true,則顯示ProgressBar,否則的話顯示箭頭
     */
    private void showRefreshingProgressBar(boolean showProgressBar) {
        progress_bar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
        iv_arrow.setVisibility(!showProgressBar ? View.VISIBLE : View.GONE);
        
        if (showProgressBar) {
            iv_arrow.clearAnimation();    // 有動畫的View要清除動畫才能真正的隱藏
        }
    }

    /**
     * 創建旋轉動畫
     * @param fromDegrees 從哪個角度開始轉
     * @param toDegrees 轉到哪個角度
     * @return 
     */
    private RotateAnimation createRotateAnim(float fromDegrees, float toDegrees) {
        int pivotXType = RotateAnimation.RELATIVE_TO_SELF;        // 旋轉點的參照物
        int pivotYType = RotateAnimation.RELATIVE_TO_SELF;        // 旋轉點的參照物
        float pivotXValue = 0.5f;    // 旋轉點x方向的位置
        float pivotYValue = 0.5f;    // 旋轉點y方向的位置
        RotateAnimation ra = new RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue);
        ra.setDuration(300);
        ra.setFillAfter(true);    // 讓動畫停留在結束位置
        return ra;
    }

    /** 隱藏HeaderView */
    private void hideHeaderView() {
        int paddingTop = -headerViewHeight;
        setHeaderViewPaddingTop(paddingTop);
    }
    
    /** 顯示HeaderView */
    private void showHeaderView() {
        int paddingTop = 0;
        setHeaderViewPaddingTop(paddingTop);
    }

    /**
     * 設置HeaderView的paddingTop
     * @param paddingTop
     */
    private void setHeaderViewPaddingTop(int paddingTop) {
        headerView.setPadding(0, paddingTop, 0, 0);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downY = ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            if (currentState == STATE_REFRESHING) {
                // 如果當前已經是“正在刷新“的狀態了,則不用去處理下拉刷新了
                return super.onTouchEvent(ev);
            }
            
            int fingerMoveDistanceY = (int) (ev.getY() - downY);        // 手指移動的距離
            // 如果是向下滑動,並且界面上可見的第一條item是ListView的索引為0的item時我們才處理下拉刷新的操作
            if (fingerMoveDistanceY > 0 && getFirstVisiblePosition() == 0) {
                int paddingTop = -headerViewHeight + fingerMoveDistanceY;
                setHeaderViewPaddingTop(paddingTop);
                
                if (paddingTop < 0 && currentState != STATE_PULL_TO_REFRESH) {
                    // 如果paddingTop小於0,說明HeaderView沒有完全顯示出來,則進入下拉刷新的狀態
                    currentState = STATE_PULL_TO_REFRESH;
                    tv_state.setText("下拉刷新");
                    iv_arrow.startAnimation(downAnim);
                    showRefreshingProgressBar(false);
                    // 讓箭頭轉一下
                } else if (paddingTop >= 0 && currentState != STATE_RELEASE_REFRESH) {
                    // 如果paddingTop>=0,說明HeaderView已經完全顯示出來,則進入鬆開刷新的狀態
                    currentState = STATE_RELEASE_REFRESH;
                    tv_state.setText("鬆開刷新");
                    iv_arrow.startAnimation(upAnim);
                    showRefreshingProgressBar(false);
                    
                }
                return true;
            }
            break;
        case MotionEvent.ACTION_UP:
            if (currentState == STATE_RELEASE_REFRESH) {
                // 如果當前狀態是鬆開刷新,並且抬起了手,則進入正在刷新狀態
                currentState = STATE_REFRESHING;
                tv_state.setText("正在刷新");
                showRefreshingProgressBar(true);
                showHeaderView();
                
                if (mOnRefreshingListener != null) {
                    mOnRefreshingListener.onRefreshing();
                }
            } else if (currentState == STATE_PULL_TO_REFRESH) {
                // 如果抬起手時是下拉刷新狀態,則把HeaderView完成隱藏
                hideHeaderView();
            }
            break;
        }
        return super.onTouchEvent(ev);
    }

    public void setOnRefreshingListener(OnRefreshingListener mOnRefreshingListener) {
        this.mOnRefreshingListener = mOnRefreshingListener;
    }
    
    /** ListView刷新的監聽器 */
    public interface OnRefreshingListener {
        /** 當ListView可以刷新數據的時候會調用這個方法 */
        void onRefreshing();
        /** 當ListView可以載入更多 的時候會調用這個方法 */
        void onLoadMore();
    }

    /** 聯網刷新數據的操作已經完成了 */
    public void onRefreshComplete() {
        hideHeaderView();
        currentState = STATE_PULL_TO_REFRESH;
        showRefreshingProgressBar(false);
    }

    /** 載入更多新數據的操作已經完成了 */
    public void onLoadmoreComplete() {
        hideFooterView();
        loadingMore = false;
    }

}

     調用listview: 

 listView = (PullToRefreshListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter); 
        listView.setOnRefreshingListener(new OnRefreshingListener() {
         @Override            
         public void onRefreshing() {
           reloadData();
          }
         @Override
         public void onLoadMore() {
          oadMore();             
          }
         });

 

    方案二: listview的多種樣式顯示
        設置listview的適配器的時候可以實現兩個方法: getViewTypeCount()和getItemViewType(),前者指定條目的種類,後者返回具體的類型,這樣可以根據不同的類型設計相關的樣式,包括上拉載入更多,和下拉刷新,兩者類似,因此這裡僅僅給出載入更多的寫法。具體實現如下:

        1、重寫getViewTypeCount()和getItemViewType(),這裡包括普通的item條目和載入更多的條目,所以getViewTypeCount()返回值為2;

@Override
    public int getViewTypeCount() {
        return super.getViewTypeCount() + 1;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == getCount() - 1) {
            return 0;
        } else {
            return addViewType(position); //構造一個方法出來,方便子類修改,添加更多的樣式
        }
    }

    public int addViewType(int position) {
        return 1;
    }

 

         2、在getview()中針對不同的類型添加佈局:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        BaseHoldle holdle;
        if (convertView == null) {
            if (getItemViewType(position) == 0) {  //type為0 表示應該載入載入更多的視圖
                holdle = getLoadmoreHoldle();
            } else {                               //否則為普通視圖
                holdle = getSpecialBaseHoldle(position);
            }
        } else {
            holdle = (BaseHoldle) convertView.getTag();
        }

        if (getItemViewType(position) == 0) {      //載入更多視圖,請求網路獲取數據
            if (havemore()) {
                holdle.setDataAndRefreshHoldleView(LoadmoreHoldle.LOADMORE_LODING);
                triggleLoadMoreData();
            } else {                              
                holdle.setDataAndRefreshHoldleView(LoadmoreHoldle.LOADMORE_NONE);
            }
        } else {                                  //普通視圖視圖,請求網路獲取數據

            T data = (T) mdata.get(position);
            holdle.setDataAndRefreshHoldleView(data);
        }

        mHoldleView = holdle.mHoldleView;
        mHoldleView.setScaleX(0.6f);
        mHoldleView.setScaleY(0.6f);
        ViewCompat.animate(mHoldleView).scaleX(1).scaleY(1).setDuration(400).setInterpolator(new OvershootInterpolator(4)).start();
        return mHoldleView;
    }

           3、具體的載入更多視圖的實現

 private BaseHoldle getLoadmoreHoldle() {
        if (mLoadmoreHoldle == null) {
            mLoadmoreHoldle = new LoadmoreHoldle();
        }
        return mLoadmoreHoldle;
    }

    public class LoadmoreHoldle extends BaseHoldle {
    @Bind(R.id.item_loadmore_container_loading)
    LinearLayout itemloadmorecontainerloading;
    @Bind(R.id.item_loadmore_container_retry)
    LinearLayout itemloadmorecontainerretry;
    @Bind(R.id.item_loadmore_tv_retry)
    TextView item_loadmore_tv_retry;

    public static final int LOADMORE_LODING = 0;
    public static final int LOADMORE_ERROR = 1;
    public static final int LOADMORE_NONE = 2;
    private int mCurretState;

    @Override
    public void refreshHoldleView(Object data) {
        itemloadmorecontainerloading.setVisibility(View.GONE);
        itemloadmorecontainerretry.setVisibility(View.GONE);
        mCurretState = (int) data;
        switch (mCurretState) {
            case LOADMORE_LODING:
                itemloadmorecontainerloading.setVisibility(View.VISIBLE);
                break;
            case LOADMORE_ERROR:
                itemloadmorecontainerretry.setVisibility(View.VISIBLE);
                break;
            case LOADMORE_NONE:
                break;
        }
    }

    @Override
    public View ininViewHoldle() {
        View view = View.inflate(UiUtils.getContext(), R.layout.itemloadmore, null);
        ButterKnife.bind(this, view);
        return view;
    }
}

//holder基類,提取公共的方法
public abstract class BaseHoldle<T> {
    public View mHoldleView;

    public T mdata;

    public BaseHoldle() {
        mHoldleView = ininViewHoldle();
        mHoldleView.setTag(this);
    }

    public void setDataAndRefreshHoldleView(T mdata) {
        this.mdata = mdata;
        refreshHoldleView(mdata);
    }

    public abstract void refreshHoldleView(T data);

    public abstract View ininViewHoldle();

}

 

方案三: SwipeRefreshLayout實現下來刷新
     SwipeRefreshLayout對下不相容,且只有下拉刷新功能沒有上拉載入更多的功能。當時作為Andriod5.0之後的新特性,使用起來方便,可以直接調用系統的API。使用方法也較為簡單。具體實現如下:
    首先聲明控制項,設置顏色:  

 refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh);
     refreshLayout.setOnRefreshListener(this); 
     refreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
            android.R.color.holo_green_light,android.R.color.holo_orange_light,
            android.R.color.holo_red_light);
     refreshLayout.setProgressBackgroundColor(R.color.refresh_bg);
     refreshLayout.setProgressBackgroundColor(R.color.refresh_bg);

 

    寫一個類實現SwipeRefreshLayout.OnRefreshListener,重寫onRefresh()方法: 

   @Override   
    public void onRefresh() {  
      refreshLayout.postDelayed(new Runnable() {
       @Override
       public void run() {
         //請求網路,獲取數據               
         refreshLayout.setRefreshing(false);
        }
       },3000);
    }

 

 


    
  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 設置圖片和文字的垂直居中。vertical-align:middle;圖片豎向居中,然後文字就可以和圖片對齊。 ...
  • 本文系筆者學習原生javascript動效的筆記。內容基於某非著名培訓機構的視頻教程。並重新做了歸類整理。刪除了一些過時的內容。並重做了GIF圖,加上了自己的一些分析。 一. 運動學基礎 引子:從左到右的div 點擊按鈕,一個紅色div從左向右運動! 運動的要素在於一個絕對定位的主體,一個定時器。和 ...
  • 註意:leftBox和rightBox設置浮動之後脫離了普通的文檔流,不再占用原來文檔中的位置,因此無法把父div撐開。 解決的方法: ...
  • 自從威少砍下45+11+11的大號三雙之後,網上出現了各種各樣的神級段子,有一條是這樣的: 威少:Hey,哥們,最近過得咋樣! 濃眉:對方開啟了好友驗證,請先添加對方為好友 威少:。。。。。。 JRS:2333333 看到了一條比賽當天的數據統計:威少45+11+11,杜少32+8+3,伊巴卡19+ ...
  • 一、變數提升 在ES6之前,JavaScript沒有塊級作用域(一對花括弧{}即為一個塊級作用域),只有全局作用域和函數作用域。變數提升即將變數聲明提升到它所在作用域的最開始的部分。上個簡歷的例子如: 之所以會是以上的列印結果,是由於js的變數提升,實際上上面的代碼是按照以下來執行的: 二、函數提升 ...
  • JavaScript [1]事件 ①用戶操作網頁或者瀏覽器所發生的交互行為稱為事件。比如:點擊按鈕,最小化視窗,修改文本框內容等。 ②JS為我們定義許多瀏覽器中的事件。比如:單擊(onclick)、雙擊(ondblclick)、移動(onmousemove) 等。 ③我們可以通過為事件設置一個響應函 ...
  • 用戶反饋是小程式開發必要的一個功能,但是和自己核心業務沒關係,主要是產品運營方便收集用戶的對產品的反饋。HotApp推出了用戶反饋的組件,方便大家直接集成使用 源碼下載地址: https://github.com/hotapp8/hotapp-fedback (1)零代碼接入用戶反饋功能 界面仿微信 ...
  • 昨天的(今天凌晨)的博文《Android中Fragment和ViewPager那點事兒》中,我們通過使用Fragment和ViewPager模仿實現了微信的佈局框架。今天我們來通過使用ListView實現其中聯繫人一欄的基本視圖,效果如下: 要實現上圖的效果,我們要用到兩個知識點: 1、這裡我們使用 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...