ListView
1. 初始ListView
ListView:
採用介面卡設計模式,讓資料和介面分離,更利於拓展和維護。
3個要素:
(1)ListView控制元件。
(2)介面卡類。它是檢視和資料直接的橋樑,負責提供對資料的訪問,生成每一個列表項第一的View。常見的有ArrayAdapter、SimpleAdapter、BaseAdapter。
(3)資料來源。
如圖用一個字串陣列來測試:

ListView
1.listview是展示大量資料的,需要實現將資料準備好,這些資料可從網上下載,也可從資料庫獲取。
2.陣列中的資料不能直接傳遞給ListView,需要藉助介面卡。最簡單的就是ArrayAdapter,可通過泛型來指定要適配的資料型別,然後在構造方法中把要適配的資料傳入即可。
由於提供的資料是字串,因此反省指定為String,在ArrayAdapter的建構函式中依次傳入當前的上下文、每個條目佈局的id、要適配的資料。這裡使用了Android內建的佈局檔案——android.R.layout.simple_list_item_1,這裡面只有一個TextView,可用於簡單顯示一列文字。這樣介面卡物件就構建好了。
3.呼叫ListView的setAdapter()方法,將構建好的介面卡物件傳遞進去,這樣ListView和介面卡之間的關聯就建立完成了。
2.定製ListView條目的介面
只能顯示一段文字會有些單獨,無法滿足需求,可對其進行定製豐富內容。
e.g. 通過定製,顯示旗幟+國家/地區。
-
字串已經無法滿足要求,可新建實體類Country。
其中有兩個欄位,name為國家和地區的名字,ImageId表示旗幟對應圖片的id。
image.png
-
建立item_country.xml。1個ImageView(iv_flag)+1個TextView(tv_name)。
-
建立自定義介面卡,繼承自ArrayAdapter,泛型指定為Country類。
新建一個類CountryAdapter:
public class CountryAdapter extends ArrayAdapter<Country>{ int resourceId; public CountryAdapter(@NonNull Context context, int resource, @NonNull List<Country> objects) { super(context, resource, objects); resourceId = resource;//記錄佈局資源Id } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { //position為當前項的位置,從0開始計數 Country country = getItem(position);//獲取當前位置條目的資料例項 //把xml轉換成view物件 View view = View.inflate(getContext(),resourceId,null); ImageView imageView = (ImageView)view.findViewById(R.id.iv_flag); TextView textView = (TextView)view.findViewById(R.id.tv_name); //設定資料 imageView.setImageResource(country.getImageId()); textView.setText(country.getName()); return view; } }
CountryAdapter 重寫了父類的建構函式,用於上下文、ListView子專案佈局的id和資料傳遞過來。另外重寫了getView()方法,這個方法在每個子項滾動出螢幕時被呼叫。
修改 ListViewActivity.xml:
public class ListViewActivity extends AppCompatActivity { private List<Country> countryList = new ArrayList<>(); ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list_view); listView = findViewById(R.id.list_view); //初始化資料 initDatas(); //給ListView設定介面卡,從而顯示在介面上 CountryAdapter adapter = new CountryAdapter(this,R.layout.item_country,countryList); listView.setAdapter(adapter); } private void initDatas() { countryList.add(new Country("中國",R.drawable.flag)); countryList.add(new Country("日本",R.drawable.flag)); countryList.add(new Country("韓國",R.drawable.flag)); countryList.add(new Country("印度",R.drawable.flag)); countryList.add(new Country("俄羅斯",R.drawable.flag)); countryList.add(new Country("新加坡",R.drawable.flag)); countryList.add(new Country("馬來西亞",R.drawable.flag)); countryList.add(new Country("越南",R.drawable.flag)); countryList.add(new Country("泰國",R.drawable.flag)); } }
- 定製結果如圖:

定製ListView條目結果
總結:
目前,CountryAdapter的getView()方法中每次都會把佈局重新載入一遍,當ListView快速滑動時就會出現程式卡頓或崩潰。
3. 優化ListView
仔細觀察,getView()方法還有一個convertView引數,這個引數用於將之前載入好的佈局進行快取,以便以後進行重用。修改CountryAdapter中getView()方法的程式碼,如下:

優化1.0
如圖可看出,在getView方法中進行了判斷,如果convertView為空,則通過inflater()方法載入佈局;如果不為空,則直接對convertView進行重用。這樣就大大提高了ListView的執行效率,在快速滾動時也可以表現出更好的效能。
但是還可以繼續優化。無論是通過inflate()載入佈局,還是複用convertView,都會呼叫findViewById()方法去初始化控制元件,這步操作也是可以複用的。
public class CountryAdapter extends ArrayAdapter<Country>{ int rescourId; public CountryAdapter(@NonNull Context context, int resource, @NonNull List<Country> objects) { super(context, resource, objects); rescourId = resource;//記錄佈局資源Id } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { //position為當前項的位置,從0開始計數 Country country = getItem(position);//獲取當前位置條目的資料例項 View view; ViewHolder viewHolder; //當convertView不為空,複用convertView if (convertView == null) { viewHolder = new ViewHolder();//建立ViewHolder //把xml轉換成view物件 view = View.inflate(getContext(), rescourId, null); viewHolder.imageView =view.findViewById(R.id.iv_flag); viewHolder.textView =view.findViewById(R.id.tv_name); view.setTag(viewHolder);//將ViewHolder儲存到view物件中 }else { view = convertView; viewHolder = (ViewHolder)view.getTag(); } //設定資料 viewHolder.imageView.setImageResource(country.getImageId()); viewHolder.textView.setText(country.getName()); return view; } private class ViewHolder { ImageView imageView; TextView textView; } }
當convertView為空時,建立一個ViewHolder物件,並將控制元件的例項都存放在ViewHolder中,然後呼叫View的setTag()方法,將ViewHolder的物件快取到View中。當convertView不為空時,則呼叫View的getTag()方法,把ViewHolder重新取出。這樣所有控制元件的例項都快取在了ViewHolder中,就不會每次都通過findViewById()方法來獲取例項了。
4.ListView的點選事件
使用setOnItemClickListener()來為ListView註冊一個監聽器,當用戶點選任意一個條目時,就會回撥onItemClick()方法,在這個方法中可以通過position引數判斷出使用者點選的是哪一個條目。然後獲取到相應的國家,Toast顯示國家/地區名稱。
public class ListViewActivity extends AppCompatActivity { private List<Country> countryList = new ArrayList<>(); ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list_view); listView = findViewById(R.id.list_view); //初始化資料 initDatas(); //給ListView設定介面卡,從而顯示在介面上 final CountryAdapter adapter = new CountryAdapter(this,R.layout.item_country,countryList); listView.setAdapter(adapter); //設定ListView條目的點選事件 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { /* * 引數:1.parent:指ListView, 2.view:指被點選條目的View, 3.posiyion:條目的位置 *4.id:如果Adapter繼承自ArrayAdapter,id和position一致 * */ Toast.makeText(getApplicationContext(),countryList.get(position).getName(),Toast.LENGTH_SHORT).show(); } }); //長按事件 listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { countryList.remove(position);//刪除長按位置的資料 adapter.notifyDataSetChanged(); //重新整理ListView介面 return true;//返回true時,處理完長按事件,不會再處理點選事件 //return false;//返回false時,處理完長按事件,還會處理點選事件 } }); } private void initDatas() { countryList.add(new Country("中國",R.drawable.flag)); countryList.add(new Country("日本",R.drawable.flag)); countryList.add(new Country("韓國",R.drawable.flag)); countryList.add(new Country("印度",R.drawable.flag)); countryList.add(new Country("俄羅斯",R.drawable.flag)); countryList.add(new Country("新加坡",R.drawable.flag)); countryList.add(new Country("馬來西亞",R.drawable.flag)); countryList.add(new Country("越南",R.drawable.flag)); countryList.add(new Country("泰國",R.drawable.flag)); } }
其中還設定長按事件,需要注意的是,OnItemLongClick()有一個boolean的返回值,代表是否消費掉這個事件。
- 返回false時,處理完長按事件,還會處理點選事件
- 返回true時,處理完長按事件,不會再處理點選事件

長按事件
當長按一個條目時,就會把這個條目從介面中移除。因為資料集合和介面卡繫結,在集合在刪除資料,然後呼叫介面卡notifyDataSetChanged()方法重新整理,就是實現了刪除ListView條目操作。

條目點選事件的傳遞
5.ListView常用屬性
- android:divider 設定分割線的顏色或者指定分割線圖片
- android:dividerHeight 設定分割線的高度
- android:scrollbars = "none" 隱藏滾動條

新增分割線