ListView是Android中最重要的組件之1,幾近每一個Android利用中都會使用ListView。它以垂直列表的方式列出所需的列表項。
(1)將數據填充到布局;
(2)處理用戶的選擇點擊等操作。
(1)ListVeiw:用來展現列表的View;
(2)適配器: 用來把數據映照到ListView上的中介;
(3)數據源: 具體的將被映照的字符串,圖片,或基本組件。
適配器是1個連接數據和AdapterView的橋梁,通過它能有效地實現數據與AdapterView的分離設置,使AdapterView與數據的綁定更加簡便,修改更加方便。
Android開發中的適配器1共可分為:
ArrayAdapter,
BaseAdapter,
CursorAdapter,
HeaderViewListAdapter,
ResourceCursorAdapter,
SimpleAdapter,
SimpleCursorAdapter,
WrapperListAdapter
其中,ArrayAdapter和SimpleAdapter最為常見。
ArrayAdapter最為簡單,只能展現1行字;
SimpleAdapter有最好的擴充性,可以自定義各種各樣的布局,除文本外,還可以放ImageView(圖片)、Button(按鈕)、CheckBox(復選框)等等;
但是實際工作中,經常使用自定義適配器。即繼承于BaseAdapter的自定義適配器類。
android:divider
android:dividerHeight
先看下面代碼:
package com.danny_jiang.day08_listview_introduce;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1 初始化ListView
ListView listView = (ListView) findViewById(R.id.list_Main);
// 2 初始化數據源
String[] data = getResources().getStringArray(R.array.arr);
// 3 初始化適配器
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, data);
// 4 將適配器設置到ListView控件中
listView.setAdapter(adapter);
// 設置ListView的單擊事件
listView.setOnItemClickListener(new OnItemClickListener() {
/**
* @param parent
* ListView
* @param view
* 所點擊的item視圖,也就是TextView
* @param position
* 所點擊item的位置
* @param id
* 所點擊item的id
*/
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (view instanceof TextView) {
TextView textView = (TextView) view;
String content = textView.getText().toString();
Toast.makeText(MainActivity.this, "點擊了 " + content,
Toast.LENGTH_SHORT).show();
}
}
});
// 設置ListView的長按事件
listView.setOnItemLongClickListener(new OnItemLongClickListener() {
/**
* @param parent
* ListView
* @param view
* 所點擊的item視圖,也就是TextView
* @param position
* 所點擊item的位置
* @param id
* 所點擊item的id
*/
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
if (view instanceof TextView) {
TextView textView = (TextView) view;
String content = textView.getText().toString();
Toast.makeText(MainActivity.this, "長按了 " + content,
Toast.LENGTH_SHORT).show();
}
// 返回true,表示將單擊事件進行攔截
return true;
}
});
}
}
布局文件:
<RelativeLayout 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"
tools:context="${relativePackage}.${activityClass}" >
<!--
通過ListView標簽可以聲明列表視圖
android:divider="#FF0000" 設置ListView間隙之間的色彩,必須指定dividerHeight后才會生效
android:dividerHeight="2dp" 設置ListView間隙之間的間隔
-->
<ListView
android:id="@+id/list_Main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#FF0000"
android:dividerHeight="2dp" >
</ListView>
</RelativeLayout>
strings.xml文件:
<?xml version="1.0" encoding="utf⑻"?>
<resources>
<string name="app_name">Day08_ListView_Introduce</string>
<string name="hello_world">Hello world!</string>
<string-array name="arr">
<item>西游記</item>
<item>紅樓夢</item>
<item>李爾王</item>
<item>麥克白</item>
<item>西游記</item>
<item>紅樓夢</item>
<item>李爾王</item>
<item>麥克白</item>
<item>西游記</item>
<item>紅樓夢</item>
<item>李爾王</item>
<item>麥克白</item>
<item>西游記</item>
<item>紅樓夢</item>
<item>李爾王</item>
<item>麥克白</item>
</string-array>
</resources>
效果圖以下:
2.1 使用系統自帶item布局
先看下面代碼:
package com.danny_jiang.day08_listview_simpleadapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class MainActivity extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化ListView控件
listView = (ListView) findViewById(R.id.list_Main);
// 初始化數據源
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
for (int i = 0; i < 10; i++) {
Map<String, String> map = new HashMap<String, String>();
map.put("name", "Android-" + i);
map.put("age", "age-" + i);
map.put("gender", "male");
list.add(map);
}
// 初始化item的布局文件(使用系統自帶布局)
int itemLayout = android.R.layout.simple_list_item_2;
/**
* 初始化SimpleAdapter適配器
*
* @param this
* 上下文
* @param list
* 數據源 List<Map>
* @param itemLayout
* item的布局文件ID,使用此item來顯示ListVirw中的每個item
* @param new String[] { "gender", "age" }
* 是1系列String,key的數組,key是Map中put時所使用的key值
* @param new int[] { android.R.id.text1, android.R.id.text2 }
* 1些列id的int數組,id的順序決定了數據源中item的擺放順序
*
*/
SimpleAdapter adapter = new SimpleAdapter(this, list, itemLayout,
new String[] { "gender", "age" },
new int[] { android.R.id.text1, android.R.id.text2 });
// ListView設置適配器
listView.setAdapter(adapter);
}
}
布局文件:
<RelativeLayout 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"
tools:context="${relativePackage}.${activityClass}" >
<ListView
android:id="@+id/list_Main"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</RelativeLayout>
效果圖以下:
2.2 自定義item布局文件
代碼以下:
package com.danny_jiang.day08_listview_iconitem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class MainActivity extends Activity {
private ListView listView;
// 聲明需要顯示到ListView上的圖片資源ID
private int[] iconId = new int[] { R.drawable.tv01, R.drawable.tv02,
R.drawable.tv03, R.drawable.tv04, R.drawable.tv05,
R.drawable.tv06, R.drawable.tv07, R.drawable.tv08,
R.drawable.tv09, R.drawable.tv10, R.drawable.tv01,};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化ListView控件
listView = (ListView) findViewById(R.id.list_main);
// 初始化數據源
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
for (int i = 0; i < iconId.length; i++) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "Android-" + i);
map.put("icon", iconId[i]);
list.add(map);
}
// 初始化item布局
int itemLayout = R.layout.list_item;
// 初始化適配器SimpleAdapter
SimpleAdapter adapter = new SimpleAdapter(this, list, itemLayout,
new String[] { "icon", "name" }, new int[] { R.id.image,
R.id.name });
// ListView設置適配器
listView.setAdapter(adapter);
}
}
布局文件:
<RelativeLayout 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"
tools:context="${relativePackage}.${activityClass}" >
<ListView
android:id="@+id/list_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
item布局文件:
<?xml version="1.0" encoding="utf⑻"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal" >
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="TEXT" />
<ImageView
android:id="@+id/image"
android:layout_width="200dp"
android:layout_height="120dp"
android:layout_marginLeft="50dp"
android:scaleType="fitXY"
android:src="@drawable/ic_launcher" />
</LinearLayout>
效果圖以下:
代碼以下:
package com.danny_jiang.day08_listview_baseadapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends Activity {
private ListView listView;
private List<Map<String, String>> list = new ArrayList<Map<String,String>>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_Main);
for(int i = 0; i < 50; i++) {
Map<String, String> map = new HashMap<String, String>();
map.put("key", "value" + i);
map.put("asd", "asd" + i);
list.add(map);
}
MyBaseAdapter adapter = new MyBaseAdapter(this, list);
listView.setAdapter(adapter);
}
}
自定義適配器 MyBaseAdapter:
package com.danny_jiang.day08_listview_baseadapter;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class MyBaseAdapter extends BaseAdapter{
// 在自定義Adapter中聲明全局的數據源對象
private List<Map<String, String>> list;
private Context context;
// 聲明布局填充器,通過它可以填充xml布局文件并返回View對象
private LayoutInflater inflater;
public MyBaseAdapter(Context context, List<Map<String, String>> list) {
this.context = context;
inflater = LayoutInflater.from(context);
this.list = list;
}
/**
* 返回ListView需要顯示條數
* 1般情況下需要返回數據源的長度
*/
@Override
public int getCount() {
// 如果數據源是null,則返回0,否則返回數據源的長度
return list == null ? 0 : list.size();
}
/**
* 返回某位置上的item對象
* 當調用ListView.getItemAtPosition(0/1)
* 應當返回數據源中position對應的對象
*/
@Override
public Object getItem(int position) {
return list.get(position);
}
/**
* 返回position對應item的id,1般返回position
*/
@Override
public long getItemId(int position) {
return position;
}
/**
* 返回ListView中position所對應的item需要顯示的視圖對象
* position從0開始
*
* @param position 需要填充視圖的視圖位置,從0開始
* @param convertView ListView的優化機制,Android系統對getView返回視圖的1個復用機制
* @param parent 將item視圖填充到的AdapterView,也就是ListView本身
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
// 通過LayoutInflater填充xml布局文件,將獲得到的convertView對象返回
convertView = inflater.inflate(R.layout.list_item, null);
// 初始化holder,并為其屬性賦值
holder = new ViewHolder();
holder.text1 = (TextView) convertView.findViewById(R.id.text1);
holder.text2 = (TextView) convertView.findViewById(R.id.text2);
// 給convertView添加標簽holder
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// 獲得當前位置中數據源的元素
Map<String, String> map = list.get(position);
String value = map.get("key");
String asd = map.get("asd");
// 為每一個item中的所有UI控件設置屬性值
holder.text1.setText(value);
holder.text2.setText(asd);
return convertView;
}
class ViewHolder{
TextView text1;
TextView text2;
}
}
布局文件:
<RelativeLayout 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"
tools:context="${relativePackage}.${activityClass}" >
<ListView
android:id="@+id/list_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
自定義item布局文件:
<?xml version="1.0" encoding="utf⑻"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal" >
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="TEXT" />
<ImageView
android:id="@+id/image"
android:layout_width="200dp"
android:layout_height="120dp"
android:layout_marginLeft="50dp"
android:scaleType="fitXY"
android:src="@drawable/ic_launcher" />
</LinearLayout>
效果圖以下:
Adapter的作用就是ListView界面與數據之間的橋梁,當列表里的每項顯示到頁面時,都會調用Adapter的getView方法返回1個View。
如果在我們的列表有上千項時會是甚么樣的?是否是會占用極大的系統資源?
Android中有個叫做Recycler的構件,如果你有100個item,其中只有可見的項目存在內存中,其他的在Recycler中。ListView先要求1個type1視圖(getView),然后要求其他可見的item,convertView在getView中是空(null)的。
當item1滾出屏幕,并且1個新的item從屏幕底端上來時,ListView再要求1個type1視圖,convertView此時不是空值了,它的值是item1。你只需設定新的數據,然后返回convertView,沒必要重新創建1個視圖。
項目中的ListView不單單是簡單的文字,常常需要自己定義ListView,如果自己定義的Item中存在諸如ImageButton,Button,CheckBox等子控件,此時這些子控件會將焦點獲得到,所以當點擊item中的子控件時有變化,而item本身的點擊沒有響應。
解決方案的關鍵是:android:descendantFocusability
當1個view獲得焦點時,定義ViewGroup及其子控件之間的關系。
屬性的值有3種:
beforeDescendants:viewgroup會優先其子類控件而獲得到焦點
afterDescendants:viewgroup只有當其子類控件不需要獲得焦點時才獲得焦點
blocksDescendants:viewgroup會覆蓋子類控件而直接取得焦點
通常我們用到的是第3種,即在Item布局的根布局加android:descendantFocusability=”blocksDescendants”的屬性(阻塞子控件搶奪焦點,讓Item具有焦點。這樣ListView的onItemClick就可以被正確觸發,同時item上的button等控件在被點擊時照樣可以觸發本身的點擊事件)就行了,至此ListView點擊的靈異事件告1段落。
1、android:layout_height屬性:
必須將ListView的布局高度屬性設置為非“wrap_content“,(可以是match_parent/fill parent/400dp等絕對數值),如果ListView的布局高度為“wrap_content”,那末getView()就會重復調用。1般來講,1個item會被調用3次左右。
2、ViewHolder:
利用ViewHolder內部類,將item布局文件中需要展現的控件定義為屬性(其實ViewHolder就是1個自定義的模型類)。這樣就把item中散在的多個控件合成1個整體,這樣可以有效地避免圖片錯位。
3、convertView:
ListView的加載是1個item1個item的加載,這樣就會每次都inflate1個item布局,然后findViewById1遍該布局上的所有控件。當數據量大的時候,是不可想象的。而利用Recycle回收利用就能夠解決問題。所以要善于重復利用convertView,這樣可以減少填充布局的進程,減少ViewHolder對象實例化的次數。減少內存開消,提高性能。
4、convertView的setTag():
利用setTag()方法將ViewHolder對象作為標簽附加到convertView上,當convertView被重復利用的時候,由于上面有ViewHolder對象,所以convertView就具有了ViewHolder中的幾個屬性,這樣就節省了findViewById()這個進程。如果1個item有3個控件,如果有100條item,那末在加載數據進程中,就就相當于節省了幾百次findViewById(),節儉了履行findViewById()的時間,提升了加載速度,節省了性能的開消。
5、LayoutInflater對象的inflate()方法:
inflate()方法1般接收兩個參數,第1個參數就是要加載的布局id,第2個參數是指給該布局的外部再嵌套1層父布局,如果不需要就直接傳null。
inflate()方法還有個接收3個參數的方法重載
1.如果root為null,attachToRoot將失去作用,設置任何值都沒成心義。
2.如果root不為null,attachToRoot設為true,則會在加載的布局文件的最外層再嵌套1層root布局。
3.如果root不為null,attachToRoot設為false,則root參數失去作用。
4.在不設置attachToRoot參數的情況下,如果root不為null,attachToRoot參數默許為true。
所以在使用LayoutInflater填充布局的時候,要注意inflate()方法的參數。如果是兩個參數,則第2個參數可以采取null;如果使用3個參數的方法,則要注意參數之間的搭配。
1. 目的:
Android 利用開發中,采取ListView組件來展現數據是很經常使用的功能,當1個利用要展現很多的數據時,1般情況下都不會把所有的數據1次就展現出來,而是通過 分頁的情勢來展現數據,這樣會有更好的用戶體驗。因此,很多利用都是采取分批次加載的情勢來獲得用戶所需的數據。例如:微博客戶端可能會在用戶滑 動至列表底端時自動加載下1頁數據,也可能在底部放置1個”查看更多”按鈕,用戶點擊后,加載下1頁數據。
2. 核心技術點:
a. 借助 ListView組件的OnScrollListener監聽事件,去判斷什么時候該加載新數據;
b.往服務器get傳遞表示頁碼的參數:page。而該page會每加載1屏數據后自動加1;
c.利用addAll()方法不斷往list集合末端添加新數據,使得適配器的數據源每新加載1屏數據就產生變化;
d.利用適配器對象的notifyDataSetChanged()方法。該方法的作用是通知適配器自己及與該數據相關的view,數據已產生變動,要刷新自己、更新數據。
案例演示:
package com.danny_jiang.day09_listview_scroll;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AbsListView.LayoutParams;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
public class MainActivity extends Activity {
private ListView listView;
private boolean isLastShown;
private List<String> data = new ArrayList<String>();
private ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_Main);
for(int i = 0; i < 20; i++) {
data.add("Android-" + i);
}
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, data);
listView.setAdapter(adapter);
/**
* 通過ListView.addHeaderView方法可以給ListView添加1個頭視圖View
*/
ImageView header = new ImageView(this);
/**
* 當需要動態修改UI控件的寬高,需要使用LayoutParams參數對象,
* 指定寬高,并將此LayoutParams設置到相應的UI控件上
*/
//初始化LayoutParams時,需要指定寬高
LayoutParams param = new LayoutParams(LayoutParams.MATCH_PARENT, 200);
//將LayoutParams設置到UI控件上
header.setLayoutParams(param);
//設置ImageView寬高都填充父視圖
header.setScaleType(ScaleType.FIT_XY);
header.setImageResource(R.drawable.food);
listView.addHeaderView(header);
/**
* 通過ListView.addFooterView方法可以給ListView添加1個底部視圖View
*/
View footer = getLayoutInflater().inflate(R.layout.footer_loading, null);
listView.addFooterView(footer);
/**
* ListView的分頁實現
* 通過setOnScrollListener可以給ListView設置滑動監聽
* 1 滑動狀態的改變
* 2 滑動時,item的位置信息
* 通過此OnScrollListener可以判斷出ListView是不是已滑動到屏幕底部,并且滑動停止
*/
listView.setOnScrollListener(new OnScrollListener() {
/**
* 當ListView的滑動狀態發送改變時,此方法被調用
* @param view 指ListView本身
* @param scrollState 表示當前ListView所處于的狀態
* SCROLL_STATE_FLING-⑵ 拋擲狀態--手指離開屏幕前,用力滑了1下,屏幕產生慣性滑動
* SCROLL_STATE_IDLE--0 停止狀態
* SCROLL_STATE_TOUCH_SCROLL-⑴ 手指觸摸屏幕觸發ListView轉動狀態
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
android.util.Log.e("TAG", "onScrollStateChanged");
// 說明ListView已滑動到底部,并且停止狀態
if (isLastShown && scrollState == SCROLL_STATE_IDLE) {
int dataSize = data.size();
for(int i = dataSize; i < dataSize + 20; i++) {
data.add("Andorid-" + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 通知適配器數據源已產生改變
adapter.notifyDataSetChanged();
}
}
/**
* 只要ListView處于滑動狀態,此方法會被1直調用
* @param view 指ListView本身
* @param firstVisibleItem 屏幕當前第1個可見Item的位置,從0開始
* @param visibleItemCount 屏幕當前可見item的個數
* @param totalItemCount ListView中總的item的個數
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
android.util.Log.e("TAG", "onScroll");
// 通過方法參數可以判斷出最后1條是不是已顯示到屏幕上
isLastShown = firstVisibleItem + visibleItemCount == totalItemCount;
}
});
}
}
布局文件:
<RelativeLayout 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"
tools:context="${relativePackage}.${activityClass}" >
<ListView
android:id="@+id/list_Main"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</RelativeLayout>
底部視圖布局文件:footer_loading.xml
<?xml version="1.0" encoding="utf⑻"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal" >
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text="Loading..." />
</LinearLayout>