ListView詳細介紹與使用

前言介紹:

關於 ListView 咱們你們都應該是很是的熟悉了,在 Android 開發中是常常用到的,今天就再來回顧一下,ListView 的使用方法,和一些須要優化注意的地方,還有平常開發過程當中的一些小技巧和經驗。java

ListView 簡介

ListView 是 Android 系統爲咱們提供的一種列表顯示的一種控件,使用它能夠用來顯示咱們常見的列表形式。繼承自抽象類 AdapterViewandroid

類的關係圖:數據庫

表現形式

這就是一種最簡單的 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 以前繪製分隔條

Adapter 簡介

使用 ListView 的話就離不開 Adapter 了。

Adapter 自己是一個接口,Adapter 接口及其子類的繼承關係以下圖:

  • Adapter 接口派生了 ListAdapter 和 SpinnerAdapter 兩個子接口

其中 ListAdapter 爲 AbsAdapter 提供列表項,SpinnerAdapter 爲 AbsSpinner 提供列表項

ArrayAdapter 、SimpleAdapter 都是 Android API 給咱們提供好的適配器,直接使用便可,不過模式都已經寫死了。

  • ArrayAdapter:簡單、易用的 Adapter,用於將數組數據做爲數據源綁定到列表項中。支持泛型操做
  • SimpleAdapter:相比 ArrayAdapter 來講,功能比較強大,能夠將數據源的數據一一的綁定到 item 中的 view 中。
  • CursorAdapter:用於綁定遊標(直接從數據庫取出數據)做爲列表項的數據源,和數據庫有關係,不經常使用。
  • BaseAdapter:這個是咱們在實際開發中常常用到的,咱們須要繼承 BaseAdapter 來自定義咱們本身的適配器

經常使用適配器介紹與使用

ArrayAdapter

特定:使用簡單、用於將數組、List 形式的數據綁定到列表中做爲數據源,支持泛型操做

步驟:

  1. 在 xml 文件佈局上實現 ListView
  2. 在 Activity 中定義數據源(列表或者數組)
  3. 構造 ArrayAdapter 對象,設置適配器
  4. 將 ListView 綁定到 ArrayAdapter 上
  5. 完事

具體實現:

  1. 添加 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>
複製代碼
  1. 定義數據源
List<String> listData = new ArrayList<>();
for(int i=0;i<20;i++){
    listData.add("item數據"+i);
}
複製代碼
  1. 建立 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 後的結果。
複製代碼
  1. 將 ArrayAdapter 適配器綁定到 ListView 上
listView.setAdapter(arrayAdapter);
複製代碼

使用 ArrayAdapter 的缺點

ArrayAdapter 使用起來很是簡單,也就致使了功能實現很是侷限,每一個列表項只能是 TextView。可用的 item 佈局要足夠簡單!

SimpleAdapter

相比 ArrayAdapter 來講,功能比較強大,能夠將數據源的數據一一的綁定到 item 中的 view 中。

使用步驟:

  1. 在 xml 中添加 ListView
  2. 實現 item 佈局(根據實際UI需求)
  3. 建立數據源(數據源形式有要求 List<?extends Map<String,? >>)
  4. 建立 SimpleAdapter 適配器
  5. 將 SimpleAdapter 適配器綁定到 ListView 中
  6. 完事

具體實現

  1. 在 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>
複製代碼
  1. 實現 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>
複製代碼
  1. 建立數據源,使用 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);
}


複製代碼
  1. 建立 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);

複製代碼
  1. 將 SimpleAdapter 綁定到 ListView 中
listView.setAdapter(simpleAdapter);

複製代碼
BaseAdapter

咱們在實際開發過程當中接觸最多的就是 BaseAdapter 了。能夠最大程度的定製咱們本身的 item。

實現步驟

  1. 在佈局中添加 ListView
  2. 實現 item 佈局(根據 ui 設計的)
  3. 建立數據源
  4. 建立本身的 Adapter 類 繼承 BaseAdapter
  5. 建立自定義的 Adapter 類對象
  6. 將建立的適配器綁定到 ListView 上

具體實現步驟

  1. 佈局中添加 ListView(就再也不寫代碼了,和上面同樣

  2. 實現 item 佈局(依然使用 SimpleAdapter 中的 item 佈局就能夠了)

  3. 建立數據源

    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);
    }
    
    
    複製代碼
  4. 建立本身的 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 中的這四個方法

  • BaseAdapter 之因此十分靈活,就是由於咱們須要本身重寫它的不少方法,尤爲是 getView() 方法,返回咱們任意想要的佈局類型。
  • 結合上面的 4 個方法瞭解一下 ListView 的繪製過程:
    1. 經過調用 getCount() 獲取 ListView 的長度(item 的個數)
    2. 經過調用getView() ,根據 ListView 的長度逐一繪製 ListView 的每一行
    3. 獲取數據時,經過 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 優化總結:

  1. 建立本身定義的 Adapter
  2. 將 Adapter 綁定到 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… 對做者表示感謝!!

相關文章
相關標籤/搜索