Android 解決ListView的複用問題
ListView是大家在專案的開發過程中不可避免要使用到的,使用ListView的同時我們還要使用到介面卡,如果ListView只有一兩條資料的話我們可能不會考慮到用ListView的複用機制,因為你用不用物件的建立和空間的開闢都是那麼多。這樣的話ListView複用出現的問題也就不存在了。然而很多應用展示的條目並不是那一兩條資料,而是很多會多餘一屏的顯示,不然也就不會有載入更多的出現了。如果我們不使用ListView的複用機制的話會造成資源空間的浪費。其實我們的ListView的複用問題是一直存在的,只不過是在有的場景顯示的比較明顯而已。如果你的條目上面有點擊發生變化的情況下,比如說,你的item上面有點選顯示隱藏效果、星星的滑動效果、CheckBox的選擇效果的時候這些複用的問題就會展現出來。關於ListView是如何實現回收複用的先看一張圖片
通過上面的圖片也許大家就明白的差不多了,ListView會預設的建立可見條目的例項,可見的有幾個條目就會建立幾個Item例項,這種情況是在ListView在佈局檔案中設定的高是充滿螢幕的,如果設定高是包裹內容的話,可能就會出現不一樣的效果了。不信的話可以通過列印日誌的看看public View getView(int position, View convertView, ViewGroup parent)這個方法被多呼叫了一次,這是為什麼呢?自定義控制元件的時候說過一個onMeasure測量的方法。這是因為當我們固定listview的高度時(match_parent或直接固定高度),那麼ListView很容易就能計算出容器內可以顯示多少行。但如果我們使用了“wrap_content”,只有在螢幕內控制元件完全載入後才知道到底能顯示多少行資料時,ListView自身便會做一些嘗試性計算。在原始碼中可以發現一些叫做onMeasure的方法在裡面我們會看到這段程式碼
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
MeasureSpec.AT_MOST這個模式在自定義控制元件裡面說過wrap_content其實就是這個模式,after first layout we should maybe start at the first visible position, not 0這句話的意思我的理解是我們可見的第一個佈局的位置不是以0開始的。就是說我們看到的預設的佈局位置為0的其實是已經執行過一次初始化後,可以理解為我們看到的是第一條其實是第二條。如果不相信的話你可以把以下程式碼複製貼上到你的工程裡面試試
public class MainActivity extends Activity {
private Context mContext;
private ListView lv_test;
private List<String>mTestList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initData() {
mTestList = new ArrayList<>();
mTestList.add("我是用來測試的我的我的索引位置為");
mTestList.add("我是用來測試的我的我的索引位置為");
mTestList.add("我是用來測試的我的我的索引位置為");
MyApadater apadater = new MyApadater();
lv_test.setAdapter(apadater);
apadater.notifyDataSetChanged();
}
private void initView() {
mContext = MainActivity.this;
lv_test = (ListView) findViewById(R.id.lv_test);
}
public class MyApadater extends BaseAdapter{
@Override
public int getCount() {
return mTestList.size();
}
@Override
public Object getItem(int position) {
return getItem(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_test,null);
holder.tv_test = (TextView) convertView.findViewById(R.id.tv_test);
System.out.println("我被執行了-------->");
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tv_test.setText(mTestList.get(position)+position);
return convertView;
}
class ViewHolder{
TextView tv_test;
}
}
}
activity_main佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="com.lyxrobert.listview.MainActivity">
<ListView
android:id="@+id/lv_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none" >
</ListView>
</LinearLayout>
item_test佈局檔案
<?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="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_test"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
列印的日誌如下
扯遠了!上面只是一個小插曲。下面我們來看一看複用出現問題的效果圖
這種情況的產生就是複用的原因,比如說一屏顯示9條資料,那麼就是建立9個物件,當地10條資料出來的時候第一條已經不可見了,這時候第10條資料所用的空間是第一條資料的(就好比我們去餐廳吃飯的時候,店家會給我們發個購餐牌,上面標有號碼。假如說第一個人的牌號為0,店家就10個牌,那麼當第11個人過來的時候怎麼辦?這個時候可能擁有1號牌的人員已經在吃了,那麼那個一號牌就會有店家給11號來用),我們在使用號牌的時候號牌肯定會慢慢的變髒,但是這個髒的程度不是很明顯而已,於是大家也就沒有注意到。比如說當第一人在排隊打飯的時候不小心在號牌上面灑了一些東西,店家也沒有注意,也是到第11人使用的時候這個號牌的髒度就明顯出來了。到最後店家也不知道是誰弄髒號牌的,但是他只知道是第一人還是第十一人或是其他使用者。其實我們做的再item進行顯示隱藏、星星的滑動就好比在號牌上面灑了一些東西一樣。你對item做了什麼樣的操作下面複用的都會延續下去,如果號牌不清洗就會一直髒下去。
那麼這個問題如何解決呢?這個問題也好解決。如果店家細心一點,在給第11人的時候發現號牌髒了,這時候店家就知道是第一個人弄髒的,然後店家就會進行清洗再給第11個人。那麼我們的ListView的複用怎麼解決這個問題呢?這個時候我們就可以給Item做一些檢查操作了,我們可以根據ListView的position來標記物件。這樣做複用的效果還是有的,但是需要開闢更多的空間容納更多的物件。就是我們中學階段在餐廳吃飯一樣,自備餐具,自己的餐具可以自己重複的使用。具體的實現程式碼如下
HashMap
if (mHashMap.get(position) == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.softmanager_localapp_item,
null);
mHashMap.put(position, convertView);
convertView.setTag(holder);
} else {
convertView = mHashMap.get(position);
holder = (ViewHolder) convertView.getTag();
}
mHashMap.get(position)和convertView 一樣, 第一次進來的時候是沒有資料的。這樣的話就可以解決ListView複用出現以上的問題。
解決之後的效果圖
如有疑問歡迎留言