• 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布局中尽量避免半透明的元素

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

    开启硬件加速

  • 相关阅读:
    动画:UIViewAnimationOptions类型
    ReactiveCocoa--RACTuple
    RACSignal的一些常用用法
    神奇的RAC宏
    UITableViewStyleGrouped模式下多余间距
    UITableViewStyleGrouped模式下烦人的多余间距
    上传到 App Store 时出错。
    [iOS]详解调整UIButton的title和image的位置
    规范化目录

  • 原文地址:https://www.cnblogs.com/cfdroid/p/16457899.html
Copyright © 2020-2023  润新知