美文网首页
ListView的使用

ListView的使用

作者: 上善若水Ryder | 来源:发表于2016-03-29 13:22 被阅读0次

    简介

    • 在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;
            }
            ...
        }
    

    相关文章

      网友评论

          本文标题:ListView的使用

          本文链接:https://www.haomeiwen.com/subject/hzlslttx.html