Android 列表控件 ListView
在學習了 ScrollView 及 Adapter 兩節內容之后,大家應該對 ListView 有了一些基本的了解,它是一個列表樣式的 ViewGroup,將若干 item 按行排列。ListView 是一個很基本的控件也是 Android 中最重要的控件之一。它可以幫助我們完成多個 View 的垂直排列并支持滾動顯示效果,而它比 ScrollView 更靈活也更易擴展,Adapter 作為 UI 控件和數據源之間的橋梁,會幫我們實現 MVC 模式,所以在實際開發中大多數的列表場景我們會優先考慮使用 ListView 來實現(目前 Google 推出了新的更強大的列表控件——RecyclerView,不過基本原理和 ListView 類似)。
1. ListView 的特性
ListView 在 Android App 中無處不在,比如最常用的“聯系人”就可以通過 ListView 輕松實現。通過 ListView 用戶可以上下滑動來瀏覽列表信息,我們可以在 ListView 中放置各種控件,比如 ImageView、Button、ToggleButton 等來豐富我們的列表樣式。
正因為 ListView 通常是用來展示大量的數據集的控件,所以我們不可能挨個的為每個 item 去設置相應的數據,這時候就要借助 Adapter 來幫助我們完成 UI 控件和數據的綁定工作了。
2. ListView 的基本用法
ListView 相比其他控件來講確實比較特殊,也有很多使用技巧,但是它作為一個 ViewGroup,同樣也有自己的布局屬性、 API 及事件監聽器。
2.1 ListView 的常用屬性
- divider:
設置 item 之間的分隔線,可以設置成顏色,也可以設置成 drawable 資源。 - dividerHeight:
設置分隔線的高度; - footerDividersEnabled:
是否在 footerView(表尾)前繪制一個分隔線,默認為 true; - headerDividersEnabled:
是否在 headerView(表首)前繪制一個分隔線,默認為 true; - android:scrollbars:
設置滾動條樣式,有兩種樣式: horizontal 和 vertical,以及 none 表示隱藏滾動條。
2.2 ListView 的常用 API
- addHeaderView(View v):
添加 headView,headView 會固定顯示在表的第一個元素之前。參數是一個 View 對象,比如可以用作“下拉刷新”的 View; - addFooterView(View v):
添加 footerView,footerView 會固定顯示在表的最后一個元素之后。參數是一個 View 對象,比如可以用作“上拉加載更多”的 View; - addHeaderView(View v, Object data, boolean isSelectable):
添加 headView,第二個參數表示與 headView 綁定的數據對象,第三個參數表示當前這條 item 是否可選中,通?!跋吕⑿隆笨梢栽O置成無法選中; - addFooterView(View v, Object data, boolean isSelectable):
添加 footerView,第二個參數表示與 footerView 綁定的數據對象,第三個參數表示當前這條 item 是否可選中,通常“上拉加載更多”可設置成無法選中。
2.3 點擊事件監聽器
ListView 的樣式示意圖如下:
ListView 中的每個 item 可以設置成任意樣式,可以包含任意的 Android 控件,非常靈活。接下來,我們來一起看看如何使用。
3. ListView 的使用示例
使用 ListView 就一定逃不開 Adapter,在上一節我們介紹了 ArrayAdapter 和 SimpleAdapter 配合 ListView 的使用方法,其實 ArrayAdapter 和 SimpleAdapter 都是繼承 BaseAdapter 做的封裝,那么這一節我們就來看看 BaseAdapter 究竟是何方神圣。為了讓大家更好的看到對比,這一節我們用 BaseAdapter 來實現上一節的水果的列表。
3.1 自定義 Adapter
BaseAdapter 是一個接口,我們自定義 Adapter 就需要實現一個 BaseAdapter 接口,首先創建一個 MyAdapter 類實現 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 個方法是必須實現的,我們具體看看這四個方法分別表示什么以及如何實現:
- public int getCount():
返回列表的長度,即 ListView 需要展示的 item 數量。通常我們會將數據保存在 List 或者數組當中,從而可以通過數據或者 list 獲取列表的長度返回即可。比如如果我們通過 ArrayList 保存列表的數據,那么我們可以通過 List 的 size() 方法獲取列表的長度,并在 getCount() 回調方法中返回,如下:@Override public int getCount() { int count = arrayList.size(); // 計算數據 ArrayList 的長度 return count; // 返回列表的長度 }
- public Object getItem(int position):
獲取位于 position 的 item 對應的數據內容,當 ListView 需要填充第 position 個 item 的時候會回調此函數獲取當前 item 上應該顯示的數據內容,如果數據存在 ArrayList 當中,直接返回當前 position 的 ArrayList 內容即可,如下:@Override public Object getItem(int i): return arrayList.get(i); // item 對應的數據內容 }
- public long getItemId(int position) :
返回當前行的 itemid,itemid 是唯一標識當前 item 的索引,通常情況下我們可以直接返回 position,如下:@Override public long getItemId(int i) { return i; }
- public View getView(int position, View convertView, ViewGroup parent):
當列表中的一個 item 即將被展示的時候系統會回調此函數,我們需要在此回調接口中完成數據與 UI 控件的綁定。通過LayoutInflater
類獲取布局對象,然后通過findViewById
拿到具體的控件,并將數據內容設置到控件當中,比如我們需要在列表中設置一個圖片資源:@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]); // 設置ImageView的圖片資源 return view; }
3.2 編寫布局文件
為了對比學習,本例實現一個和上一節中 SimpleAdapter 的水果列表相同的例子,布局文件可直接引用,具體代碼可以參考 第 23 節第 2 小節的內容。
3.3 創建數據模型
在 Adapter 中創建我們需要保存的數據,和上一節的例子一樣,我們用兩個數組分別保存水果名稱和水果圖片資源:
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 中新增數據更新接口,用于初始數據的設置及后續數據的更新:
MyAdapter.java
public void setData(String[] name, int[] resId)
這樣一來我們就有了數據源,接著按照 第 24 節 3.1 小節中對 4 個回調接口的描述修改回調方法體。主要是在getCount
中返回數組長度,getView
中通過 layout 對象獲取到 TextView 和 ImageView,然后設置水果名稱和圖片,最終 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 對象
- 創建自定義 adapter,即 MyAdapter,并設置數據
- 將自定義 Adapter 設置給 ListView
- 設置 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();
}
});
}
}
運行效果和上一節一樣:
4. 小結
本節主要介紹了 ListView 搭配 BaseAdapter 實現列表功能的方法,BaseAdapter 比 ArrayAdapter 和 SimpleAdapter 擁有更大的可控性,我們可以自己實現很多復雜的功能。目前更推薦使用的是 Google 近年推出的 RecyclerView,不過基本原理和 ListView 類似,本節主要介紹的是基礎的用法,在掌握了基本用法及核心思想之后,可以繼續學習一些優化手段及高級用法。