ListView的使用

簡介

  • 在Android開發中ListView是比較常用的組件。
  • 以列表的形式展示具體內容。
  • 并且能夠根據數據的長度自適應顯示。
  • 列表的顯示需要三個元素:
    1. ListView中的每一行的View。
    2. 將數據映射到ListView上的適配器。
    3. 填入View上的的數據。

Adapter

  • 適配器。適配器是一個連接數據和AdapterView(ListView就是一個典型的AdapterView)的橋梁,通過它能有效地實現數據與AdapterView的分離設置,使AdapterView與數據的綁定更加簡便,修改更加方便。

提供哪些Adapter

  • ArrayAdapter<T>
    • 綁定一個數組,支持泛型操作。
  • SimpleAdapter
    • 綁定在xml中定義的控件對應的數據。
  • SimpleCursorAdapter
    • 綁定游標得到的數據。
  • BaseAdapter
    • 通用的基礎適配器。

何時使用BaseAdapter

  • 在ListView的使用中,有時候還需要在里面加入按鈕等控件,實現單獨的操作。也就是說,這個ListView不再只是展示數據,也不僅僅是這一行要來處理用戶的操作,而是里面的控件要獲得用戶的焦點。用SimpleAdapter添加一個按鈕到ListView的條目中,會發現添加可以,但是卻無法獲得焦點,點擊操作被ListView的Item所覆蓋。這時候最方便的方法就是使用靈活的適配器BaseAdapter了。

Adapter的執行

  • 在我們給ListView跟Adapter建立橋梁時,也就是調用setAdapter()函數;
  • 在setAdapter()函數中
    • 首先調用Adapter.unregisterDataSetObserver(mDataSetObserver),目的是為了清空之前綁定的mDataSetObserver對象。
    • 接著調用Adapter.getCount()函數,目的是獲取行數(顯示多少行的個數)。
    • 接著調用Adapter.registerDataSetObserver(mDataSetObserver)函數,其原理是采用觀察者設計模式來實現,目的是對綁定Adapter的數據進行監測,一旦數據有更新,就會做相應的處理(下面在詳述)。
    • 接著調用Adapter.getViewTypeCount()函數,目的是獲取樣式的個數(可以這么理解,通常ListView顯示每一行的View都是一樣的,是因為該函數默認返回值是1,但有時需要顯示的每行的View有不一樣的,就好比定制View一樣,奇數行顯示用戶姓名,偶數行顯示用戶照片,這時我們可以設置兩種View來分別顯示,怎么實現呢?我們可以在自定義的Adapter里重寫getViewTypeCount()函數,讓其返回2,具體實現可以參考下面的實例代碼)。
    • 接著調用RecyclerBin.setViewTypeCount(int viewTypeCount),其參數viewTypeCount就是上面調用Adapter.getViewTypeCount()返回的值。該函數的作用是通過參數viewTypeCount的值,創建多少可復用View的List對象,就是說,如果參數viewTypeCount是2的話,那就創建2個ArrayList<View>的對象。
    • 最后通過調用requestLayout()函數。requestLayout()會向系統發一個重新繪制布局的信號,目的是為了調用ListView.onMeasure方法(該函數的實現原理不再討論范圍之內,自己可以去了解一下)。
  • 繼上面流程,接下來,調用ListView.onMeasure()函數。
    • 首先判斷Adapter是否為空,目的是為了調用Adapter.getCount()函數,獲取其行數。
    • 接著調用AbsListView.obtainView()函數,該函數的作用是繪制每一行的View。
      • 首先調用RecyclerBin.getTransientStateView(int position)函數,其目的是通過參數position的值來獲取剛消失的View對象。比如,一共20條記錄,手機屏幕只能顯示10條記錄,當往下滑動時,第11行記錄顯示,可第1行的記錄就消失,該函數就是獲取到第1行的View對象。
        * 調用Adapter.getItemId(position)函數,獲取該position對應的View的id。
      • 接著調用Adapter.getView()函數,獲取一行的View。這就是我們在手機屏幕上看到的一行的樣子。
    • 接著調用RecyclerBin.addScrapView()函數,將剛顯示的View放進可復用View的List集合里。
  • 當我們數據有更新的時候,即調用notifyDataSetChanged()函數的時候。一旦數據發生改變,在AdapterView.AdapterDataSetObserver會做相應的處理。我們在BaseAdapter抽象類也能看到有notifyDataSetChanged()函數,其實現就一行代碼,即mDataSetObservable.notifyChanged();而在該notifyChanged()函數里,采用了同步機制,遍歷觀察者對象里的onChanged()函數。(onChange()函數的具體實現位于AdapterView.AdapterDataSetObserver)。
    • 首先調用Adapter.getCount()函數,目的是獲取行數(顯示多少行的個數)。
    • 最后依舊調用requestLayout()函數。

優化

  • 其實完全可以不用所謂的convertView和ViewHolder,直接導入布局并且設置控件顯示的內容就可以了。
  • 這意味著有多少行數據就需要繪制多少行ListView,這顯然是不可取的。
  • 代碼中,當啟動Activity呈現第一屏ListView的時候,convertView為零。當用戶向下滾動ListView時,上面的條目變為不可見,下面出現新的條目。此時convertView不再為空,而是創建了一系列的convertView的值。當又往下滾一屏的時候,發現第11行的容器用來容納第22行,第12行的容器用來容納第23行。也就是說convertView相當于一個緩存,開始為0,當有條目變為不可見,它緩存了它的數據,后面再出來的條目只需要更新數據就可以了,這樣大大節省了系統資源的開銷。
  • 繼續優化。雖然重復利用了已經繪制的view,但是要得到其中的控件,需要在控件的容器中通過findViewById的方法來獲得。如果這個容器非常復雜,這顯然會增加系統資源的開銷。
  • 引入Tag。或許不是最好的辦法,但是它確實能使ListView變得更流暢。
  • 代碼中,當convertView為空時,用setTag()方法為每個View綁定一個存放控件的ViewHolder對象。當convertView不為空,重復利用已經創建的view的時候,使用getTag()方法獲取綁定的ViewHolder對象,這樣就避免了findViewById對控件的層層查詢,而是快速定位到控件。

容易出現的Exception

  • java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification.
    • 復現場景:
    • 對ListView和Adapter實現展示數據,在初始化數據時分為兩部分:本地和網絡。所以在Adapter的數據初始化的時候,先將本地數據添加到了容器內。同時發起網絡請求,等加載完畢后追加到容器內。
    • 問題出現在:
    • 當網絡請求完畢后追加數據的時候,拋出上述異常。
    • 原因分析:
    • Adapter的數據內容已經改變,但是ListView卻未接收到通知。當網絡請求完畢后,直接在網絡線程(非UI線程)里調用了在Adapter中新增的自定義方法addData(List)更新數據,而addData(List)方法內更新換完數據后,通過Handler發送Message策略后調用Adapter的notifyDataSetChanged()方法通知更新。這樣的話,就不能保證Adapter的數據更新時,立馬調用notifyDataSetChanged()通知ListView,其實是這兩個線程之間的時間差引起的數據不同步,導致ListView的layoutChildren()中訪問Adapter的getCount()方法時,Adapter內已經是最新數據源,而ListView內的緩存數據Count仍是舊數據的Count,導致出現了該Exception。
    • 解決方案:
    • 把addData(List)方法內更新數據的代碼挪出來,和notifyDataSetChanged()方法一同放在Handler里,保證數據更新時及時通知ListView。
    • 如何避免:
    • 其實,我們在使用ListView控件來展示數據的時候,首先確保Adapter的數據更新后一定要調用notifyDataSetChanged()方法通知ListView數據更新和notifyDataSetChanged()放在UI線程內,且必須同步順序執行,不可異步,其次查看getCount()方法返回值是否正確。

補充

實例代碼(使用getViewTypeCount和getItemViewType)

package com.cienet.android;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

import com.cienet.android.adapter.FourAdapter;

import java.util.ArrayList;
import java.util.HashMap;

public class FourActivity extends Activity {

    private ListView mListView;
    private MyAdapter mAdapter;
    private ArrayList<HashMap<String, Object>> mList;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.lv);

        getDate();

        mAdapter = new MyAdapter(this, mList);
        //為ListView綁定適配器
        mListView.setAdapter(mAdapter);

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {

            }
        });
    }

    private void getDate() {
        ArrayList<HashMap<String, Object>> listItem =
                new ArrayList<HashMap<String, Object>>();
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("province", "江蘇省");
        map.put("type", 0);
        listItem.add(map);

        HashMap<String, Object> map1 = new HashMap<String, Object>();
        map1.put("city", "南京市");
        map1.put("type", 1);
        listItem.add(map1);

        HashMap<String, Object> map8 = new HashMap<String, Object>();
        map8.put("city", "鎮江市");
        map8.put("type", 1);
        listItem.add(map8);

        HashMap<String, Object> map2 = new HashMap<String, Object>();
        map2.put("province", "浙江省");
        map2.put("type", 0);
        listItem.add(map2);

        HashMap<String, Object> map3 = new HashMap<String, Object>();
        map3.put("city", "寧波市");
        map3.put("type", 1);
        listItem.add(map3);

        HashMap<String, Object> map4 = new HashMap<String, Object>();
        map4.put("province", "山東省");
        map4.put("type", 0);
        listItem.add(map4);

        HashMap<String, Object> map5 = new HashMap<String, Object>();
        map5.put("city", "濟南市");
        map5.put("type", 1);
        listItem.add(map5);

        HashMap<String, Object> map6 = new HashMap<String, Object>();
        map6.put("province", "安徽省");
        map6.put("type", 0);
        listItem.add(map6);

        HashMap<String, Object> map7 = new HashMap<String, Object>();
        map7.put("city", "合肥市");
        map7.put("type", 1);
        listItem.add(map7);

        mList = listItem;
    }
}


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <TextView
        android:id="@+id/item_province_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
</RelativeLayout>

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <TextView
        android:id="@+id/item_city_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"/>
</RelativeLayout>

package com.cienet.android.adapter;

import java.util.ArrayList;
import java.util.HashMap;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.cienet.android.R;

public class MyAdapter extends BaseAdapter {
    public static final int ITEM_TITLE = 0;
    public static final int ITEM_INTRODUCE = 1;
    private ArrayList<HashMap<String, Object>> mList;
    private Context context;

    private LayoutInflater inflater;

    public MyAdapter(Context context, ArrayList<HashMap<String, Object>> mList) {
        this.context = context;
        this.mList = mList;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        System.out.println("mList.size()" + mList.size());
        return mList.size();
    }

    @Override
    public Object getItem(int arg0) {
        return mList.get(arg0);
    }

    @Override
    public int getItemViewType(int position) {
        return (Integer) mList.get(position).get("type");
    }

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public long getItemId(int arg0) {
        return arg0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        int type = getItemViewType(position);
        FirstHolder firstHolder = null;
        SecondHolder secondHolder = null;
        if (convertView == null) {
            switch (type) {
                case ITEM_TITLE:
                    convertView = inflater.inflate(R.layout.item_province, null);
                    firstHolder = new FirstHolder(convertView);
                    firstHolder.provinceTitle.setText(mList.get(position).get("province").toString());
                    convertView.setTag(firstHolder);
                    break;
                case ITEM_INTRODUCE:
                    convertView = inflater.inflate(R.layout.item_city, null);
                    secondHolder = new SecondHolder(convertView);
                    secondHolder.cityTitle.setText(mList.get(position).get("city").toString());
                    convertView.setTag(secondHolder);
                    break;
                default:
                    break;
            }
        } else {
            switch (type) {
                case ITEM_TITLE:
                    firstHolder = (FirstHolder) convertView.getTag();
                    firstHolder.provinceTitle.setText(mList.get(position).get("province").toString());
                    break;
                case ITEM_INTRODUCE:
                    secondHolder = (SecondHolder) convertView.getTag();
                    secondHolder.cityTitle.setText(mList.get(position).get("city").toString());
                    break;

                default:
                    break;
            }
        }

        return convertView;
    }

    class FirstHolder {
        TextView provinceTitle;

        FirstHolder(View view) {
            provinceTitle = (TextView) view.findViewById(R.id.item_province_text);
        }
    }

    class SecondHolder {
        TextView cityTitle;

        SecondHolder(View view) {
            cityTitle = (TextView) view.findViewById(R.id.item_city_text);
        }
    }
}

閱讀ListView源代碼

  • Android源代碼可以到Github去查看閱讀。源代碼鏈接
  • 引用文件
    • platform_frameworks_base/blob/master/core/java/android/widget/AdapterView.java
    • platform_frameworks_base/blob/master/core/java/android/database/DataSetObservable.java
    • platform_frameworks_base/tree/master/core/java/android/widget/AbsListView.java
  • 下面代碼來自'AdapterView.java'文件,主要是響應數據的更改處理。
    class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;

        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();

            // Detect the case where a cursor that was previously invalidated has
            // been repopulated with new data.
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            mDataChanged = true;

            if (AdapterView.this.getAdapter().hasStableIds()) {
                // Remember the current state for the case where our hosting activity is being
                // stopped and later restarted
                mInstanceState = AdapterView.this.onSaveInstanceState();
            }

            // Data is invalid so we should reset our state
            mOldItemCount = mItemCount;
            mItemCount = 0;
            mSelectedPosition = INVALID_POSITION;
            mSelectedRowId = INVALID_ROW_ID;
            mNextSelectedPosition = INVALID_POSITION;
            mNextSelectedRowId = INVALID_ROW_ID;
            mNeedSync = false;

            checkFocus();
            requestLayout();
        }

        public void clearSavedState() {
            mInstanceState = null;
        }
    }
* 看了這段代碼,可以看出調用notifyDataSetChanged()跟調用notifyDataSetInvalidated()兩者之間的區別。
* 運用了一種設計模式:觀察者設計模式。借此可以學習或者鞏固這一設計模式。
* AdapterView之所以能對Adapter的數據更新進行響應,原因就是在Adapter上注冊了一個數據觀察者(AdapterDataSetObserver)的內部類,所以,我們只要對Adpater狀態的改變發送一個通知,就可以讓AdapterView調用相應的方法。
* 查看AdapterDataSetObserver內部類的父類DataSetObservable。
* 對比onChange()與onInvalidated()兩個方法,前者會對當前位置的狀態進行同步,后者會重置所有位置的狀態。
  • 下面代碼來自'AdapterView.java'文件,主要是知道點擊view的時候,獲取對應的位置。
    /**
     * Get the position within the adapter's data set for the view, where view is a an adapter item
     * or a descendant of an adapter item.
     *
     * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
     *        AdapterView at the time of the call.
     * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
     *         if the view does not correspond to a list item (or it is not currently visible).
     */
    public int getPositionForView(View view) {
        View listItem = view;
        try {
            View v;
            while (!(v = (View) listItem.getParent()).equals(this)) {
                listItem = v;
            }
        } catch (ClassCastException e) {
            // We made it up to the window without find this list view
            return INVALID_POSITION;
        }

        // Search the children for the list item
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            if (getChildAt(i).equals(listItem)) {
                return mFirstPosition + i;
            }
        }

        // Child not found!
        return INVALID_POSITION;
    }
  • 下面代碼來自'AdapterView.java'的直接子類'AbsListView.java'文件,主要是獲取到AdapterView里面item的view對應的位置。
    /**
     * Get a view and have it show the data associated with the specified
     * position. This is called when we have already discovered that the view is
     * not available for reuse in the recycle bin. The only choices left are
     * converting an old view or making a new one.
     *
     * @param position The position to display
     * @param isScrap Array of at least 1 boolean, the first entry will become true if
     *                the returned view was taken from the scrap heap, false if otherwise.
     *
     * @return A view displaying the data associated with the specified position
     */
    View obtainView(int position, boolean[] isScrap) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        isScrap[0] = false;
        View scrapView;

        scrapView = mRecycler.getTransientStateView(position);
        if (scrapView == null) {
            scrapView = mRecycler.getScrapView(position);
        }

        View child;
        if (scrapView != null) {
            child = mAdapter.getView(position, scrapView, this);

            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            if (child != scrapView) {
                mRecycler.addScrapView(scrapView, position);
                if (mCacheColorHint != 0) {
                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
                }
            } else {
                isScrap[0] = true;

                // Clear any system-managed transient state so that we can
                // recycle this view and bind it to different data.
                if (child.isAccessibilityFocused()) {
                    child.clearAccessibilityFocus();
                }

                child.dispatchFinishTemporaryDetach();
            }
        } else {
            child = mAdapter.getView(position, null, this);

            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
        }

        if (mAdapterHasStableIds) {
            final ViewGroup.LayoutParams vlp = child.getLayoutParams();
            LayoutParams lp;
            if (vlp == null) {
                lp = (LayoutParams) generateDefaultLayoutParams();
            } else if (!checkLayoutParams(vlp)) {
                lp = (LayoutParams) generateLayoutParams(vlp);
            } else {
                lp = (LayoutParams) vlp;
            }
            lp.itemId = mAdapter.getItemId(position);
            child.setLayoutParams(lp);
        }

        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            if (mAccessibilityDelegate == null) {
                mAccessibilityDelegate = new ListItemAccessibilityDelegate();
            }
            if (child.getAccessibilityDelegate() == null) {
                child.setAccessibilityDelegate(mAccessibilityDelegate);
            }
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return child;
    }

    class ListItemAccessibilityDelegate extends AccessibilityDelegate {
        @Override
        public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
            // If the data changed the children are invalid since the data model changed.
            // Hence, we pretend they do not exist. After a layout the children will sync
            // with the model at which point we notify that the accessibility state changed,
            // so a service will be able to re-fetch the views.
            if (mDataChanged) {
                return null;
            }
            return super.createAccessibilityNodeInfo(host);
        }

        @Override
        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
            super.onInitializeAccessibilityNodeInfo(host, info);

            final int position = getPositionForView(host);
            onInitializeAccessibilityNodeInfoForItem(host, position, info);
        }

        @Override
        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
            if (super.performAccessibilityAction(host, action, arguments)) {
                return true;
            }

            final int position = getPositionForView(host);
            final ListAdapter adapter = getAdapter();

            if ((position == INVALID_POSITION) || (adapter == null)) {
                // Cannot perform actions on invalid items.
                return false;
            }

            if (!isEnabled() || !adapter.isEnabled(position)) {
                // Cannot perform actions on disabled items.
                return false;
            }

            final long id = getItemIdAtPosition(position);

            switch (action) {
                case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
                    if (getSelectedItemPosition() == position) {
                        setSelection(INVALID_POSITION);
                        return true;
                    }
                } return false;
                case AccessibilityNodeInfo.ACTION_SELECT: {
                    if (getSelectedItemPosition() != position) {
                        setSelection(position);
                        return true;
                    }
                } return false;
                case AccessibilityNodeInfo.ACTION_CLICK: {
                    if (isClickable()) {
                        return performItemClick(host, position, id);
                    }
                } return false;
                case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
                    if (isLongClickable()) {
                        return performLongPress(host, position, id);
                    }
                } return false;
            }

            return false;
        }
    }

    /**
     * Call the OnItemClickListener, if it is defined. Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @param view The view within the AdapterView that was clicked.
     * @param position The position of the view in the adapter.
     * @param id The row id of the item that was clicked.
     * @return True if there was an assigned OnItemClickListener that was
     *         called, false otherwise is returned.
     */
    public boolean performItemClick(View view, int position, long id) {
        if (mOnItemClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            if (view != null) {
                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            }
            mOnItemClickListener.onItemClick(this, view, position, id);
            return true;
        }

        return false;
    }
* 運用了一種設計模式:委托代理模式。借此可以學習或者鞏固這一設計模式。
* obtainView() 方法可知,這是一個用于生成itemView的方法。
* ListItemAccessibilityDelegate類應該是一個委托類,對item的動作進行初始化,以及響應對應的操作。
* 從ListItemAccessibilityDelegate類代碼可以知道,一個Item的View為什么對Click,LongClick,Select動作進行響應。
* 通過調用performItemClick()把事件調用到AdapterView.java里的performItemClick() 里面的監聽器方法.

Adapter內部執行流程

  • 為ListView設置適配器的函數是ListView.setAdapter。
    • 下面函數中,首先調用了Adapter.getCount函數,接著為Adapter注冊了一個監聽器AdapterDataSetObserver。
    public void setAdapter(ListAdapter adapter) {
        ...
        if (mAdapter != null) {
            ...
            mItemCount = mAdapter.getCount();
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
            ...
        } else {
            ...
        }

        requestLayout();
    }
  • 下面函數是監測Adapter的數據是否變更。同時也可得知調用Adapter.notifyDataSetChanged()跟調用notifyDataSetInvalidated()兩者之間的區別。
    class AdapterDataSetObserver extends DataSetObserver {
        ...
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();
            ...
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            mDataChanged = true;
            ...
            requestLayout();
        }

        public void clearSavedState() {
            mInstanceState = null;
        }
    }
  • requestLayout()會向系統發一個重新繪制布局的信號,調用ListView.onMeasure方法。
    • 調用了AbsListView.obtainView方法。
    • 調用了AbsListView.RecycleBin.getTransientStateView方法。
    • 調用了AbsListView.RecycleBin.getScrapView方法。
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
                heightMode == MeasureSpec.UNSPECIFIED)) {
            final View child = obtainView(0, mIsScrap);
            ...
        }
        ...
    }

    View obtainView(int position, boolean[] isScrap) {
        ...
        scrapView = mRecycler.getTransientStateView(position);
        if (scrapView == null) {
            scrapView = mRecycler.getScrapView(position);
        }

        View child;
        if (scrapView != null) {
            child = mAdapter.getView(position, scrapView, this);
            ...
        } else {
            child = mAdapter.getView(position, null, this);
            ...
        }

        if (mAdapterHasStableIds) {
            ...
            lp.itemId = mAdapter.getItemId(position);
            child.setLayoutParams(lp);
        }
        ...
        return child;
    }

    class RecycleBin {
        ...
        View getTransientStateView(int position) {
            if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
                long id = mAdapter.getItemId(position);
                ...
                return result;
            }
            ...
            return null;
        }

        View getScrapView(int position) {
            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position);
            } else {
                int whichScrap = mAdapter.getItemViewType(position);
                ...
            }
            return null;
        }
        ...
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容