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複用出現以上的問題。
解決之後的效果圖
這裏寫圖片描述

點擊下載源碼

如有疑問歡迎留言

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章