標籤:android
《Android ListView下拉/上拉重新整理:設計原理與實現》
Android上ListView的第三方開源的下拉重新整理架構很多,應用情境很多很普遍,幾乎成為現在APP的通用設計典範,甚至Google官方都索性在Android SDK層面支援下拉重新整理,我之前寫了一篇文章《Android SwipeRefreshLayout:Google官方SDK包中的下拉重新整理》專門介紹過(連結地址:http://blog.csdn.net/zhangphil/article/details/46965377 )。
每一種ListView下拉重新整理的開源架構,準系統相同,設計原理大同小異,下拉重新整理的功能實現,其中一個設計實現的的方案核心要點大多集中在ListView的OnScrollListener()等事件的重寫上。但是,常見的一些下拉重新整理開源架構中,有些缺乏上拉重新整理的功能。上拉重新整理的功能在一些應用情境中也是需要的,比如,當使用者的裝置螢幕由於資料需要從網路中載入,但一次網路請求根本不可能把全部資料都載入完,因此在初始化階段只喂全部資料中的一部分資料。當使用者在一個ListView中翻到最底時候,“載入更多”,注意!此處出現另外一種設計方案,比如在ListView的footer view中設計一個按鈕,假設按鈕就叫做“載入更多”,當使用者翻到ListView最後見底時候,點擊該按鈕後才“載入更多”再次發起資料請求載入更多資料,然後重新整理ListView,這種設計方案也比較常見。本文則介紹一個可以自動感知ListView下拉到底、然後可自動載入更多的支援下拉/上拉重新整理的ListView。
A:設計原理之綜述:
因為我們要同時設計與實現下拉和上拉重新整理,顯然,我們不能僅僅只做下拉重新整理的功能,同時還要做上拉重新整理的功能。下拉重新整理和上拉重新整理的手勢方向不同(相反),所以我們首先要做的事情就是把這兩種情況(使用者的意圖究竟是下拉見頂重新整理還是上拉見底重新整理?)區分出來。
為達到這一目的,我們在ListView中監測onTouch()事件,然後使用GestureDetector判斷使用者手指在螢幕上的移動方向是向上還是向下,進而明確使用者的意圖到底是打算下拉見頂(頂,ListView的第一個item,編號為0)重新整理抑或上拉見底(底,ListView的最後、最尾部的一個元素)重新整理。
當我們知道使用者的意圖之後(下拉見頂重新整理,或,上拉見底重新整理 )。然後計算和分析:當前ListView在螢幕可見地區內的第一個元素(firstVisibleItem)、ListView在可見地區內的元素數量(visibleItemCount),ListView全部元素的(totalItemCount)這三者的數量關係。簡單的說,當firstVisibleItem==0且使用者是下拉時候,代碼就認為使用者的意圖是下拉重新整理;當firstVisibleItem + visibleItemCount == totalItemCount 時候,代碼就認為使用者的意圖是是上拉重新整理。
其中,firstVisibleItem , visibleItemCount , totalItemCount 的值可從ListView的OnScrollListener中獲得更新。
B:設計原理之實現:
(第1步)給ListView setOnScrollListener,重寫該ListView中OnScrollListener的onScroll方法,目的是即時更新firstVisibleItem , visibleItemCount , totalItemCount值。
(第2步):每一個Android ListView繼承自Android View,Android View有一個:
public void setOnTouchListener (View.OnTouchListener l);
我們傳給這個方法一個View.OnTouchListener,然後重寫View.OnTouchListener裡面的:
public abstract boolean onTouch (View v, MotionEvent event);
在這個onTouch()中我們用GestureDetector做監測,用GestureDetector判斷使用者是在上拉還是下拉。
更具體一些,在構造GestureDetector時候需要傳進來一個SimpleOnGestureListener,其實就是重載SimpleOnGestureListener的onFling,在onFling中判斷velocityY值大於0還是小於0,大於0我們就認為使用者是在下拉,小於0我們就認為使用者是在上拉。然後根據這兩種情況,分別觸發不同的onTop和onBottom事件。
代碼:
測試主程式(MainActivity.java):
package zhangphil.listview;import java.util.ArrayList;import android.app.Activity;import android.os.Bundle;import android.widget.ArrayAdapter;public class MainActivity extends Activity {privatePhilListView listView=null;private int DATA = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);listView = (PhilListView) findViewById(R.id.philListView);final ArrayList<String> items = new ArrayList<String>();// 使用最簡單的Android系統內建的android.R.layout.simple_list_item_1裝載資料。final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, items);listView.setAdapter(adapter);listView.setOnPullToRefreshListener(new PhilListView.OnPullToRefreshListener() {@Overridepublic void onBottom() {listView.onRefresh(true);items.add("上拉到尾部追加:" + DATA++);adapter.notifyDataSetChanged();listView.onRefresh(false);}@Overridepublic void onTop() {listView.onRefresh(true);items.add(0, "下拉到頂部添加:" + DATA++);adapter.notifyDataSetChanged();listView.onRefresh(false);}});}}
代碼設計實現的下拉/上拉重新整理列表:
package zhangphil.listview;import android.app.ProgressDialog;import android.content.Context;import android.util.AttributeSet;import android.view.GestureDetector;import android.view.MotionEvent;import android.view.View;import android.widget.AbsListView;import android.widget.ListView;public class PhilListView extends ListView {private Context context;private int firstVisibleItem = 0;private int visibleItemCount = 0;private int totalItemCount = 0;// 一個簡單的圓球形進度滾動球,向使用者表明正在載入private ProgressDialog progressDialog = null;private OnPullToRefreshListener mOnPullToRefreshListener = null;public PhilListView(Context context) {super(context);this.context = context;}public PhilListView(Context context, AttributeSet attrs) {super(context, attrs);this.context = context;}// 此處對外開放的回調介面,讓使用者可以使用上拉見底重新整理或者下拉見頂重新整理。public interface OnPullToRefreshListener {// 當使用者的手指在螢幕上往上拉見到ListView的底部最後一個元素時候回調。public void onBottom();// 當使用者的手指在螢幕上往下拉見到ListView的頂部第一個元素時候回調。public void onTop();}public void setOnPullToRefreshListener(OnPullToRefreshListener listener) {mOnPullToRefreshListener = listener;this.setOnScrollListener(new ListView.OnScrollListener() {// 把最新值賦給firstVisibleItem , visibleItemCount , totalItemCount.@Overridepublic void onScroll(AbsListView view, int arg0, int arg1, int arg2) {firstVisibleItem = arg0;visibleItemCount = arg1;totalItemCount = arg2;}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {}});// mGestureDetector用於監測使用者在手機螢幕上的上滑和下滑事件。// 之所以用GestureDetector而不完全依賴ListView.OnScrollListener,主要是因為當ListView在0個元素,或者當資料元素不多不足以多螢幕滾動顯示時候(換句話說,正常情況假設一屏可以顯示15個,但ListView只有3個元素,那麼ListView下方就會剩餘空出很多空白空間,在此空間上的事件不觸發ListView.OnScrollListener)。final GestureDetector mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2,float velocityX, float velocityY) {// velocityY是向量,velocityY表明手指在豎直方向(y座標軸)移動的向量距離。// 當velocityY >0時,表明使用者的手指在螢幕上往下移動。// 即e2事件發生點在e1事件發生點的下方。// 我們可以據此認為使用者在下拉:使用者下拉希望看到位於頂部的資料。// 然後在這個代碼塊中,判斷ListView中的第一個條目firstVisibleItem是否等於0 ,// 等於0則意味著此時的ListView已經滑到頂部了。// 然後開始回調mOnPullToRefreshListener.onTop()觸發onTop()事件。if (velocityY > 0) {if (firstVisibleItem == 0) {mOnPullToRefreshListener.onTop();}}// 與上面的道理相同,velocityY < 0,此時的e1在e2的下方。// 表明使用者的手指在螢幕上往上移動,希望看到底部的資料。// firstVisibleItem表明螢幕當前可見視野上第一個item的值,// visibleItemCount是可見視野中的數目。// totalItemCount是ListView全部的item數目// 如果 firstVisibleItem + visibleItemCount ==// totalItemCount,則說明此時的ListView已經見底。if (velocityY < 0) {int cnt = firstVisibleItem + visibleItemCount;if (cnt == totalItemCount) {mOnPullToRefreshListener.onBottom();}}return super.onFling(e1, e2, velocityX, velocityY);}});this.setOnTouchListener(new View.OnTouchListener() {// 用mGestureDetector監測Touch事件。@Overridepublic boolean onTouch(View v, MotionEvent event) {return mGestureDetector.onTouchEvent(event);}});}/** * @param refreshing * * 控制在載入過程中的動畫顯示 * ture:顯示. * false:關閉 */public void onRefresh(boolean refreshing) {if (refreshing)showProgress();elsecloseProgress();}// 顯示ProgressDialog,表明正在載入...private void showProgress() {progressDialog = ProgressDialog.show(context, "PhilListView","載入中,請稍候...", true, true);}// 關閉載入顯示private void closeProgress() {if (progressDialog != null)progressDialog.dismiss();}}
MainActivity.java需要的activity_main.xml :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <zhangphil.listview.PhilListView android:id="@+id/philListView" android:layout_width="match_parent" android:layout_height="match_parent" > </zhangphil.listview.PhilListView></LinearLayout>
:
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Android ListView下拉/上拉重新整理:設計原理與實現