##寫在前面
很長時間沒有更新部落格了,因為最近一直在寫即時聊天的Demo。跟著教程完成了一個基於XMPP的IM,完成後又寫了一個基於環信的IM。感覺真心學了很多東西,都想分享出來,依次來吧。首先想說的就是關於ListView的一些知識。
ListView是Android中最常見也最難用的控制元件,因為ListView用處很多,幾乎所有的APP都會用到這樣一個控制元件。它基本的用法大家都知道,這裡我就不再贅述了。本文主要是ListView的一些擴充套件知識,有以下幾個方面:
- 更改自帶ListView主題的樣式
- 多樣式ListView
- ViewHolder的兩種寫法
- ListView的效率優化
- 一些乾貨網站推薦
##更改自帶ListView主題的樣式
我們知道,在大部分APP開發環境中,ListView都是需要自定義的。這裡說的自定義意思是需要我們自定義一個介面卡繼承基本的Adapter類,比如BaseAdapter等。因為基本的Adapter不滿足一些複雜的列表效果展示。當我們自定義Adapter的時候,就可以複寫父類的方法,從而實現複雜的列表。比如這樣:
而小部分場景呢,我們只需要基本的Adapter就可以了,比如只需要展示字串,這個時候只需要繫結ArrayAdapter就能滿足需求。
下面就是上面這個ListView的具體程式碼,很簡單,不再解釋。
/**
* @ClassName: MainActivity
* @Description:ListView的擴充
* @author: iamxiarui@foxmail.com
* @date: 2016年5月30日 下午2:15:46
*/
public class MainActivity extends Activity {
private ListView mainListView;
private ArrayList<String> listData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
initAdapter();
}
/**
* @Title: initView
* @Description:初始化View
* @return: void
*/
private void initView() {
mainListView = (ListView) findViewById(R.id.lv_main);
}
/**
* @Title: initData
* @Description:初始化列表資料
* @return: void
*/
private void initData() {
listData = new ArrayList<String>();
for (int i = 0; i < 30; i++) {
listData.add("這是第 " + i + " 條資料");
}
}
/**
* @Title: initAdapter
* @Description:繫結介面卡
* @return: void
*/
private void initAdapter() {
mainListView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listData));
}
}
複製程式碼
可以看出,這裡ListView的樣式是android自帶的樣式android.R.layout.simple-list-item-1。當然說的重點不是這個,現在我們設想有這樣一個需求。我們只需要展示字串,沒有複雜的樣式,不需要自定義Adapter,但是我們需要在android自帶樣式的基礎上,將列表中文字的顏色改成其他顏色或者更換文字大小。那該怎麼做呢?
沒錯!就是複寫**getView()**方法。程式碼如下:
/**
* @Title: initAdapter
* @Description:繫結介面卡,並更改自帶主題樣式
* @return: void
*/
private void initAdapter() {
mainListView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listData) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 這裡我們知道,item中只有一個TextView,可以直接寫
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(Color.BLUE);
textView.setTextSize(36);
return textView;
}
});
}
複製程式碼
現在執行後可以看出,樣式已經發生了改變。
所以在沒有自定義Adapter的前提下,我們仍然能對基本的Adapter做一些樣式上的更改。
##多樣式ListView
多樣式ListView也是比較常見的,意思就是一個ListView中的item有多種樣式。最常見的莫過於聊天記錄的ListView了,傳送者與接收者的訊息是不同的樣式。
那這樣是如何做的呢?毫無疑問,我們需要自定義Adapter,但是在這裡,我們複寫的方法除了常用的**getCount()、getItem()、getItemId()、getView()**四個方法外,我們還需要複寫下面兩個方法:
- public int getViewTypeCount() :這個方法是指定ListView中樣式的數目,預設返回1
- public int getItemViewType(int position) :根據引數positon指定當前item的樣式型別
現在我們試著寫出如下效果的ListView,條目依次不同,相當於一個ListView中存在兩種樣式。
程式碼中的重點就是下面的自定義Adapter,重點中的重點就是上面說到的兩個方法的複寫,具體如下:
/**
* @ClassName: MyListAdapter
* @Description:自定義兩種樣式的介面卡
* @author: iamxiarui@foxmail.com
* @date: 2016年5月30日 下午2:51:00
*/
public class MyListAdapter extends BaseAdapter {
private Context context;
private ArrayList<String> list;
public MyListAdapter(Context context, ArrayList<String> list) {
this.context = context;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* @Title: getViewTypeCount
* @Description:兩種樣式
* @return: 樣式的數目
*/
@Override
public int getViewTypeCount() {
return 2;
}
/**
* @Title: getItemViewType
* @Description:每個item的樣式
* @param position:item位置
* @return: 樣式
*/
@Override
public int getItemViewType(int position) {
// 為了方便起見,我們根據奇偶項來區別樣式,注意從0開始數
if (position % 2 != 0) {
// 奇數項返回0
return 0;
} else {
// 偶數項返回0
return 1;
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
convertView = View.inflate(context, R.layout.item_list, null);
}
view = convertView;
// 如果是奇數項
if (getItemViewType(position) == 0) {
TextView itemText = (TextView) view.findViewById(R.id.tv_item);
itemText.setText(list.get(position));
itemText.setTextColor(Color.BLUE);
}
// 如果是偶數項
else if (getItemViewType(position) == 1) {
TextView itemText = (TextView) view.findViewById(R.id.tv_item);
itemText.setText(list.get(position));
itemText.setTextColor(Color.GRAY);
}
return view;
}
}
複製程式碼
這樣我們就完成了ListView中存在多樣式的實現,有了這個簡單例子,當然可以實現聊天記錄列表或者其他更加複雜的多樣式。
##ViewHolder的兩種寫法
ViewHolder是官方提出的一種優化ListView的工具,主要是減少了一些重複的操作,極大的提升了ListView的一些效能,當然這個大家也都非常熟悉了。
用我們之前的例子來看,它的一般寫法是這樣的:
/**
* @ClassName: ViewHolder
* @Description:定義ViewHolder類
* @author: iamxiarui@foxmail.com
* @date: 2016年5月30日 下午3:28:05
*/
private static class ViewHolder {
TextView itemText;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
// 不存在的時候,建立ViewHolder
viewHolder = new ViewHolder();
convertView = View.inflate(context, R.layout.item_list, null);
viewHolder.itemText = (TextView) convertView.findViewById(R.id.tv_item);
viewHolder.itemText.setText(list.get(position));
// 儲存ViewHolder
convertView.setTag(viewHolder);
} else {
// 存在的話直接複用
viewHolder = (ViewHolder) convertView.getTag();
}
return convertView;
}
複製程式碼
效果圖跟第一幅圖一樣,這裡就不貼了。這種寫法也是谷歌官方的寫法,一般來說大家都這樣寫。
但是在看Github上一個開源庫的時候,看到一個大神是這樣寫的:
/**
* @ClassName: ViewHolder
* @Description:定義ViewHolder類
* @author: iamxiarui@foxmail.com
* @date: 2016年5月30日 下午3:28:05
*/
private static class ViewHolder {
TextView itemText;
// 建構函式中就初始化View
public ViewHolder(View convertView) {
itemText = (TextView) convertView.findViewById(R.id.tv_item);
}
// 得到一個ViewHolder
public static ViewHolder getViewHolder(View convertView) {
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
if (viewHolder == null) {
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
}
return viewHolder;
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = View.inflate(context, R.layout.item_list, null);
}
// 得到一個ViewHolder
viewHolder = ViewHolder.getViewHolder(convertView);
viewHolder.itemText.setText(list.get(position));
return convertView;
}
複製程式碼
對比來看,在定義ViewHolder類的時候,需要實現建構函式,並封裝得到ViewHolder的方法,看似是比原來的複雜很多,但是仔細推敲,這樣做的好處有以下幾點:
- 將ViewHolder的實現方法完全封裝在其本身的類中,功能更強大
- 其方法與Adapter的getView()方法隔離,條理更加清晰
- 當getView()中需要實現多種樣式時,不需要寫重複程式碼
所以在開發過程中,我一般選取第二種方式,雖然看似複雜很多,但是更加符合邏輯。
##ListView的效率優化
關於ListView的優化是很多開發者感到困擾的問題,比如ListView的卡頓、圖片載入異常等問題十分棘手。其實提高ListView的流暢度只要記著兩點就行了:
- 複用View中的item
- 非同步處理耗時任務
這個問題在《Android開發藝術探索》中有專門的介紹,具體大概有四種方法:
###1、使用ViewHoler
不論是getView()方法中的convertView還是使用ViewHolder,其實基本原理都是複用已經用過的item。也就是在滑動過程中不需要一直new出新的View。而關於ViewHolder的具體使用,上面已經說過了,這裡就不在贅述了。
###2、不在getView()中執行耗時任務
其實這個跟網路請求要在子執行緒中執行一樣,不在UI執行緒中執行耗時任務,這樣會造成主執行緒異常的卡頓。同樣的在ListView中也不能在getView()方法中執行耗時任務,比如從網路上請求一些圖片之類的。
關於載入圖片我們可以使用ImageLoader實現,也就是非同步的方式處理。
###3、控制非同步任務的執行頻率
其實這句話的意思就是,滑動的時候不執行非同步載入,等停止滑動的時候再執行非同步任務。這樣就避免了滑動過程中的卡頓問題。
具體實現方式可以在**onScrollStateChange()**方法中定義一個滑動標誌,然後在getView()中判斷這個標誌,如果已經停止滑動,那就啟動載入任務。
###4、開啟硬體加速
一般來說,複用View與非同步載入方式已經能夠解決ListView卡頓的情況,如果你還是不滿意的話,可以嘗試開啟硬體加速,也可以解決一些卡頓問題。開啟方式是在AndroidManifest.xml中對應的Activity新增下面這句:
android:hardwareAccelerated = "true"
專案原始碼
Github-IamXiaRui-Android_ListViewDemo
個人部落格:www.iamxiarui.com 原文連結:http://www.iamxiarui.com/?p=674