ListView詳細介紹與使用

image

前言介紹:

關于 ListView 我們大家都應該是非常的熟悉了,在 Android 開發(fā)中是經常用到的填硕,今天就再來回顧一下麦萤,ListView 的使用方法鹿鳖,和一些需要優(yōu)化注意的地方扁眯,還有日常開發(fā)過程中的一些小技巧和經驗。

ListView 簡介

ListView 是 Android 系統(tǒng)為我們提供的一種列表顯示的一種控件翅帜,使用它可以用來顯示我們常見的列表形式姻檀。繼承自抽象類 AdapterView

類的關系圖:

[圖片上傳失敗...(image-2ce24b-1566899080667)]

表現形式

image

這就是一種最簡單的 ListView 的表現形式涝滴,黑色框就是 ListView 控件绣版,其中由一個個的 item 組成(紅色框內容),然后可以通過向下滑動來查看很多的條目歼疮。

工作原理

ListView 僅是作為容器(列表)杂抽,用于裝載顯示數據(就是上面的一個個的紅色框的內容,也稱為 item)韩脏。item 中的具體數據是由適配器(adapter)來提供的缩麸。

適配器(adapter):作為 View (不僅僅指的 ListView)和數據之間的橋梁或者中介,將數據映射到要展示的 View 中赡矢。這就是最簡單適配器模式杭朱,也是適配器的主要作用!

當需要顯示數據的時候吹散,ListView 會從適配器(Adapter)中取出數據弧械,然后來加載數據。

image

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)靶庙。

演示圖來自網絡:

image

具體使用

引入 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 接口及其子類的繼承關系如下圖:

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

其中 ListAdapter 為 AbsAdapter 提供列表項鲤遥,SpinnerAdapter 為 AbsSpinner 提供列表項

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

  • ArrayAdapter:簡單狐援、易用的 Adapter钢坦,用于將數組數據作為數據源綁定到列表項中。支持泛型操作
  • SimpleAdapter:相比 ArrayAdapter 來說啥酱,功能比較強大爹凹,可以將數據源的數據一一的綁定到 item 中的 view 中。
  • CursorAdapter:用于綁定游標(直接從數據庫取出數據)作為列表項的數據源懈涛,和數據庫有關系逛万,不常用泳猬。
  • BaseAdapter:這個是我們在實際開發(fā)中經常用到的批钠,我們需要繼承 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. 創(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 后的結果。
  1. 將 ArrayAdapter 適配器綁定到 ListView 上
listView.setAdapter(arrayAdapter);

使用 ArrayAdapter 的缺點

ArrayAdapter 使用起來非常簡單慷彤,也就導致了功能實現非常局限娄蔼,每個列表項只能是 TextView怖喻。可用的 item 布局要足夠簡單岁诉!

SimpleAdapter

相比 ArrayAdapter 來說锚沸,功能比較強大,可以將數據源的數據一一的綁定到 item 中的 view 中涕癣。

使用步驟:

  1. 在 xml 中添加 ListView
  2. 實現 item 布局(根據實際UI需求)
  3. 創(chuàng)建數據源(數據源形式有要求 List<哗蜈?extends Map<String,? >>)
  4. 創(chuàng)建 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. 創(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);
}


  1. 創(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);

  1. 將 SimpleAdapter 綁定到 ListView 中
listView.setAdapter(simpleAdapter);

BaseAdapter

我們在實際開發(fā)過程中接觸最多的就是 BaseAdapter 了∽阍桑可以最大程度的定制我們自己的 item嫂粟。

實現步驟

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

具體實現步驟

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

  2. 實現 item 布局(依然使用 SimpleAdapter 中的 item 布局就可以了)

  3. 創(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);
    }
    
    
    
  4. 創(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 的繪制過程:
    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京闰,如果沒有就創(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)]

image
  1. 創(chuàng)建自己定義的 Adapter
  2. 將 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对!

image
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末弃榨,一起剝皮案震驚了整個濱河市菩收,隨后出現的幾起案子,更是在濱河造成了極大的恐慌惭墓,老刑警劉巖坛梁,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件而姐,死亡現場離奇詭異腊凶,居然都是意外死亡,警方通過查閱死者的電腦和手機拴念,發(fā)現死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門钧萍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人政鼠,你說我怎么就攤上這事风瘦。” “怎么了公般?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵万搔,是天一觀的道長胡桨。 經常有香客問我,道長瞬雹,這世上最難降的妖魔是什么昧谊? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮酗捌,結果婚禮上呢诬,老公的妹妹穿的比我還像新娘。我一直安慰自己胖缤,他們只是感情好尚镰,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哪廓,像睡著了一般狗唉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涡真,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天敞曹,我揣著相機與錄音,去河邊找鬼综膀。 笑死澳迫,一個胖子當著我的面吹牛,可吹牛的內容都是我干的剧劝。 我是一名探鬼主播橄登,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼讥此!你這毒婦竟也來了拢锹?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤萄喳,失蹤者是張志新(化名)和其女友劉穎卒稳,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體他巨,經...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡充坑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了染突。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捻爷。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖份企,靈堂內的尸體忽然破棺而出也榄,到底是詐尸還是另有隱情,我是刑警寧澤司志,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布甜紫,位于F島的核電站降宅,受9級特大地震影響,放射性物質發(fā)生泄漏囚霸。R本人自食惡果不足惜钉鸯,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望邮辽。 院中可真熱鬧唠雕,春花似錦、人聲如沸吨述。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揣云。三九已至捕儒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邓夕,已是汗流浹背刘莹。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留焚刚,地道東北人点弯。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像矿咕,于是被迫代替她去往敵國和親抢肛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內容