前言介紹:
關于 ListView
我們大家都應該是非常的熟悉了,在 Android 開發(fā)中是經常用到的填硕,今天就再來回顧一下麦萤,ListView
的使用方法鹿鳖,和一些需要優(yōu)化注意的地方扁眯,還有日常開發(fā)過程中的一些小技巧和經驗。
ListView 簡介
ListView
是 Android 系統(tǒng)為我們提供的一種列表顯示的一種控件翅帜,使用它可以用來顯示我們常見的列表形式姻檀。繼承自抽象類 AdapterView
。
類的關系圖:
[圖片上傳失敗...(image-2ce24b-1566899080667)]
表現形式
這就是一種最簡單的 ListView
的表現形式涝滴,黑色框就是 ListView
控件绣版,其中由一個個的 item
組成(紅色框內容),然后可以通過向下滑動來查看很多的條目歼疮。
工作原理
ListView
僅是作為容器(列表)杂抽,用于裝載顯示數據(就是上面的一個個的紅色框的內容,也稱為 item)韩脏。item 中的具體數據是由適配器(adapter)來提供的缩麸。
適配器(adapter):作為 View (不僅僅指的 ListView)和數據之間的橋梁或者中介,將數據映射到要展示的 View 中赡矢。這就是最簡單適配器模式杭朱,也是適配器的主要作用!
當需要顯示數據的時候吹散,ListView 會從適配器(Adapter)中取出數據弧械,然后來加載數據。
ListView 負責以列表的形式向我們展示 Adapter 提供的內容
緩存原理
前面講了 ListView 負責把 Adapter 提供的內容一一的展現出來空民,每一條數據對應一個 item 刃唐。試想如果把所有的數據信息全部加載到 ListView 上顯示,加入這些數據有 100 條。那么 ListView 就要創(chuàng)建 100 個視圖唁桩。如果有更多的數據闭树,那么 ListView 就會創(chuàng)建更多的視圖。這種行為顯然是不可取的荒澡,這樣會消耗大量的內容报辱。
解決方案:
為了節(jié)省內存的占用,ListView 是不會為每一條數據創(chuàng)建一個視圖的单山,而是采用了 Recycler組件 的方式碍现。回收和復用 View米奸。
那么是如何來復用的呢昼接?
我們都知道一個屏幕可見的內容就是那么大,所以用戶一次能看到的 item 就是固定的那么幾個悴晰。假如當屏幕一次可以顯示 x 個 item 時(不用是完整的)慢睡,那么 ListView 會創(chuàng)建 x+1 個視圖;當第1個 item 離開屏幕的時候铡溪,此時這個 item 的 View 就會被回收漂辐,再入屏的 item 的 View 就會優(yōu)先從該緩存中獲取。
只有 item 完全離開屏幕后才會復用棕硫,這也是為什么 ListView 要創(chuàng)建比屏幕需要顯示視圖多 1 個的原因:緩沖顯示視圖髓涯。
第 1 個 item 離開屏幕是有一個過程的,會有 1 個 第一個 item 的下半部分 & 第 X+1 個 item 的上半部分同時在屏幕中顯示的狀態(tài) 這種情況是沒法使用緩存的 View 的哈扮。只能繼續(xù)用新創(chuàng)建的視圖 View纬纪。
實例演示:
假如屏幕一次只能顯示 5 個 item,那么 ListView 會創(chuàng)建 (5+1)個 item 視圖滑肉;當第 1 個 item 完全離開屏幕后才會回收至緩存包各,從而復用。(用于顯示第 7 個 item)靶庙。
演示圖來自網絡:
具體使用
引入 ListView 和普通的 View 一樣问畅,直接在布局中添加 ListView
控件即可。
xml 中文件配置信息
<LinearLayout xmlns:android:"http://schemas.android.com/apk/res/android"
xmlns:tools:"http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE1FF"
android:orientation="vertical">
<ListView
android:id="@+id/listView"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</LinearLayout>
AbsListView 常用屬性和相關方法:
屬性 | 說明 | 備注 |
---|---|---|
android:choiceMode | 列表的選擇行為:默認:none 沒有選擇行為 | 選擇方式:none:不顯示任何選中項目 singleChoice:允許單選<br />multipleChoiceModel:允許多選 配合 getCheckedItemPosition 惶洲、getCheckedItemCount按声、等使用 |
android:drawSelectorOnTop | 如果該屬性設置為 true,選中的列表項的選中顏色會 成為前景顏色(實驗沒有效果) | |
android:transcriptMode | 指定列表添加新的選項的時候恬吕,是否自動滑動到底部签则,顯示新的選項。 | disabled:取消 transcriptMode 模式铐料; 默認的 normal:當接受到數據集合改變的通知渐裂,并且僅僅當最后一個選項已經顯示在屏幕的時候豺旬,自動滑動到底部。 alwaysScroll:無論當前列表顯示什么選項柒凉,列表將會自動滑動到底部顯示最新的選項族阅。 |
ListView 提供的 xml 屬性
XML 屬性 | 說明 | 備注 |
---|---|---|
android:divider | 設置 List 列表項的分隔條(可用顏色分割,也可用圖片 Drawable 分割) | 不設置列表之間的分割線膝捞,可設置屬性為 @null |
android:dividerHeight | 用于設置分隔條的高度 | |
android:background 屬性 | 設置列表的背景 | |
android:entries | 指定一個數組資源坦刀,Android 將根據該數組資源來生成 ListView | |
android:footerDividerEnabled | 如果設置成 false 則不在 footerView 之前繪制分隔條 | |
android:headerDividerEnabled | 如果設置成 false 則不再 headerView 之前繪制分隔條 |
Adapter 簡介
使用 ListView 的話就離不開 Adapter 了。
Adapter 本身是一個接口蔬咬,Adapter 接口及其子類的繼承關系如下圖:
- Adapter 接口派生了 ListAdapter 和 SpinnerAdapter 兩個子接口
其中 ListAdapter 為 AbsAdapter 提供列表項鲤遥,SpinnerAdapter 為 AbsSpinner 提供列表項
ArrayAdapter 、SimpleAdapter 都是 Android API 給我們提供好的適配器林艘,直接使用即可盖奈,不過模式都已經寫死了。
- ArrayAdapter:簡單狐援、易用的 Adapter钢坦,用于將數組數據作為數據源綁定到列表項中。支持泛型操作
- SimpleAdapter:相比 ArrayAdapter 來說啥酱,功能比較強大爹凹,可以將數據源的數據一一的綁定到 item 中的 view 中。
- CursorAdapter:用于綁定游標(直接從數據庫取出數據)作為列表項的數據源懈涛,和數據庫有關系逛万,不常用泳猬。
- BaseAdapter:這個是我們在實際開發(fā)中經常用到的批钠,我們需要繼承 BaseAdapter 來自定義我們自己的適配器
常用適配器介紹與使用
ArrayAdapter
特定:使用簡單、用于將數組得封、List 形式的數據綁定到列表中作為數據源埋心,支持泛型操作
步驟:
- 在 xml 文件布局上實現 ListView
- 在 Activity 中定義數據源(列表或者數組)
- 構造 ArrayAdapter 對象含长,設置適配器
- 將 ListView 綁定到 ArrayAdapter 上
- 完事
具體實現:
- 添加 ListView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lv"
>
</ListView>
</LinearLayout>
- 定義數據源
List<String> listData = new ArrayList<>();
for(int i=0;i<20;i++){
listData.add("item數據"+i)栏妖;
}
- 創(chuàng)建 ArrayAdapter 適配器
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this,android.R.layaout.simple_list_item_1,listData);
這里簡單介紹一下 ArrayAdapter 的構造方法攒驰,ArrayAdapter 有好幾個構造方法疫粥。
其中第一參數都是 Context 第二個參數就是要添加的 item 的布局 id 然后就是數據茬斧,數據可以使用數組也可以使用List。還有一點要注意的是梗逮,如果 List 里面存放的是一個普通對象而不是String 的話项秉,則顯示在 item 中的數據為這個對象調用 toString 后的結果。
- 將 ArrayAdapter 適配器綁定到 ListView 上
listView.setAdapter(arrayAdapter);
使用 ArrayAdapter 的缺點
ArrayAdapter 使用起來非常簡單慷彤,也就導致了功能實現非常局限娄蔼,每個列表項只能是 TextView怖喻。可用的 item 布局要足夠簡單岁诉!
SimpleAdapter
相比 ArrayAdapter 來說锚沸,功能比較強大,可以將數據源的數據一一的綁定到 item 中的 view 中涕癣。
使用步驟:
- 在 xml 中添加 ListView
- 實現 item 布局(根據實際UI需求)
- 創(chuàng)建數據源(數據源形式有要求 List<哗蜈?extends Map<String,? >>)
- 創(chuàng)建 SimpleAdapter 適配器
- 將 SimpleAdapter 適配器綁定到 ListView 中
- 完事
具體實現
- 在 xml 中添加 ListView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lv"
>
</ListView>
</LinearLayout>
- 實現 item 布局,這里我自己隨便寫了一個布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@color/colorAccent"
android:id="@+id/tv_one"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@color/colorAccent"
android:id="@+id/tv_two"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@color/colorAccent"
android:id="@+id/tv_three"/>
<ImageView
android:contentDescription="@string/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_four"/>
</LinearLayout>
-
創(chuàng)建數據源坠韩,使用 SimpleAdapter 的時候創(chuàng)建數據源很關鍵恬叹。
數據源的固定格式是 List<?extends Map<String,?extends Object>> ,一般我們都這樣寫 List<HashMap<String,Object>> ,當然 List 里面存放的一條一條的數據就是對應 itme 中的數據同眯。如果 item 中的布局有點復雜的話绽昼,item 中的每個控件又需要設置不同的值,那么 item 中的每個布局的內容就又對應 HashMap 中的值了须蜗。
// 比如上面的布局硅确,有 4 個內容需要填充,則對應的數據源應該是
HashMap<String,Object> hashMap = new HashMap<>();
hashMap.put("name","小明")明肮;
hashMap.put("age",18);
hashMap.put("height",180);
hashMap.put("picture",R.drawable.icon);
然后多了個 item 就是設置多個這樣的 hashMap 加入到 List 中構成數據源菱农。
// 具體的實現方法:
List<HashMap<String,Object>> listData = new ArrayList();
String[] name = new String[]{"小明","小華"柿估,"小趙"循未,"小王"};
String[] age = new int[]{15,16,17,18};
int[] height = new int[]{180,179,174,177};
int[] picture = new int[]{R.drawable.icon,R.drawable.c,R.drawable.a,R.drawable.aa,R.drawable.ww};
for(int i =0;i<name.length,i++){
HashMap<String,Object> hashMap = new HashMap<>();
hashMap.put("name",name[i]);
hashMap.put("age",age[i]);
hashMap.put("height",height[i]);
hashMap.put("picture",picture[i]);
listData.add(hashMap);
}
-
創(chuàng)建 SimpleAdapter
SimpleAdapter 的創(chuàng)建是非常容易和固定的秫舌,因為它就只有一個構造方法
// 將 hashMap 的 key 組成一個字符串數組
String[] form = new String[]{"name","age","height","picture"};
// 將 item 布局中的 view 的 id 組成一個數組的妖,要和 form 對應
int[] to = new int[]{R.id.tv_one,R.id.tv_two,R.id.tv_three,R.id.tv_four};
SimpleAdapter simpleAdapter = new SimpleAdapter(this,listData,R.layout.item_simple_adapter,form,to);
- 將 SimpleAdapter 綁定到 ListView 中
listView.setAdapter(simpleAdapter);
BaseAdapter
我們在實際開發(fā)過程中接觸最多的就是 BaseAdapter
了∽阍桑可以最大程度的定制我們自己的 item嫂粟。
實現步驟
- 在布局中添加 ListView
- 實現 item 布局(根據 ui 設計的)
- 創(chuàng)建數據源
- 創(chuàng)建自己的 Adapter 類 繼承 BaseAdapter
- 創(chuàng)建自定義的 Adapter 類對象
- 將創(chuàng)建的適配器綁定到 ListView 上
具體實現步驟
布局中添加 ListView(就不再寫代碼了,和上面一樣
實現 item 布局(依然使用 SimpleAdapter 中的 item 布局就可以了)
-
創(chuàng)建數據源
class User{ private String name; private int age; private int height; private int picture(); get.. set... 方法 } List<User> listData = new ArrayList<>(); for(int i=0;i<20;i++){ User user = new User(); user.setName("小明"+i); user.setAge(age); user.setHeight(height); user.setPicture(id); listData.add(user); }
-
創(chuàng)建自己的 Adapter
// 繼承 BaseAdapter 必須要實現它的 4 個方法 class MyAdapter extends BaseAdapter{ // 返回適配器中所代表的數據集合的條數 // 會首先執(zhí)行這個方法(連續(xù)執(zhí)行好幾次)墨缘,如果是 0 則后面的方法就不會執(zhí)行了 @Override public int getCount() { return 0; } // 返回數據集合中指定索引 position 對應的數據項 // 手動調用才會執(zhí)行 @Override public Object getItem(int position) { return null; } // 返回列表中與指定索引對應的行 id // 手動調用才會執(zhí)行 @Override public long getItemId(int position) { return 0; } // 返回指定索引對應的數據的視圖星虹,會多次調用 @Override public View getView(int position, View convertView, ViewGroup parent) { return null; } }
重點講解一下 BaseAdapter 中的這四個方法
- BaseAdapter 之所以十分靈活,就是因為我們需要自己重寫它的很多方法镊讼,尤其是
getView()
方法宽涌,返回我們任意想要的布局類型。 - 結合上面的 4 個方法了解一下 ListView 的繪制過程:
- 通過調用
getCount()
獲取 ListView 的長度(item 的個數) - 通過調用
getView()
蝶棋,根據 ListView 的長度逐一繪制 ListView 的每一行 - 獲取數據時卸亮,通過
getItem()
getItemId()
來獲取 Adapter 中的數據
- 通過調用
重點看一下 getView
實現方式一:
直接返回索引對應的數據的視圖
@Override
public View getView(int position,View convertView,ViewGroup parent){
View item = mInflater.inflater(R.layout.item,null);
TextView tv = item.findViewById(R.id.tv);
ImageView iv = item.findViewById(R.id.iv);
Button bt = item.findViewById(R.id.bt);
tv.setTextView("");
img.setImageResource("");
....各種設置
return item;
}
這是最直接的一種方式嚼松,目標很明確就是返回對應的視圖嫡良。同樣缺點也很明確锰扶,沒有利用 ListView 對 item 的復用機制,假如有 1000 個 item 就要繪制 1000 個 view。然后再進行 findViewById 會十分消耗資源寝受。
實現方式二:使用 convertView 作為 View 緩存
將 convertView 作為 getView 的輸入參數坷牛、返回參數
借助 ListView 的緩存機制,實現 view 的復用很澄。
@Override
public View getView(int position,View convertView,ViewGroup parent){
// 檢測有無可重復使用的 View京闰,如果沒有就創(chuàng)建新的
// ListView 的緩存原理前面已經介紹了,從頁面消失進入緩存區(qū)的 View 就會傳遞過來
if(convertView == null){
convertView = mInflater.inflater(R.layout.item,null);
}
TextView tv = convertView.findViewById(R.id.tv);
ImageView iv = convertView.findViewById(R.id.iv);
Button bt = convertView.findViewById(R.id.bt);
tv.setTextView("");
img.setImageResource("")甩苛;
....各種設置
return convertView;
}
// 優(yōu)點:減少了 View 的重新繪制蹂楣,實現了 view 復用機制
// 缺點:每次都要 findViewById 尋找組件
實現方式三:在方式二的基礎上,進行優(yōu)化讯蒲,引入 ViewHolder 減少 findViewById
class ViewHolder{
TextView tv;
ImageView iv;
Button bt;
}
@Override
public View getView(int position,View convertView,ViewGroup parent){
ViewHolder viewHolder;
if(convertView == null){
convertView = mInflater.inflater(R.layout.item,null);
viewHolder = new ViewHolder();
viewHolder.tv = convertView.findViewById(R.id.tv);
viewHolder.iv = convertView.findViewById(R.id.iv);
viewHolder.bt = convertView.findViewById(R.id.bt);
// 將 viewHolder 綁定到 convertView 中實現復用
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder)convertView.getTag();
}
viewHolder.iv.set.....
.....各種設置
return convertView;
}
// 優(yōu)點:重用 View 的時候不用再次重復使用 findViewById 了痊土。是 ListView 的最佳方案
Adapter 優(yōu)化總結:
[圖片上傳失敗...(image-10fd84-1566899080667)]
- 創(chuàng)建自己定義的 Adapter
- 將 Adapter 綁定到 ListView 上。
Adapter 的一些其他優(yōu)化
getView 內部應做盡可能少的業(yè)務邏輯處理墨林。因為 getView 調用很頻繁赁酝。
關于可見和不可見的邏輯可以提前在數據源里面填充好。
getView 中不要出現大量的對象
最好把創(chuàng)建對象放到 ViewHolder 中
加載圖片旭等,滑動的時候不要加載圖片酌呆,會造成 ListView 卡頓,需要在監(jiān)聽器里面判斷 ListView 的狀態(tài)搔耕。
listView.setOnScrollListener(new OnScrollListenr){
@Override
public void onScrollStateChanged(AbsListView lsitView,int scrollState){
if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING){
imageLoader.stopProcessingQueue();
}else{
// 加載圖片
}
}
}
本篇文章大部分內容參考:http://www.reibang.com/p/4e8e4fd13cf7 對作者表示感謝O对!