4.1 ListView 常用優化技巧
ListView 的基本用法相信大多數的讀者都已經能夠非常熟練的使用了,下面我們就着重來學習一下使用 ListView 的技巧。
4.1.1 使用 ViewHolder 模式提高效率
ViewHolder 模式是提高 ListView 效率的一個很重要的方法。ViewHolder 模式充分利用了 ListView 的視圖緩存機制,避免了每次在調用 getView() 的時候都去通過 findViewById() 執行個體化控件。據測試,使用 ViewHolder 将提高 50% 以上的效率。使用 ViewHolder 模式來優化 ListView 非常簡單,隻需要在自定義 Adapter 中定義一個内部類 ViewHolder,并将布局中的控件作為成員變量,代碼如下所示:
public final class ViewHolder {
public ImageView imgIcon;
public TextView txtTitle;
}
接下來,隻要在 getView() 方法中通過視圖緩存機制來重用以緩存即可,完整的使用 ViewHolder 建立 ListView Adapter 的執行個體代碼如下所示:
package com.example.test;
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;
import java.util.List;
/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {
private List<String> mData;
private LayoutInflater mInflater;
public ViewHolderAdapter(Context context, List<String> mData) {
this.mData = mData;
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
//判斷是否緩存
if (convertView == null) {
holder = new ViewHolder();
//通過 LayoutInflater 執行個體化布局
convertView = mInflater.inflate(R.layout.viewholder_item, null);
holder.imgIcon = (ImageView) convertView.findViewById(R.id.img_icon);
holder.txtTitle = (TextView) convertView.findViewById(R.id.txt_title);
convertView.setTag(holder);
} else {
//通過 tag 找到緩存的布局
holder = (ViewHolder) convertView.getTag();
}
//設定布局中控件要顯示的視圖
holder.imgIcon.setBackgroundResource(R.mipmap.ic_launcher);
holder.txtTitle.setText(mData.get(position));
return convertView;
}
public final class ViewHolder {
public ImageView imgIcon;
public TextView txtTitle;
}
}
item 的布局檔案 viewholder_view 是:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/img_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/txt_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp" />
</LinearLayout>
MainActivity 的布局檔案 activity_main 是:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
MainActivity 的代碼是:
package com.example.test;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private ListView listView;
private List<String> mData = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
for (int i = ; i < ; i++) {
mData.add(i + "");
}
listView.setAdapter(new ViewHolderAdapter(this, mData));
}
}
效果很簡單如下圖所示,這就是一個簡單的 ListView。
4.1.2 設定項目間分割線
ListView 的各個項目之間,可以通過設定分割線來進行區分,系統提供了 divider 和 dividerHeight 這樣兩個屬性來幫助我們實作這一功能。通過這兩個屬性,也可以控制 ListView 之間的分割線和它的高度。當然分割線不僅僅可以設定為一個顔色,同樣也可以設定為一個圖檔資源,分割線的使用代碼如下所示:
android:divider="#ccc"
android:dividerHeight="10dp"
以上代碼所執行的效果是:
特殊情況下,當設定分割線為如下代碼時,就可以把分割線設定為透明了。
效果如下所示:
4.1.3 隐藏 ListView 的滾動條
預設的 ListView 在滾動時,在右邊會顯示滾動條,訓示目前滑動的位置,我們可以設定 scrollbars 屬性,控件 ListView 的滾動條狀态。特别地,當設定 scrollbars 屬性為 none 的時候,ListView 滾動或者不滾動,就都不會出現滾動條了,代碼如下所示:
4.1.4 取消 ListView 的 Item 點選效果
當點選 ListView 中的一項時,系統預設會出現一個點選效果,在 Android 5.X 上是一個波紋效果,而在 Android 5.X 之下的版本則是一個改變背景顔色的效果,但是可以通過修改 listSelector 屬性來取消掉點選後的回饋效果,代碼如下所示:
當然,也可以直接使用 Android 自帶的透明色來實作這個效果,代碼如下所示:
4.1.5 設定 ListView 需要顯示在第幾頁
ListView 以 Item 為機關進行顯示,預設顯示在第一個 Item,當需要指定具體顯示的 Item 時,可以通過以下代碼來實作:
listView.setSelection(N);
其中 N 就是需要顯示的第 N 個 Item。
當然,這個方法類似 scrollTo,是瞬間完成的移動。除此之外,還可以使用如下代碼來實作平滑移動:
listView.smoothScrollBy(distance,duration);
listView.smoothScrollByOffset(offset);
listView.smoothScrollToPosition(index);
4.1.6 動态修改 ListView
ListView 中的資料在某些情況下是需要改變的,當然可以通過重新設定 ListView 的 Adapter來更新 ListView 的顯示,但這也就需要重新擷取一下資料,相當于重新建立 ListView,這也顯然不是非常友好,而且效率也不會太高。是以,可以使用一個更簡單的方法來實作 ListView 的動态修改,代碼如下所示:
mData.add("new");
adapter.notifyDataSetChanged();
當修改了傳遞給 Adapter 的映射 List 之後,隻需要通過調用 Adapter 的 notifyDataSetChanged() 方法,通知 ListView 更改資料源即可完成對 ListView的動态修改。不過使用這個方法有一點需要注意的是,在使用 adapter.notifyDataSetChanged() 方法時,必須保證傳進 Adapter 的資料 List 是同一個 List 而不能是其他對象,否則将無法實作該效果。下面這個執行個體就示範了如何動态地修改 ListView。代碼如下所示:
package com.example.test;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private ListView listView;
private List<String> mData = new ArrayList<>();
private ViewHolderAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
for (int i = ; i < ; i++) {
mData.add(i + "");
}
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
btnAdd(listView.getChildAt());
}
public void btnAdd(View view) {
mData.add("new");
adapter.notifyDataSetChanged();
listView.setSelection(mData.size() - );
}
}
運作效果如下所示:
4.1.7 周遊 ListView 中的所有 Item
ListView 作為一個 ViewGroup,為我們提供了操縱子 View 的各種方法,最常用的就是通過 getChildAt(i) 來擷取第 i 個子 View,代碼如下所示:
for (int i = ; i < listView.getChildCount(); i++) {
View view = listView.getChildAt(i);
}