LIstView相关问题总结

什么是ListView

ListView是一个能数据集合以动态滚动的方式展示到用户界面上的view

ListView适配器模式

image

ListView只是一个垂直显示的列表而已,最关心的是把view准确无误的显示到它所在的item上。

ListView和数据是分开的,不直接接触,所以说只能通过adapter这个适配器,把数据加载到ListView上;
adapter是数据源和ListView之间的桥梁,负责为每一个数据制造view并显示在ListView上;
adapter保证了数据和view的分离,这也是mvc的设计模式。
同时由于adapter的接口是统一的,ListView不用关心数据适配方面的问题。
由于adapter是一个接口,可以通过子类去实现自己的逻辑,去完成特定的功能

具体实现

image

image

这段代码简单实现一个MyAdapter,交给ListView;
在MyAdapter中最重要的是两个方法:
一个是getCount返回数据的总数;
一个是getView,为每个数据创建view,用于显示到ListView的item上;

ListView的recycleBin机制

public class ListView extends AbsListView {
...
}

public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
        ViewTreeObserver.OnTouchModeChangeListener,
        RemoteViewsAdapter.RemoteAdapterConnectionCallback {
    class RecycleBin {
        ...

        /**
         * Views that were on screen at the start of layout. This array is populated at the start of
         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
         * Views in mActiveViews represent a contiguous range of Views, with position of the first
         * view store in mFirstActivePosition.
         */
        private View[] mActiveViews = new View[0];

        /**
         * Unsorted views that can be used by the adapter as a convert view.
         */
        private ArrayList<View>[] mScrapViews;

        private ArrayList<View> mCurrentScrap;
        ...
    |
|

ListView 继承自 AbsListView, 而 AbsListView 包含一个内部类 RecycleBin;

RecycleBin 的三个重要变量:

  1. mActiveViews: 存储的是活动的view,表示listview中在屏幕上可见的view,这些view是可以被直接复用的;

  2. mScrapViews: 表示所有废弃类型view的list,当你的listview滑出屏幕的view就变成scrapview,所有的scrapview组成mScrapViews;

  3. mCurrentScrap: 表示当前废弃的item

当item滑出屏幕时,会被回收,不在进行绘制,而被回收的view放到RecycleBin中管理

RecycleBin 中几个重要方法:

    class RecycleBin {
        ...

        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //noinspection unchecked
            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
            for (int i = 0; i < viewTypeCount; i++) {
                scrapViews[i] = new ArrayList<View>();
            }
            mViewTypeCount = viewTypeCount;
            mCurrentScrap = scrapViews[0];
            mScrapViews = scrapViews;
        }


        /**
         * Fill ActiveViews with all of the children of the AbsListView.
         *
         * @param childCount The minimum number of views mActiveViews should hold
         * @param firstActivePosition The position of the first view that will be stored in
         *        mActiveViews
         */
        void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;

            //noinspection MismatchedReadAndWriteOfArray
            final View[] activeViews = mActiveViews;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                // Don't put header or footer views into the scrap heap
                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
                    //        However, we will NOT place them into scrap views.
                    activeViews[i] = child;
                    // Remember the position so that setupChild() doesn't reset state.
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

        /**
         * Get the view corresponding to the specified position. The view will be removed from
         * mActiveViews if it is found.
         *
         * @param position The position to look up in mActiveViews
         * @return The view if it is found, null otherwise
         */
        View getActiveView(int position) {
            int index = position - mFirstActivePosition;
            final View[] activeViews = mActiveViews;
            if (index >=0 && index < activeViews.length) {
                final View match = activeViews[index];
                activeViews[index] = null;
                return match;
            }
            return null;
        }


        /**
         * Puts a view into the list of scrap views.
         * <p>
         * If the list data hasn't changed or the adapter has stable IDs, views
         * with transient state will be preserved for later retrieval.
         *
         * @param scrap The view to add
         * @param position The view's position within its parent
         */
        void addScrapView(View scrap, int position) {
            final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
            if (lp == null) {
                // Can't recycle, but we don't know anything about the view.
                // Ignore it completely.
                return;
            }

            lp.scrappedFromPosition = position;

            // Remove but don't scrap header or footer views, or views that
            // should otherwise not be recycled.
            final int viewType = lp.viewType;
            if (!shouldRecycleViewType(viewType)) {
                // Can't recycle. If it's not a header or footer, which have
                // special handling and should be ignored, then skip the scrap
                // heap and we'll fully detach the view later.
                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    getSkippedScrap().add(scrap);
                }
                return;
            }

            scrap.dispatchStartTemporaryDetach();

            // The the accessibility state of the view may change while temporary
            // detached and we do not allow detached views to fire accessibility
            // events. So we are announcing that the subtree changed giving a chance
            // to clients holding on to a view in this subtree to refresh it.
            notifyViewAccessibilityStateChangedIfNeeded(
                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);

            // Don't scrap views that have transient state.
            final boolean scrapHasTransientState = scrap.hasTransientState();
            if (scrapHasTransientState) {
                if (mAdapter != null && mAdapterHasStableIds) {
                    // If the adapter has stable IDs, we can reuse the view for
                    // the same data.
                    if (mTransientStateViewsById == null) {
                        mTransientStateViewsById = new LongSparseArray<>();
                    }
                    mTransientStateViewsById.put(lp.itemId, scrap);
                } else if (!mDataChanged) {
                    // If the data hasn't changed, we can reuse the views at
                    // their old positions.
                    if (mTransientStateViews == null) {
                        mTransientStateViews = new SparseArray<>();
                    }
                    mTransientStateViews.put(position, scrap);
                } else {
                    // Otherwise, we'll have to remove the view and start over.
                    clearScrapForRebind(scrap);
                    getSkippedScrap().add(scrap);
                }
            } else {
                clearScrapForRebind(scrap);
                if (mViewTypeCount == 1) {
                    mCurrentScrap.add(scrap);
                } else {
                    mScrapViews[viewType].add(scrap);
                }

                if (mRecyclerListener != null) {
                    mRecyclerListener.onMovedToScrapHeap(scrap);
                }
            }
        }
        ...
    |

setViewTypeCount方法:

为listview中的每个类型的数据项建立RecycleBin机制,listview可以绑定多种类型的数据,为每一种类型都建立RecycleBin机制;
在listview中设定不同类型的item,每个item有不同的样式,对每种样式都要设定RecycleBin机制;

fillActiveViews方法:

第一个参数 childCount : 表示存储view的数量;
第二个参数 firstActivePosition : 表示listview中第一个可见元素的position值;
调用这个方法后,会根据参数,将listview当中的指定元素,存储到mActiveViews中;

getActiveView方法:

和 fillActiveViews 方法是对应的,fillActiveViews方法是填充,是获取相应的view;
首先会将传入得position转成 mActiveViews 数组对应的下标;
最重要的是拿到view之后会将 activeViews 数组对应的下标值改为null,下次获取同样位置的view,返回的将是null,也就是说屏幕上显示的 mActiveViews 是不能被重复利用的;

addScrapView方法:

将废弃的view进行缓存的重要方法;
RecycleBin就是调用这个方法,将滑出屏幕的view进行缓存的;

第一个参数 scrap : 表示将要被添加到废弃数组 mScrapViews 中的view;

总结一下 RecycleBIn 回收机制:

image

这张图解释了,为什么Listview当中如果存储了几万行也不会造成OOM的原因。

也就是说只有显示在屏幕中的元素1到元素5是存储在内存中的。

比如当元素0滑出屏幕时,通过addScrapView方法,将元素0缓存起来,
而当元素6将要显示到屏幕上的时候,而这个缓存的item就会通过getActiveView方法获取到元素0的位置,达到复用的效果
元素0滑出屏幕时会被保存在RecycleBin机制中,而当元素6将要进入屏幕显示时,会去复用元素0的item.

ListView的优化

image

convertview重用

adapter中的getView方法,有个参数是convertView:
它的作用就是缓存,只有缓存convertView为null的时候,才去创建相应的view,如果存在缓存,可以调用已有的view
但是在初次显示时,每显示一个item都会创建view,当它移出屏幕的时候convertView就不为null了。
所以convertView是listview性能优化最重要的点;

viewholder

使用ViewHolder可以避免多次的findViewById
所有的view都是树形结构,每次遍历非常耗时,利用viewholder就能减少每次遍历的耗时

三级缓存

比如图片加载的时候用到缓存机制

监听滑动事件

getView方法中少做耗时操作,这是为了保障listview滑动的流畅性

如果一定要做耗时操作可以监听滑动事件,当滑动停止的时候在去加载比如图片等

item布局中尽量避免半透明的元素

因为半透明的绘制比透明更耗时

开启硬件加速

posted @ 2022-07-08 18:22  cfdroid  阅读(62)  评论(0编辑  收藏  举报