關於 ListView
咱們你們都應該是很是的熟悉了,在 Android 開發中是常常用到的,今天就再來回顧一下,ListView
的使用方法,和一些須要優化注意的地方,還有平常開發過程當中的一些小技巧和經驗。java
ListView
是 Android 系統爲咱們提供的一種列表顯示的一種控件,使用它能夠用來顯示咱們常見的列表形式。繼承自抽象類 AdapterView
。android
類的關係圖:數據庫
這就是一種最簡單的 ListView
的表現形式,黑色框就是 ListView
控件,其中由一個個的 item
組成(紅色框內容),而後能夠經過向下滑動來查看不少的條目。數組
ListView
僅是做爲容器(列表),用於裝載顯示數據(就是上面的一個個的紅色框的內容,也稱爲 item)。item 中的具體數據是由適配器(adapter)來提供的。緩存
適配器(adapter):做爲 View (不只僅指的 ListView)和數據之間的橋樑或者中介,將數據映射到要展現的 View 中。這就是最簡單適配器模式,也是適配器的主要做用!網絡
當須要顯示數據的時候,ListView 會從適配器(Adapter)中取出數據,而後來加載數據。app
ListView 負責以列表的形式向咱們展現 Adapter 提供的內容ide
前面講了 ListView 負責把 Adapter 提供的內容一一的展示出來,每一條數據對應一個 item 。試想若是把全部的數據信息所有加載到 ListView 上顯示,加入這些數據有 100 條。那麼 ListView 就要建立 100 個視圖。若是有更多的數據,那麼 ListView 就會建立更多的視圖。這種行爲顯然是不可取的,這樣會消耗大量的內容。佈局
解決方案:優化
爲了節省內存的佔用,ListView 是不會爲每一條數據建立一個視圖的,而是採用了 Recycler組件 的方式。回收和複用 View。
那麼是如何來複用的呢?
咱們都知道一個屏幕可見的內容就是那麼大,因此用戶一次能看到的 item 就是固定的那麼幾個。假如當屏幕一次能夠顯示 x 個 item 時(不用是完整的),那麼 ListView 會建立 x+1 個視圖;當第1個 item 離開屏幕的時候,此時這個 item 的 View 就會被回收,再入屏的 item 的 View 就會優先從該緩存中獲取。
只有 item 徹底離開屏幕後纔會複用,這也是爲何 ListView 要建立比屏幕須要顯示視圖多 1 個的緣由:緩衝顯示視圖。
第 1 個 item 離開屏幕是有一個過程的,會有 1 個 第一個 item 的下半部分 & 第 X+1 個 item 的上半部分同時在屏幕中顯示的狀態 這種狀況是無法使用緩存的 View 的。只能繼續用新建立的視圖 View。
實例演示:
假如屏幕一次只能顯示 5 個 item,那麼 ListView 會建立 (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:容許單選 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 以前繪製分隔條 |
使用 ListView 的話就離不開 Adapter 了。
Adapter 自己是一個接口,Adapter 接口及其子類的繼承關係以下圖:
其中 ListAdapter 爲 AbsAdapter 提供列表項,SpinnerAdapter 爲 AbsSpinner 提供列表項
ArrayAdapter 、SimpleAdapter 都是 Android API 給咱們提供好的適配器,直接使用便可,不過模式都已經寫死了。
特定:使用簡單、用於將數組、List 形式的數據綁定到列表中做爲數據源,支持泛型操做
步驟:
具體實現:
<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);
}
複製代碼
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this,android.R.layaout.simple_list_item_1,listData);
這裏簡單介紹一下 ArrayAdapter 的構造方法,ArrayAdapter 有好幾個構造方法。
其中第一參數都是 Context 第二個參數就是要添加的 item 的佈局 id 而後就是數據,數據可使用數組也可使用List。還有一點要注意的是,若是 List 裏面存放的是一個普通對象而不是String 的話,則顯示在 item 中的數據爲這個對象調用 toString 後的結果。
複製代碼
listView.setAdapter(arrayAdapter);
複製代碼
使用 ArrayAdapter 的缺點
ArrayAdapter 使用起來很是簡單,也就致使了功能實現很是侷限,每一個列表項只能是 TextView。可用的 item 佈局要足夠簡單!
相比 ArrayAdapter 來講,功能比較強大,能夠將數據源的數據一一的綁定到 item 中的 view 中。
使用步驟:
具體實現
<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>
複製代碼
<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>
複製代碼
建立數據源,使用 SimpleAdapter 的時候建立數據源很關鍵。
數據源的固定格式是 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);
}
複製代碼
建立 SimpleAdapter
SimpleAdapter 的建立是很是容易和固定的,由於它就只有一個構造方法
// 將 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);
複製代碼
listView.setAdapter(simpleAdapter);
複製代碼
咱們在實際開發過程當中接觸最多的就是 BaseAdapter
了。能夠最大程度的定製咱們本身的 item。
實現步驟
具體實現步驟
佈局中添加 ListView(就再也不寫代碼了,和上面同樣
實現 item 佈局(依然使用 SimpleAdapter 中的 item 佈局就能夠了)
建立數據源
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);
}
複製代碼
建立本身的 Adapter
// 繼承 BaseAdapter 必需要實現它的 4 個方法
class MyAdapter extends BaseAdapter{
// 返回適配器中所表明的數據集合的條數
// 會首先執行這個方法(連續執行好幾回),若是是 0 則後面的方法就不會執行了
@Override
public int getCount() {
return 0;
}
// 返回數據集合中指定索引 position 對應的數據項
// 手動調用纔會執行
@Override
public Object getItem(int position) {
return null;
}
// 返回列表中與指定索引對應的行 id
// 手動調用纔會執行
@Override
public long getItemId(int position) {
return 0;
}
// 返回指定索引對應的數據的視圖,會屢次調用
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
複製代碼
重點講解一下 BaseAdapter 中的這四個方法
getView()
方法,返回咱們任意想要的佈局類型。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,若是沒有就建立新的
// ListView 的緩存原理前面已經介紹了,從頁面消失進入緩存區的 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;
}
// 優勢:減小了 View 的從新繪製,實現了 view 複用機制
// 缺點:每次都要 findViewById 尋找組件
實現方式三:在方式二的基礎上,進行優化,引入 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;
}
// 優勢:重用 View 的時候不用再次重複使用 findViewById 了。是 ListView 的最佳方案
複製代碼
Adapter 優化總結:
getView 內部應作儘量少的業務邏輯處理。由於 getView 調用很頻繁。
關於可見和不可見的邏輯能夠提早在數據源裏面填充好。
getView 中不要出現大量的對象
最好把建立對象放到 ViewHolder 中
加載圖片,滑動的時候不要加載圖片,會形成 ListView 卡頓,須要在監聽器裏面判斷 ListView 的狀態。
listView.setOnScrollListener(new OnScrollListenr){
@Override
public void onScrollStateChanged(AbsListView lsitView,int scrollState){
if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING){
imageLoader.stopProcessingQueue();
}else{
// 加載圖片
}
}
}
複製代碼
本篇文章大部份內容參考:www.jianshu.com/p/4e8e4fd13… 對做者表示感謝!!