Android 列表控件 ListView
在學(xué)習(xí)了 ScrollView 及 Adapter 兩節(jié)內(nèi)容之后,大家應(yīng)該對 ListView 有了一些基本的了解,它是一個列表樣式的 ViewGroup,將若干 item 按行排列。ListView 是一個很基本的控件也是 Android 中最重要的控件之一。它可以幫助我們完成多個 View 的垂直排列并支持滾動顯示效果,而它比 ScrollView 更靈活也更易擴展,Adapter 作為 UI 控件和數(shù)據(jù)源之間的橋梁,會幫我們實現(xiàn) MVC 模式,所以在實際開發(fā)中大多數(shù)的列表場景我們會優(yōu)先考慮使用 ListView 來實現(xiàn)(目前 Google 推出了新的更強大的列表控件——RecyclerView,不過基本原理和 ListView 類似)。
1. ListView 的特性
ListView 在 Android App 中無處不在,比如最常用的“聯(lián)系人”就可以通過 ListView 輕松實現(xiàn)。通過 ListView 用戶可以上下滑動來瀏覽列表信息,我們可以在 ListView 中放置各種控件,比如 ImageView、Button、ToggleButton 等來豐富我們的列表樣式。
正因為 ListView 通常是用來展示大量的數(shù)據(jù)集的控件,所以我們不可能挨個的為每個 item 去設(shè)置相應(yīng)的數(shù)據(jù),這時候就要借助 Adapter 來幫助我們完成 UI 控件和數(shù)據(jù)的綁定工作了。
2. ListView 的基本用法
ListView 相比其他控件來講確實比較特殊,也有很多使用技巧,但是它作為一個 ViewGroup,同樣也有自己的布局屬性、 API 及事件監(jiān)聽器。
2.1 ListView 的常用屬性
- divider:
設(shè)置 item 之間的分隔線,可以設(shè)置成顏色,也可以設(shè)置成 drawable 資源。 - dividerHeight:
設(shè)置分隔線的高度; - footerDividersEnabled:
是否在 footerView(表尾)前繪制一個分隔線,默認(rèn)為 true; - headerDividersEnabled:
是否在 headerView(表首)前繪制一個分隔線,默認(rèn)為 true; - android:scrollbars:
設(shè)置滾動條樣式,有兩種樣式: horizontal 和 vertical,以及 none 表示隱藏滾動條。
2.2 ListView 的常用 API
- addHeaderView(View v):
添加 headView,headView 會固定顯示在表的第一個元素之前。參數(shù)是一個 View 對象,比如可以用作“下拉刷新”的 View; - addFooterView(View v):
添加 footerView,footerView 會固定顯示在表的最后一個元素之后。參數(shù)是一個 View 對象,比如可以用作“上拉加載更多”的 View; - addHeaderView(View v, Object data, boolean isSelectable):
添加 headView,第二個參數(shù)表示與 headView 綁定的數(shù)據(jù)對象,第三個參數(shù)表示當(dāng)前這條 item 是否可選中,通?!跋吕⑿隆笨梢栽O(shè)置成無法選中; - addFooterView(View v, Object data, boolean isSelectable):
添加 footerView,第二個參數(shù)表示與 footerView 綁定的數(shù)據(jù)對象,第三個參數(shù)表示當(dāng)前這條 item 是否可選中,通?!吧侠虞d更多”可設(shè)置成無法選中。
2.3 點擊事件監(jiān)聽器
ListView 的樣式示意圖如下:
ListView 中的每個 item 可以設(shè)置成任意樣式,可以包含任意的 Android 控件,非常靈活。接下來,我們來一起看看如何使用。
3. ListView 的使用示例
使用 ListView 就一定逃不開 Adapter,在上一節(jié)我們介紹了 ArrayAdapter 和 SimpleAdapter 配合 ListView 的使用方法,其實 ArrayAdapter 和 SimpleAdapter 都是繼承 BaseAdapter 做的封裝,那么這一節(jié)我們就來看看 BaseAdapter 究竟是何方神圣。為了讓大家更好的看到對比,這一節(jié)我們用 BaseAdapter 來實現(xiàn)上一節(jié)的水果的列表。
3.1 自定義 Adapter
BaseAdapter 是一個接口,我們自定義 Adapter 就需要實現(xiàn)一個 BaseAdapter 接口,首先創(chuàng)建一個 MyAdapter 類實現(xiàn) BaseAdapter 接口,如下:
package com.emercy.myapplication;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
public class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return 0;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
可以看到,繼承自 BaseAdapter 的類有 4 個方法是必須實現(xiàn)的,我們具體看看這四個方法分別表示什么以及如何實現(xiàn):
- public int getCount():
返回列表的長度,即 ListView 需要展示的 item 數(shù)量。通常我們會將數(shù)據(jù)保存在 List 或者數(shù)組當(dāng)中,從而可以通過數(shù)據(jù)或者 list 獲取列表的長度返回即可。比如如果我們通過 ArrayList 保存列表的數(shù)據(jù),那么我們可以通過 List 的 size() 方法獲取列表的長度,并在 getCount() 回調(diào)方法中返回,如下:@Override public int getCount() { int count = arrayList.size(); // 計算數(shù)據(jù) ArrayList 的長度 return count; // 返回列表的長度 }
- public Object getItem(int position):
獲取位于 position 的 item 對應(yīng)的數(shù)據(jù)內(nèi)容,當(dāng) ListView 需要填充第 position 個 item 的時候會回調(diào)此函數(shù)獲取當(dāng)前 item 上應(yīng)該顯示的數(shù)據(jù)內(nèi)容,如果數(shù)據(jù)存在 ArrayList 當(dāng)中,直接返回當(dāng)前 position 的 ArrayList 內(nèi)容即可,如下:@Override public Object getItem(int i): return arrayList.get(i); // item 對應(yīng)的數(shù)據(jù)內(nèi)容 }
- public long getItemId(int position) :
返回當(dāng)前行的 itemid,itemid 是唯一標(biāo)識當(dāng)前 item 的索引,通常情況下我們可以直接返回 position,如下:@Override public long getItemId(int i) { return i; }
- public View getView(int position, View convertView, ViewGroup parent):
當(dāng)列表中的一個 item 即將被展示的時候系統(tǒng)會回調(diào)此函數(shù),我們需要在此回調(diào)接口中完成數(shù)據(jù)與 UI 控件的綁定。通過LayoutInflater
類獲取布局對象,然后通過findViewById
拿到具體的控件,并將數(shù)據(jù)內(nèi)容設(shè)置到控件當(dāng)中,比如我們需要在列表中設(shè)置一個圖片資源:@Override public View getView(int i, View view, ViewGroup viewGroup) { view = inflter.inflate(R.layout.activity_gridview, null); // 獲取布局對象 ImageView icon = (ImageView) view.findViewById(R.id.icon); // 通過ID拿到具體的View對象 icon.setImageResource(flags[i]); // 設(shè)置ImageView的圖片資源 return view; }
3.2 編寫布局文件
為了對比學(xué)習(xí),本例實現(xiàn)一個和上一節(jié)中 SimpleAdapter 的水果列表相同的例子,布局文件可直接引用,具體代碼可以參考 第 23 節(jié)第 2 小節(jié)的內(nèi)容。
3.3 創(chuàng)建數(shù)據(jù)模型
在 Adapter 中創(chuàng)建我們需要保存的數(shù)據(jù),和上一節(jié)的例子一樣,我們用兩個數(shù)組分別保存水果名稱和水果圖片資源:
String[] mDataName = {"蘋果", "梨", "香蕉", "桃子", "西瓜", "荔枝", "橘子"};
int[] mDataImage = {R.drawable.apple, R.drawable.pear, R.drawable.banana, R.drawable.peach,
R.drawable.watermelon, R.drawable.lychee, R.drawable.orange, R.drawable.orange};
在 MyAdapter 中新增數(shù)據(jù)更新接口,用于初始數(shù)據(jù)的設(shè)置及后續(xù)數(shù)據(jù)的更新:
MyAdapter.java
public void setData(String[] name, int[] resId)
這樣一來我們就有了數(shù)據(jù)源,接著按照 第 24 節(jié) 3.1 小節(jié)中對 4 個回調(diào)接口的描述修改回調(diào)方法體。主要是在getCount
中返回數(shù)組長度,getView
中通過 layout 對象獲取到 TextView 和 ImageView,然后設(shè)置水果名稱和圖片,最終 MyAdapter 類的代碼如下:
package com.emercy.myapplication;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MyAdapter extends BaseAdapter {
private Context mContext;
private String[] mName;
private int[] mResId;
public MyAdapter(Context context) {
mContext = context;
}
public void setData(String[] name, int[] resId) {
mName = name;
mResId = resId;
}
@Override
public int getCount() {
return mName.length;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_view, null);
TextView name = convertView.findViewById(R.id.textView);
ImageView image = convertView.findViewById(R.id.imageView);
name.setText(mName[position]);
image.setImageResource(mResId[position]);
return convertView;
}
}
3.4 編寫主 Activity
ListView 的核心適配邏輯都在 Adapter 中完成,主 Activity 比較簡單,主要做以下幾件事:
- 獲取 listView 對象
- 創(chuàng)建自定義 adapter,即 MyAdapter,并設(shè)置數(shù)據(jù)
- 將自定義 Adapter 設(shè)置給 ListView
- 設(shè)置 ListView 列表項的點擊事件
代碼如下:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
public class MainActivity extends Activity {
ListView mListView;
String[] mDataName = {"蘋果", "梨", "香蕉", "桃子", "西瓜", "荔枝", "橘子"};
int[] mDataImage = {R.drawable.apple, R.drawable.pear, R.drawable.banana, R.drawable.peach,
R.drawable.watermelon, R.drawable.lychee, R.drawable.orange, R.drawable.orange};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = findViewById(R.id.listView);
MyAdapter adapter = new MyAdapter(this);
adapter.setData(mDataName, mDataImage);
mListView.setAdapter(adapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Toast.makeText(getApplicationContext(), mDataName[i], Toast.LENGTH_LONG).show();
}
});
}
}
運行效果和上一節(jié)一樣:
4. 小結(jié)
本節(jié)主要介紹了 ListView 搭配 BaseAdapter 實現(xiàn)列表功能的方法,BaseAdapter 比 ArrayAdapter 和 SimpleAdapter 擁有更大的可控性,我們可以自己實現(xiàn)很多復(fù)雜的功能。目前更推薦使用的是 Google 近年推出的 RecyclerView,不過基本原理和 ListView 類似,本節(jié)主要介紹的是基礎(chǔ)的用法,在掌握了基本用法及核心思想之后,可以繼續(xù)學(xué)習(xí)一些優(yōu)化手段及高級用法。