咱們以前討論了ListView的基本使用方法和ListView的優化android
今天咱們再來討論一個關於ListView的一個新的東西~就是分頁加載。那麼什麼是分頁加載呢?簡單點說,就是「下拉刷新」。數組
咱們來簡單的構思一下咱們都須要些什麼東西。app
首先,咱們須要主界面佈局,而且在佈局文件中寫一個ListView。ide
而後,咱們須要ListView中每一個item的佈局佈局
而後,咱們還須要一個當頁面加載的時候出現的那個旋轉的小圓圈(ProgressBar)的佈局,咱們能夠將其稱之爲「底部佈局」(本身起的名字)。優化
而後,咱們須要一個Activity,在其中咱們須要設置數據源和一系列操做。ui
而後,咱們須要一個類,該類表明每一個item,存儲的是每一個item中的內容this
接下來,咱們來按照咱們上面的簡單的構思,來作一個具體點的構思吧spa
主界面佈局文件我就不用多說了,就是裏面添加一個ListView線程
而後,咱們來考慮item中的佈局,咱們想顯示一個頭像,一個名字,一個內容。咱們就定義一個ImageView、一個TextView(管名字)、一個TextView(管內容)
而後,咱們須要在底部佈局中,添加一個ProgressBar (就是旋轉小圓圈,又叫圓形進度條)和一個TextView(用來寫「正在加載~」字樣)。
而後,咱們須要一個類,表明item中的內容,因爲咱們上面已經說了,咱們想顯示一個頭像,一個名字,一個內容。 那麼,咱們在該類中就須要維護一個int類型的icon(圖片是int類型的),一個String類型的title(名字/標題),一個String類型的content(內容)
而後,咱們須要Acitivty,在Activity中,咱們須要定義數據源,和一個適配器,還有一個存儲對象的list集合。而且讓Activity實現OnScrollListener接口,並實現其中的兩個方法 1.onScroll方法 2.onScrollStateChanged方法
咱們根據上面的構思,來作具體的實現:
咱們先來看咱們的主佈局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="application.smile.listview.MainActivity"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
而後,咱們來看每一個item中的佈局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp"> <ImageView android:id="@+id/iv_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="5dp" android:src="@mipmap/ic_launcher"/> <TextView android:id="@+id/tv_title" android:layout_marginTop="10dp" android:layout_toRightOf="@+id/iv_icon" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="這是標題" android:textSize="20sp"/> <TextView android:id="@+id/tv_content" android:layout_toRightOf="@+id/iv_icon" android:layout_below="@+id/tv_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="這是內容"/> </RelativeLayout>
就是這樣的效果:
接下來,咱們來看咱們底部佈局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="拼命加載中"/> </LinearLayout>
看到的效果就是這樣的:
這樣,咱們的佈局文件就到這了。
接下來,咱們先來看建立的item的類
public class ItemContent { int icon; String title ; String content; }
而後,咱們再來看Activity中的實現:
這裏是最重要的了……由於全部的實現都在Activity中,咱們仍是有必要再細一些的講解
首先,咱們須要實現接口,而後設置數據源(就是圖片和名字),建立一個list集合用來存放剛剛建立的類的對象(就是ItemContent),再維護一個適配器
而後,咱們在onCreate(...)方法中去執行咱們的老套路,找到控件、而後更新數據、而後設置適配器,而後設置點擊事件(這裏有兩個事件,一個是滾動事件,一個是點擊事件),這裏還要加一個,就是獲得咱們的底部佈局,並加入到ListView中。
而後,咱們在更新數據的方法中去設置每次加載多少條數據。就是經過一個循環來控制每次加載多少條數據,每次都建立一個ItemContent(建立的item類)的對象,併爲其賦值,最後添加到List集合中,這裏須要作一個操做,由於咱們是模擬,因此沒有不少的數據,那麼咱們就須要讓咱們現有的數據來循環使用,咱們須要一個控制變量(至關於一個指針),當這個控制變量的大小和數據數組的長度一致的時候,就將控制變量置零,讓咱們從數據數組的開始繼續讀取,從而實現數據的循環使用。
而後咱們來看咱們重寫的兩個方法中的onScroll方法:
該方法用於監聽屏幕滾動
該方法中有四個參數:
第一個參數:AbsListView類型的view 該參數是正在滾動的視圖
第二個參數:int類型的firstVisibleItem 該參數是第一個可見的item的索引
第三個參數:int類型的visibleItemCount 該參數是可見的item的數量
第四個參數:int類型的totalItemCount 該參數是列表適配器中的項目數(咱們這裏用不到)
而後,咱們來看這個方法中,咱們須要作些什麼:
咱們須要定義一個int類型的變量,用來標記咱們能看見的最後一個item的索引 ,而後咱們經過<「第一個可見的item的索引」 + 「可見的item的數量」 -1> 來得到咱們想要的最後一個item的索引。
這裏爲何要-1呢?咱們來仔細的考慮一下。所謂索引也就是下標,咱們知道下標是從0開始的,若是咱們不-1 ,咱們就會多獲得一條數據,每次就不是10個了,而是11個。因此,咱們須要-1 來控制,咱們每次都拿到10條數據
這樣,咱們這個方法就完成了。
而後,咱們來看重寫的兩個方法中的onScrollStateChanged方法:
該方法用於監聽屏幕滾動的狀態
該方法中有兩個參數:
第一個參數:AbsListView類型的view 該參數是正在滾動的視圖
第二個參數:int類型的scrollState 該參數是當前滾動的狀態
該參數,能夠有三個值與之匹配:
1.SCROLL_STATE_FLING 表明:用戶已經使用觸摸來滾動,手指已經離開屏幕,可是視圖還在慣性的滾動
2.SCROLL_STATE_IDLE 表明:視圖不滾動,而且手指鬆開
3.SCROLL_STATE_TOUCH_SCROLL 表明:用戶正在使用觸摸來滾動,而且手指還在屏幕上
而後,咱們來看這個方法中,咱們須要作些什麼:
咱們須要經過線程來模擬加載的時間,可是在此以前,咱們須要先判斷「滾動的狀態」 是否是處於「視圖再也不滾動,而且手指鬆開的狀態」而且判斷能看到的最後一條item的索引是否是適配器的長度
若是這兩個條件都知足,證實已經滾動到了最下面一條,須要更新數據。
因而咱們開啓一個線程而且睡上1秒,睡完要更新數據,而且顯示到ListView上,可是在安卓中想要更新UI就必須在UI線程中就行,不能夠在子線程中進行,那麼咱們想要個更新UI就用上了Handler 這個類。
咱們要在成員位置建立一個Handler對象,並重寫handleMessage方法,在方法中作更新UI的操做,那麼咱們如何讓子線程與HandleMessage方法連接上呢?咱們須要在子線程中經過Handler對象,去發送一個一個消息,並附上一個標誌
而後,咱們在HandleMessage中去判斷這個標誌,若是成功就更新UI
接下來,咱們就來看咱們自定義的適配器。仍是以前的套路,繼承BaseAdapter,並重寫方法
再重寫方法以前,咱們須要維護一個佈局填充器,並在構造方法中給他賦值
而後咱們來看重寫的方法:
第一個方法:getCount方法 這個須要返回總共有多少個item,這裏就是list集合的size
第二個方法:getItem方法 這個就是返回每一個item,這裏就是經過list集合的get方法得到item並返回
第三個方法:getItemId方法 這個就是返回每一個item的id,這裏就是直接返回position
第四個方法:getView方法 這個方法中就是具體的狀況了
首先建立一個類取名爲ViewHolder並在其中聲明item中的三個控件
而後,在getView方法中維護一個ViewHolder
而後,判斷convetView是否爲空,若是爲空就經過佈局填充器得到item的佈局。
並實例化ViewHolder,經過convertView找到控件並賦值給ViewHolder的相應的控件。
而後設置一個標記,把ViewHolder放進去。
若是不爲空,就經過標記取出ViewHolder,並給每一個空間設置數據源。
最後,返回convertView
而後,咱們來上代碼:
public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener { //用來判斷點擊的是否是「正在加載」的item,true表明是,false表明不是 private boolean flag = false; //用於Handler返回的標誌 private static final int SEND_OK = 0X1; //存儲文字的數組 private String[] names = {"郭嘉", "黃月英", "華佗", "劉備", "陸遜", "呂布", "呂蒙", "馬超", "司馬懿", "孫權", "孫尚香", "夏侯惇", "許褚", "楊修", "張飛", "趙雲", "甄姬", "周瑜", "諸葛亮"}; private List<ItemContent> project = new ArrayList<>(); //存儲圖片的數組 private int[] images = {R.mipmap.guojia, R.mipmap.huangyueying, R.mipmap.huatuo, R.mipmap.liubei, R.mipmap.luxun, R.mipmap.lvbu, R.mipmap.lvmeng, R.mipmap.machao, R.mipmap.simayi, R.mipmap.sunquan, R.mipmap.sunshangxiang, R.mipmap.xiahoudun, R.mipmap.xuchu, R.mipmap.yangxiu, R.mipmap.zhangfei, R.mipmap.zhaoyun, R.mipmap.zhenji, R.mipmap.zhouyu, R.mipmap.zhugeliang}; private MyAdapter myAdapter; //建立一個Handler對象 private Handler handler = new Handler() { //當子線程調用sendMessage方法時會自動調用該方法 @Override public void handleMessage(Message msg) { super.handleMessage(msg); //根據標誌作相應的操做 switch (msg.what) { case SEND_OK: //更新ui myAdapter.notifyDataSetChanged(); //將「正在加載」的標誌,設置爲沒在加載 flag = false; break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //找到控件 ListView listView = (ListView) findViewById(R.id.listView); //更新數據 initData(); //找到底部佈局 View bottom_layout = getLayoutInflater().inflate(R.layout.bottom_layout, null); //設置到listView上 listView.addFooterView(bottom_layout); //設置適配器 myAdapter = new MyAdapter(this); listView.setAdapter(myAdapter); listView.setOnScrollListener(this); //設置點擊事件 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //彈出吐司 if (!flag) { //若是點擊的不是「正在加載」就彈出這個吐司 Toast.makeText(MainActivity.this, "點我了?我是" + names[position % 19], Toast.LENGTH_SHORT).show(); } else { //若是點擊的是「正在加載」就彈出這個吐司 Toast.makeText(MainActivity.this, "加載中....", Toast.LENGTH_SHORT).show(); } } }); } //控制數據循環的控制變量 int count = 0; public void initData() { //經過循環控制每次獲取10條數據 for (int i = 0; i < 10; i++) { //實例化一個item的類的對象 ItemContent item = new ItemContent(); //判斷,若是控制變量的長度大於或者等於數據數組的長度,就置零 if (count >= images.length) { count = 0; } //賦值 item.icon = images[count]; item.title = names[count]; item.content = "我是" + names[count]; //添加到list集合 project.add(item); //控制變量自增 count++; } } /** * 用於監聽ListView滾動狀態的變化 * * @param view 正在報告滾動狀態的視圖 * @param scrollState 當前的滾動狀態 */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (!flag) { /** *SCROLL_STATE_FLING: 用戶已經使用觸摸來滾動,手指已經離開屏幕,可是視圖還在慣性的滾動 *SCROLL_STATE_IDLE: 視圖不滾動,而且手指鬆開 * SCROLL_STATE_TOUCH_SCROLL: 用戶正在使用觸摸來滾動,而且他們的手指仍在屏幕上 */ if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && visibleLastItem == myAdapter.getCount()) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } initData(); handler.sendEmptyMessage(SEND_OK); } }).start(); flag = true; } } } private int visibleLastItem = 0; //能看到的最後一個單元格的索引 /** * 用於監聽ListView屏幕滾動 * * @param view 正在報告滾動狀態的視圖 * @param firstVisibleItem 第一個可見單元格的索引 * @param visibleItemCount 可見單元格的數量 * @param totalItemCount 列表適配器中的項目數 */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //最後能看到的視圖索引 = 第一個能夠看見的單元格索引 + 可見單元格的數量 - 1; //由於是從0開始的,可是數量是從1開始的,因此 須要減一才能夠作索引 visibleLastItem = firstVisibleItem + visibleItemCount -1; } /** * 自定義適配器,繼承BaseAdapter */ class MyAdapter extends BaseAdapter { //維護一個佈局填充器,爲了獲得每一個item 的佈局 private LayoutInflater layoutInflater; public MyAdapter(Context context) { //經過構造方法獲取佈局填充器對象 layoutInflater = LayoutInflater.from(context); } //該方法是總共有多少個item @Override public int getCount() { return project.size(); } //該方法是獲得每一個item的值 @Override public Object getItem(int position) { return project.get(position); } //該方法是獲得每一個item的id @Override public long getItemId(int position) { return position; } //該方法是得到視圖,也是這些裏面最重要的方法 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { //首先,咱們經過佈局填充器得到item的佈局 convertView = layoutInflater.inflate(R.layout.item_layout, null); //實例化ViewHolder viewHolder = new ViewHolder(); //根據item的佈局找到圖片 viewHolder.iv_icon = (ImageView) convertView.findViewById(iv_icon); //根據item的佈局找到標題 viewHolder.tv_title = (TextView) convertView.findViewById(tv_title); //根據item的佈局找到內容 viewHolder.tv_content = (TextView) convertView.findViewById(tv_content); //設置tag標記 convertView.setTag(viewHolder); } viewHolder = (ViewHolder) convertView.getTag(); //設置圖片源 viewHolder.iv_icon.setImageResource(project.get(position).icon); //設置標題文字 viewHolder.tv_title.setText(project.get(position).title); //設置內容文字 viewHolder.tv_content.setText(project.get(position).content); //返回item的佈局 return convertView; } class ViewHolder { ImageView iv_icon; TextView tv_title; TextView tv_content; } } }
接下來,咱們來看一下運行的結果吧~
這樣,咱們的ListView的分頁加載就完成了
讓程序寫入生命,將代碼融入靈魂
-------smile、zj