Android_ListView (基本使用 / RecycleBin机制 / 源码解析 / 异步图片错位解决方案)

来源:互联网 发布:饥荒联机网络较差 编辑:程序博客网 时间:2024/06/14 02:12

Android_ListView (基本使用 / RecycleBin机制 / 源码解析 / 异步图片错位解决方案)


本文由 Luzhuo 编写,转发请保留该信息.
原文: http://blog.csdn.net/Rozol/article/details/78161840


  • 把数据用列表的形式,动态滚动的方式,展示给用户.
  • ListView 作为界面展示的容器控件必然会直接或者间接的继承ViewGroup, 现在看看源代码的继承结构

    public class ListView extends AbsListView { }public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback { }public abstract class AdapterView<T extends Adapter> extends ViewGroup { }
  • 现在知道ListView确实是继承ViewGroup的,那么就会重写 onMeasure() onLayout() onDraw() 这三个基本的方法, 大家是否注意到继承ViewGroup的后,onMeasure() onLayout()会多次执行的问题(执行了4次onMeasure(), 2次onLayout()),以下是log.

基本使用

public class MainActivity extends AppCompatActivity {    private ListView listView;    private String[] listImage = Resource.grilImage;    private BitmapUtils bitmapUtils;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initView();        initData();    }    private void initView() {        bitmapUtils = new BitmapUtils(this);        listView = (ListView) findViewById(R.id.listview);    }    private void initData() {        ImageAdapter IAdapter = new ImageAdapter(MainActivity.this, listImage, bitmapUtils);        listView.setAdapter(IAdapter);        listView.setOnScrollListener(new PauseOnScrollListener(bitmapUtils, false, true));    }}

public class ImageAdapter extends BaseAdapter {    private Context context;    private String[] listImage;    private LayoutInflater inflater;    private BitmapUtils bitmapUtils;    public ImageAdapter(Context context, String[] listImage, BitmapUtils bitmapUtils) {        this.context = context;        this.listImage = listImage;        inflater = LayoutInflater.from(context);        this.bitmapUtils = bitmapUtils;    }    @Override    public int getCount() {        return listImage == null ? 0 : listImage.length;    }    @Override    public Object getItem(int position) {        return listImage[position];    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder viewHolder;        if(convertView == null){            convertView = inflater.inflate(R.layout.item_list, null);            viewHolder = new ViewHolder();            viewHolder.imageview = (ImageView)convertView.findViewById(R.id.imageview);            viewHolder.textview = (TextView)convertView.findViewById(R.id.textview);            convertView.setTag(viewHolder);        }else{            viewHolder = (ViewHolder) convertView.getTag();        }        if(listImage.length != 0){            bitmapUtils.display(viewHolder.imageview, listImage[position]);            viewHolder.textview.setText("第"+position+"张图片");        }        return  convertView;    }    class ViewHolder{        ImageView imageview;        TextView textview;    }}

Adapter适配器模式

  • 从上面的使用代码可以看出,要让ListView正常工作,就要设置Adapter,Adapter就是适配器

    public class MyAdapter extends BaseAdapter {    @Override    public int getCount() {        return 0;    }    @Override    public Object getItem(int position) {        return null;    }    @Override    public long getItemId(int position) {        return 0;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        return null;    }}
  • 继承Adapter会被要求必须重写上述4个方法. 数据是不尽相同的, ListView只关心交互和展示的工作,不关心你数据是什么样的,从哪来的. 而Adapter的统一接口就解决的数据适配的问题.

  • 示范图:

recycleBin机制

  • 为了简洁说明ListView是怎么工作的,先讲下 AbsListView 类里的内部类 RecycleBin

    public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback {    final RecycleBin mRecycler = new RecycleBin();    class RecycleBin {        private RecyclerListener mRecyclerListener;        /**         * The position of the first view stored in mActiveViews.         */        private int mFirstActivePosition;        /**         * 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 int mViewTypeCount;        // ↓↓↓        private ArrayList<View> mCurrentScrap;        private ArrayList<View> mSkippedScrap;        private SparseArray<View> mTransientStateViews;        private LongSparseArray<View> mTransientStateViewsById;        // ↓↓↓        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;        }        /**         * @return A view from the ScrapViews collection. These are unordered.         */        // ↓↓↓        View getScrapView(int position) {            final int whichScrap = mAdapter.getItemViewType(position);            if (whichScrap < 0) {                return null;            }            if (mViewTypeCount == 1) {                return retrieveFromScrap(mCurrentScrap, position);            } else if (whichScrap < mScrapViews.length) {                return retrieveFromScrap(mScrapViews[whichScrap], position);            }            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.                    getSkippedScrap().add(scrap);                }            } else {                if (mViewTypeCount == 1) {                    mCurrentScrap.add(scrap);                } else {                    mScrapViews[viewType].add(scrap);                }                if (mRecyclerListener != null) {                    mRecyclerListener.onMovedToScrapHeap(scrap);                }            }        }    }}
    • 以上代码是从源码中拷贝出来,并删掉了一些不重要的方法
    • 成员变量:

      • View[] mActiveViews: 用于存放活动View(也就是在屏幕上展示的View)
      • int mFirstActivePosition: 存放mActiveViews中第一个View的Position(也就是第几个Item)
      • ArrayList<View>[] mScrapViews: 废弃的View,可通过Adapter转为convertView继续使用(看看基本使用Adapter代码,我们就是将这些废弃的view重复使用的)
      • ArrayList<View> mCurrentScrap: ViewTypeCount == 1 的废弃View会被存在这个集合里
      • 方法:
      • public void setViewTypeCount(int viewTypeCount) {}: 该方法会根据传入的类型数量初始化 mScrapViews 和 mCurrentScrap; mScrapViews 存了不同类型View的集合, mCurrentScrap是mScrapViews的第一个集合; 看来ListView是可以传入多个类型的View的
      • void fillActiveViews(int childCount, int firstActivePosition) {}: 主要是将mActiveViews填满子View (保存屏幕上展示的View)
      • View getActiveView(int position) {}: 根据position获取mActiveViews里的View
      • View getScrapView(int position) {}: 源码主要调用了retrieveFromScrap(mCurrentScrap, position)方法,现在看看这个方法是干吗的?

        private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {    final int size = scrapViews.size();    if (size > 0) {        // See if we still have a view for this position or ID.        for (int i = 0; i < size; i++) {            final View view = scrapViews.get(i);            final AbsListView.LayoutParams params =                    (AbsListView.LayoutParams) view.getLayoutParams();            if (mAdapterHasStableIds) {                final long id = mAdapter.getItemId(position);                if (id == params.itemId) {                    return scrapViews.remove(i);                }            } else if (params.scrappedFromPosition == position) {                final View scrap = scrapViews.remove(i);                clearAccessibilityFromScrap(scrap);                return scrap;            }        }        final View scrap = scrapViews.remove(size - 1);        clearAccessibilityFromScrap(scrap);        return scrap;    } else {        return null;    }}
        • 看来getScrapView(int position)是获取废弃的View, 如果能获取到就返回View,获取不到就返回null
    • void addScrapView(View scrap, int position) {}: 把废弃的View添加到mCurrentScrap里, 把具有过渡效果的废弃View添加到mTransientStateViews里(带有过渡效果的View这里不做讲解)
    • 可见recycleBin主要工作就是填满和获取展示View,添加和获取缓存View.

ListView的执行逻辑源码

ListView的初始化逻辑

  • ListView作为View容器控件,那么我们就从 onMeasure() onLayout() 这2个基本的被重写方法开始研究
  • onMeasure() 主要是测量ListView的大小; onLayout()用于确定子View的布局, 这才是核心, 该方法并没有在ListView中实现, 而是在抽象父类AbsListView中实现.

    protected void onLayout(boolean changed, int l, int t, int r, int b) {    super.onLayout(changed, l, t, r, b);    mInLayout = true;    final int childCount = getChildCount();    // ↓↓↓ 1. 如果change == true, ListView的大小和位置发生变化     if (changed) {        for (int i = 0; i < childCount; i++) {            // ↓↓↓ 2. 那么就把所有子布局强制重绘            getChildAt(i).forceLayout();        }        mRecycler.markChildrenDirty();    }    // ↓↓↓ 3. 调用子类ListView的layoutChildren()方法    layoutChildren();    mInLayout = false;    mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;    // TODO: Move somewhere sane. This doesn't belong in onLayout().    if (mFastScroll != null) {        mFastScroll.onItemCountChanged(getChildCount(), mItemCount);    }}
    • 接着看看layoutChildren()做了什么

      protected void layoutChildren() {    final boolean blockLayoutRequests = mBlockLayoutRequests;    if (blockLayoutRequests) {        return;    }    mBlockLayoutRequests = true;    try {        super.layoutChildren();        invalidate();        if (mAdapter == null) {            resetList();            invokeOnItemScrollListener();            return;        }        final int childrenTop = mListPadding.top;        final int childrenBottom = mBottom - mTop - mListPadding.bottom;        // ↓↓↓ 1. ListView中还未填充任务子View, 得到结果为0        final int childCount = getChildCount();        int index = 0;        int delta = 0;        View sel;        View oldSel = null;        View oldFirst = null;        View newSel = null;        // Remember stuff we will need down below        switch (mLayoutMode) {        case LAYOUT_SET_SELECTION:            index = mNextSelectedPosition - mFirstPosition;            if (index >= 0 && index < childCount) {                newSel = getChildAt(index);            }            break;        case LAYOUT_FORCE_TOP:        case LAYOUT_FORCE_BOTTOM:        case LAYOUT_SPECIFIC:        case LAYOUT_SYNC:            break;        case LAYOUT_MOVE_SELECTION:        default:            // Remember the previously selected view            index = mSelectedPosition - mFirstPosition;            if (index >= 0 && index < childCount) {                oldSel = getChildAt(index);            }            // Remember the previous first child            oldFirst = getChildAt(0);            if (mNextSelectedPosition >= 0) {                delta = mNextSelectedPosition - mSelectedPosition;            }            // Caution: newSel might be null            newSel = getChildAt(index + delta);        }        boolean dataChanged = mDataChanged;        if (dataChanged) {            handleDataChanged();        }        // Handle the empty set by removing all views that are visible        // and calling it a day        if (mItemCount == 0) {            resetList();            invokeOnItemScrollListener();            return;        } else if (mItemCount != mAdapter.getCount()) {            throw new IllegalStateException("The content of the adapter has changed but "                    + "ListView did not receive a notification. Make sure the content of "                    + "your adapter is not modified from a background thread, but only from "                    + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "                    + "when its content changes. [in ListView(" + getId() + ", " + getClass()                    + ") with Adapter(" + mAdapter.getClass() + ")]");        }        setSelectedPositionInt(mNextSelectedPosition);        AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;        View accessibilityFocusLayoutRestoreView = null;        int accessibilityFocusPosition = INVALID_POSITION;        // Remember which child, if any, had accessibility focus. This must        // occur before recycling any views, since that will clear        // accessibility focus.        final ViewRootImpl viewRootImpl = getViewRootImpl();        if (viewRootImpl != null) {            final View focusHost = viewRootImpl.getAccessibilityFocusedHost();            if (focusHost != null) {                final View focusChild = getAccessibilityFocusedChild(focusHost);                if (focusChild != null) {                    if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)                            || focusChild.hasTransientState() || mAdapterHasStableIds) {                        // The views won't be changing, so try to maintain                        // focus on the current host and virtual view.                        accessibilityFocusLayoutRestoreView = focusHost;                        accessibilityFocusLayoutRestoreNode = viewRootImpl                                .getAccessibilityFocusedVirtualView();                    }                    // If all else fails, maintain focus at the same                    // position.                    accessibilityFocusPosition = getPositionForView(focusChild);                }            }        }        View focusLayoutRestoreDirectChild = null;        View focusLayoutRestoreView = null;        // Take focus back to us temporarily to avoid the eventual call to        // clear focus when removing the focused child below from messing        // things up when ViewAncestor assigns focus back to someone else.        final View focusedChild = getFocusedChild();        if (focusedChild != null) {            // TODO: in some cases focusedChild.getParent() == null            // We can remember the focused view to restore after re-layout            // if the data hasn't changed, or if the focused position is a            // header or footer.            if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)                    || focusedChild.hasTransientState() || mAdapterHasStableIds) {                focusLayoutRestoreDirectChild = focusedChild;                // Remember the specific view that had focus.                focusLayoutRestoreView = findFocus();                if (focusLayoutRestoreView != null) {                    // Tell it we are going to mess with it.                    focusLayoutRestoreView.dispatchStartTemporaryDetach();                }            }            requestFocus();        }        // Pull all children into the RecycleBin.        // These views will be reused if possible        final int firstPosition = mFirstPosition;        final RecycleBin recycleBin = mRecycler;        // ↓↓↓ 2. 一旦数据源有变化, dataChanged == true        if (dataChanged) {            for (int i = 0; i < childCount; i++) {                recycleBin.addScrapView(getChildAt(i), firstPosition+i);            }        } else {            // ↓↓↓ 2.2 如果数据源, 执行该方法, 调用RecycleBin的fillActiveViews()缓存展示的所有view, 但是现在并没有展示的View            recycleBin.fillActiveViews(childCount, firstPosition);        }        // Clear out old views        // ↓↓↓ 2.3 从父容器中清除所有view, 现在没有可清理的view        detachAllViewsFromParent();        recycleBin.removeSkippedScrap();        // ↓↓↓ 3. 判断布局模式, 默认布局模式为LAYOUT_NORMAL        switch (mLayoutMode) {        case LAYOUT_SET_SELECTION:            if (newSel != null) {                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);            } else {                sel = fillFromMiddle(childrenTop, childrenBottom);            }            break;        case LAYOUT_SYNC:            sel = fillSpecific(mSyncPosition, mSpecificTop);            break;        case LAYOUT_FORCE_BOTTOM:            sel = fillUp(mItemCount - 1, childrenBottom);            adjustViewsUpOrDown();            break;        case LAYOUT_FORCE_TOP:            mFirstPosition = 0;            sel = fillFromTop(childrenTop);            adjustViewsUpOrDown();            break;        case LAYOUT_SPECIFIC:            final int selectedPosition = reconcileSelectedPosition();            sel = fillSpecific(selectedPosition, mSpecificTop);            /**             * When ListView is resized, FocusSelector requests an async selection for the             * previously focused item to make sure it is still visible. If the item is not             * selectable, it won't regain focus so instead we call FocusSelector             * to directly request focus on the view after it is visible.             */            if (sel == null && mFocusSelector != null) {                final Runnable focusRunnable = mFocusSelector                        .setupFocusIfValid(selectedPosition);                if (focusRunnable != null) {                    post(focusRunnable);                }            }            break;        case LAYOUT_MOVE_SELECTION:            sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);            break;        default:            // ↓↓↓ 3.1 于是会执行该部分代码            if (childCount == 0) {                if (!mStackFromBottom) {                    final int position = lookForSelectablePosition(0, true);                    setSelectedPositionInt(position);                    // ↓↓↓ 3.2 执行方法                    sel = fillFromTop(childrenTop);                } else {                    final int position = lookForSelectablePosition(mItemCount - 1, false);                    setSelectedPositionInt(position);                    sel = fillUp(mItemCount - 1, childrenBottom);                }            } else {                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {                    sel = fillSpecific(mSelectedPosition,                            oldSel == null ? childrenTop : oldSel.getTop());                } else if (mFirstPosition < mItemCount) {                    sel = fillSpecific(mFirstPosition,                            oldFirst == null ? childrenTop : oldFirst.getTop());                } else {                    sel = fillSpecific(0, childrenTop);                }            }            break;        }        // Flush any cached views that did not get reused above        recycleBin.scrapActiveViews();        // remove any header/footer that has been temp detached and not re-attached        removeUnusedFixedViews(mHeaderViewInfos);        removeUnusedFixedViews(mFooterViewInfos);        if (sel != null) {            // The current selected item should get focus if items are            // focusable.            if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {                final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&                        focusLayoutRestoreView != null &&                        focusLayoutRestoreView.requestFocus()) || sel.requestFocus();                if (!focusWasTaken) {                    // Selected item didn't take focus, but we still want to                    // make sure something else outside of the selected view                    // has focus.                    final View focused = getFocusedChild();                    if (focused != null) {                        focused.clearFocus();                    }                    positionSelector(INVALID_POSITION, sel);                } else {                    sel.setSelected(false);                    mSelectorRect.setEmpty();                }            } else {                positionSelector(INVALID_POSITION, sel);            }            mSelectedTop = sel.getTop();        } else {            final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP                    || mTouchMode == TOUCH_MODE_DONE_WAITING;            if (inTouchMode) {                // If the user's finger is down, select the motion position.                final View child = getChildAt(mMotionPosition - mFirstPosition);                if (child != null) {                    positionSelector(mMotionPosition, child);                }            } else if (mSelectorPosition != INVALID_POSITION) {                // If we had previously positioned the selector somewhere,                // put it back there. It might not match up with the data,                // but it's transitioning out so it's not a big deal.                final View child = getChildAt(mSelectorPosition - mFirstPosition);                if (child != null) {                    positionSelector(mSelectorPosition, child);                }            } else {                // Otherwise, clear selection.                mSelectedTop = 0;                mSelectorRect.setEmpty();            }            // Even if there is not selected position, we may need to            // restore focus (i.e. something focusable in touch mode).            if (hasFocus() && focusLayoutRestoreView != null) {                focusLayoutRestoreView.requestFocus();            }        }        // Attempt to restore accessibility focus, if necessary.        if (viewRootImpl != null) {            final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();            if (newAccessibilityFocusedView == null) {                if (accessibilityFocusLayoutRestoreView != null                        && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {                    final AccessibilityNodeProvider provider =                            accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();                    if (accessibilityFocusLayoutRestoreNode != null && provider != null) {                        final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(                                accessibilityFocusLayoutRestoreNode.getSourceNodeId());                        provider.performAction(virtualViewId,                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);                    } else {                        accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();                    }                } else if (accessibilityFocusPosition != INVALID_POSITION) {                    // Bound the position within the visible children.                    final int position = MathUtils.constrain(                            accessibilityFocusPosition - mFirstPosition, 0,                            getChildCount() - 1);                    final View restoreView = getChildAt(position);                    if (restoreView != null) {                        restoreView.requestAccessibilityFocus();                    }                }            }        }        // Tell focus view we are done mucking with it, if it is still in        // our view hierarchy.        if (focusLayoutRestoreView != null                && focusLayoutRestoreView.getWindowToken() != null) {            focusLayoutRestoreView.dispatchFinishTemporaryDetach();        }        mLayoutMode = LAYOUT_NORMAL;        mDataChanged = false;        if (mPositionScrollAfterLayout != null) {            post(mPositionScrollAfterLayout);            mPositionScrollAfterLayout = null;        }        mNeedSync = false;        setNextSelectedPositionInt(mSelectedPosition);        updateScrollIndicators();        if (mItemCount > 0) {            checkSelectionChanged();        }        invokeOnItemScrollListener();    } finally {        if (mFocusSelector != null) {            mFocusSelector.onLayoutComplete();        }        if (!blockLayoutRequests) {            mBlockLayoutRequests = false;        }    }}private View fillFromTop(int nextTop) {    mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);    mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);    if (mFirstPosition < 0) {        mFirstPosition = 0;    }    return fillDown(mFirstPosition, nextTop);}private View fillDown(int pos, int nextTop) {    View selectedView = null;    int end = (mBottom - mTop);    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {        end -= mListPadding.bottom;    }    // ↓↓↓ 3.2 循环计算子布局位置, 然后调用makeAndAddView()    while (nextTop < end && pos < mItemCount) {        // is this the selected item?        boolean selected = pos == mSelectedPosition;        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);        nextTop = child.getBottom() + mDividerHeight;        if (selected) {            selectedView = child;        }        pos++;    }    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);    return selectedView;}private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,        boolean selected) {    View child;    if (!mDataChanged) {        // Try to use an existing view for this position        // ↓↓↓ 3.3 从RecycleBin里获取一个缓存的View, 但是缓存为空, 返回null        child = mRecycler.getActiveView(position);        if (child != null) {            // Found it -- we're using an existing child            // This just needs to be positioned            setupChild(child, position, y, flow, childrenLeft, selected, true);            return child;        }    }    // Make a new view for this position, or convert an unused view if possible    // ↓↓↓ 3.4 然而从RecycleBin里获取缓存的View失败, 则试图通过obtainView()方法获取一个View    child = obtainView(position, mIsScrap);    // This needs to be positioned and measured    // ↓↓↓ 3.7 将适配器获得的View, 交给该方法    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);    return child;}View obtainView(int position, boolean[] isScrap) {    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");    isScrap[0] = false;    // ...    // ↓↓↓ 3.5 从RecycleBin里获取一个缓存的废弃的View, 现在RecycleBin里没有缓存任何View, 所以返回null    final View scrapView = mRecycler.getScrapView(position);    // ↓↓↓ 3.6 并且从适配器里获取一个View, 这个只要我们给了数据, 一定能获取到    final View child = mAdapter.getView(position, scrapView, this);    if (scrapView != null) {        if (child != scrapView) {            // Failed to re-bind the data, return scrap to the heap.            mRecycler.addScrapView(scrapView, position);        } else {            if (child.isTemporarilyDetached()) {                isScrap[0] = true;                // Finish the temporary detach started in addScrapView().                child.dispatchFinishTemporaryDetach();            } else {                // we set isScrap to "true" only if the view is temporarily detached.                // if the view is fully detached, it is as good as a view created by the                // adapter                isScrap[0] = false;            }        }    }    if (mCacheColorHint != 0) {        child.setDrawingCacheBackgroundColor(mCacheColorHint);    }    if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {        child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);    }    setItemViewLayoutParams(child, position);    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;}private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) {    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");    final boolean isSelected = selected && shouldShowSelector();    final boolean updateChildSelected = isSelected != child.isSelected();    final int mode = mTouchMode;    final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL && mMotionPosition == position;    final boolean updateChildPressed = isPressed != child.isPressed();    final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();    // Respect layout params that are already in the view. Otherwise make some up...    // noinspection unchecked    AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();    if (p == null) {        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();    }    p.viewType = mAdapter.getItemViewType(position);    p.isEnabled = mAdapter.isEnabled(position);    if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {        attachViewToParent(child, flowDown ? -1 : 0, p);    } else {        p.forceAdd = false;        if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {            p.recycledHeaderFooter = true;        }        // ↓↓↓ 3.8 把获得的View添加到ListView中, 这样就完成了一次onLayout的加载        addViewInLayout(child, flowDown ? -1 : 0, p, true);    }    if (updateChildSelected) {        child.setSelected(isSelected);    }    if (updateChildPressed) {        child.setPressed(isPressed);    }    if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {        if (child instanceof Checkable) {            ((Checkable) child).setChecked(mCheckStates.get(position));        } else if (getContext().getApplicationInfo().targetSdkVersion                >= android.os.Build.VERSION_CODES.HONEYCOMB) {            child.setActivated(mCheckStates.get(position));        }    }    if (needToMeasure) {        final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,                mListPadding.left + mListPadding.right, p.width);        final int lpHeight = p.height;        final int childHeightSpec;        if (lpHeight > 0) {            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);        } else {            childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),                    MeasureSpec.UNSPECIFIED);        }        child.measure(childWidthSpec, childHeightSpec);    } else {        cleanupLayoutState(child);    }    final int w = child.getMeasuredWidth();    final int h = child.getMeasuredHeight();    final int childTop = flowDown ? y : y - h;    if (needToMeasure) {        final int childRight = childrenLeft + w;        final int childBottom = childTop + h;        child.layout(childrenLeft, childTop, childRight, childBottom);    } else {        child.offsetLeftAndRight(childrenLeft - child.getLeft());        child.offsetTopAndBottom(childTop - child.getTop());    }    if (mCachingStarted && !child.isDrawingCacheEnabled()) {        child.setDrawingCacheEnabled(true);    }    if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)            != position) {        child.jumpDrawablesToCurrentState();    }    Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
    • 第一次onLayout, 由于第一次, 所以屏幕上和RecycleBin的缓存里并没有View, 只有通过适配器获取View, 直至将屏幕填满.

    • 接下来我们来看看第二次onLayout, 与第一次的差别是, 这一次屏幕上已经有View了

      protected void onLayout(boolean changed, int l, int t, int r, int b) {    super.onLayout(changed, l, t, r, b);    mInLayout = true;    final int childCount = getChildCount();    if (changed) {        for (int i = 0; i < childCount; i++) {            getChildAt(i).forceLayout();        }        mRecycler.markChildrenDirty();    }    // ↓↓↓ 1.第二次的onLayout执行还是从这里开始    layoutChildren();    mInLayout = false;    mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;    // TODO: Move somewhere sane. This doesn't belong in onLayout().    if (mFastScroll != null) {        mFastScroll.onItemCountChanged(getChildCount(), mItemCount);    }}protected void layoutChildren() {    final boolean blockLayoutRequests = mBlockLayoutRequests;    if (blockLayoutRequests) {        return;    }    mBlockLayoutRequests = true;    try {        super.layoutChildren();        invalidate();        if (mAdapter == null) {            resetList();            invokeOnItemScrollListener();            return;        }        final int childrenTop = mListPadding.top;        final int childrenBottom = mBottom - mTop - mListPadding.bottom;        // 2. 获取子View的数量, 由于屏幕已经填充了View, 所以这里返回的数字 > 0        final int childCount = getChildCount();        int index = 0;        int delta = 0;        View sel;        View oldSel = null;        View oldFirst = null;        View newSel = null;        // Remember stuff we will need down below        switch (mLayoutMode) {        case LAYOUT_SET_SELECTION:            index = mNextSelectedPosition - mFirstPosition;            if (index >= 0 && index < childCount) {                newSel = getChildAt(index);            }            break;        case LAYOUT_FORCE_TOP:        case LAYOUT_FORCE_BOTTOM:        case LAYOUT_SPECIFIC:        case LAYOUT_SYNC:            break;        case LAYOUT_MOVE_SELECTION:        default:            // Remember the previously selected view            index = mSelectedPosition - mFirstPosition;            if (index >= 0 && index < childCount) {                oldSel = getChildAt(index);            }            // Remember the previous first child            oldFirst = getChildAt(0);            if (mNextSelectedPosition >= 0) {                delta = mNextSelectedPosition - mSelectedPosition;            }            // Caution: newSel might be null            newSel = getChildAt(index + delta);        }        boolean dataChanged = mDataChanged;        if (dataChanged) {            handleDataChanged();        }        // Handle the empty set by removing all views that are visible        // and calling it a day        if (mItemCount == 0) {            resetList();            invokeOnItemScrollListener();            return;        } else if (mItemCount != mAdapter.getCount()) {            throw new IllegalStateException("The content of the adapter has changed but "                    + "ListView did not receive a notification. Make sure the content of "                    + "your adapter is not modified from a background thread, but only from "                    + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "                    + "when its content changes. [in ListView(" + getId() + ", " + getClass()                    + ") with Adapter(" + mAdapter.getClass() + ")]");        }        setSelectedPositionInt(mNextSelectedPosition);        AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;        View accessibilityFocusLayoutRestoreView = null;        int accessibilityFocusPosition = INVALID_POSITION;        // Remember which child, if any, had accessibility focus. This must        // occur before recycling any views, since that will clear        // accessibility focus.        final ViewRootImpl viewRootImpl = getViewRootImpl();        if (viewRootImpl != null) {            final View focusHost = viewRootImpl.getAccessibilityFocusedHost();            if (focusHost != null) {                final View focusChild = getAccessibilityFocusedChild(focusHost);                if (focusChild != null) {                    if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)                            || focusChild.hasTransientState() || mAdapterHasStableIds) {                        // The views won't be changing, so try to maintain                        // focus on the current host and virtual view.                        accessibilityFocusLayoutRestoreView = focusHost;                        accessibilityFocusLayoutRestoreNode = viewRootImpl                                .getAccessibilityFocusedVirtualView();                    }                    // If all else fails, maintain focus at the same                    // position.                    accessibilityFocusPosition = getPositionForView(focusChild);                }            }        }        View focusLayoutRestoreDirectChild = null;        View focusLayoutRestoreView = null;        // Take focus back to us temporarily to avoid the eventual call to        // clear focus when removing the focused child below from messing        // things up when ViewAncestor assigns focus back to someone else.        final View focusedChild = getFocusedChild();        if (focusedChild != null) {            // TODO: in some cases focusedChild.getParent() == null            // We can remember the focused view to restore after re-layout            // if the data hasn't changed, or if the focused position is a            // header or footer.            if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)                    || focusedChild.hasTransientState() || mAdapterHasStableIds) {                focusLayoutRestoreDirectChild = focusedChild;                // Remember the specific view that had focus.                focusLayoutRestoreView = findFocus();                if (focusLayoutRestoreView != null) {                    // Tell it we are going to mess with it.                    focusLayoutRestoreView.dispatchStartTemporaryDetach();                }            }            requestFocus();        }        // Pull all children into the RecycleBin.        // These views will be reused if possible        final int firstPosition = mFirstPosition;        final RecycleBin recycleBin = mRecycler;        if (dataChanged) {            for (int i = 0; i < childCount; i++) {                recycleBin.addScrapView(getChildAt(i), firstPosition+i);            }        } else {            // ↓↓↓ 3. 将屏幕上的View全部添加到RecycleBin里            recycleBin.fillActiveViews(childCount, firstPosition);        }        // Clear out old views        // ↓↓↓ 4. 从父容器中清除所有的view        detachAllViewsFromParent();        recycleBin.removeSkippedScrap();        switch (mLayoutMode) {        case LAYOUT_SET_SELECTION:            if (newSel != null) {                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);            } else {                sel = fillFromMiddle(childrenTop, childrenBottom);            }            break;        case LAYOUT_SYNC:            sel = fillSpecific(mSyncPosition, mSpecificTop);            break;        case LAYOUT_FORCE_BOTTOM:            sel = fillUp(mItemCount - 1, childrenBottom);            adjustViewsUpOrDown();            break;        case LAYOUT_FORCE_TOP:            mFirstPosition = 0;            sel = fillFromTop(childrenTop);            adjustViewsUpOrDown();            break;        case LAYOUT_SPECIFIC:            final int selectedPosition = reconcileSelectedPosition();            sel = fillSpecific(selectedPosition, mSpecificTop);            /**             * When ListView is resized, FocusSelector requests an async selection for the             * previously focused item to make sure it is still visible. If the item is not             * selectable, it won't regain focus so instead we call FocusSelector             * to directly request focus on the view after it is visible.             */            if (sel == null && mFocusSelector != null) {                final Runnable focusRunnable = mFocusSelector                        .setupFocusIfValid(selectedPosition);                if (focusRunnable != null) {                    post(focusRunnable);                }            }            break;        case LAYOUT_MOVE_SELECTION:            sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);            break;        default:            // ↓↓↓ 5. 这里的childCount > 0            if (childCount == 0) {                if (!mStackFromBottom) {                    final int position = lookForSelectablePosition(0, true);                    setSelectedPositionInt(position);                    sel = fillFromTop(childrenTop);                } else {                    final int position = lookForSelectablePosition(mItemCount - 1, false);                    setSelectedPositionInt(position);                    sel = fillUp(mItemCount - 1, childrenBottom);                }            } else {                // ↓↓↓ 6. 所以执行以下判断代码                // ↓↓↓ 6.1 由于是新加载的, 所以不会有被选择的条目, 所以 mSelectedPosition = -1, 改判断不成立                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {                    sel = fillSpecific(mSelectedPosition,                            oldSel == null ? childrenTop : oldSel.getTop());                // ↓↓↓ 6.2 所以这 mFirstPosition = 0, mItemCount >= 0, 判断成立                } else if (mFirstPosition < mItemCount) {                    // ↓↓↓ 7. 可见该方法会去获取缓存View,然后优先填充指定位置的View, 然后填满其他位置的View, 第二次onLayout结束                    sel = fillSpecific(mFirstPosition,                            oldFirst == null ? childrenTop : oldFirst.getTop());                } else {                    sel = fillSpecific(0, childrenTop);                }            }            break;        }        // Flush any cached views that did not get reused above        recycleBin.scrapActiveViews();        // remove any header/footer that has been temp detached and not re-attached        removeUnusedFixedViews(mHeaderViewInfos);        removeUnusedFixedViews(mFooterViewInfos);        if (sel != null) {            // The current selected item should get focus if items are            // focusable.            if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {                final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&                        focusLayoutRestoreView != null &&                        focusLayoutRestoreView.requestFocus()) || sel.requestFocus();                if (!focusWasTaken) {                    // Selected item didn't take focus, but we still want to                    // make sure something else outside of the selected view                    // has focus.                    final View focused = getFocusedChild();                    if (focused != null) {                        focused.clearFocus();                    }                    positionSelector(INVALID_POSITION, sel);                } else {                    sel.setSelected(false);                    mSelectorRect.setEmpty();                }            } else {                positionSelector(INVALID_POSITION, sel);            }            mSelectedTop = sel.getTop();        } else {            final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP                    || mTouchMode == TOUCH_MODE_DONE_WAITING;            if (inTouchMode) {                // If the user's finger is down, select the motion position.                final View child = getChildAt(mMotionPosition - mFirstPosition);                if (child != null) {                    positionSelector(mMotionPosition, child);                }            } else if (mSelectorPosition != INVALID_POSITION) {                // If we had previously positioned the selector somewhere,                // put it back there. It might not match up with the data,                // but it's transitioning out so it's not a big deal.                final View child = getChildAt(mSelectorPosition - mFirstPosition);                if (child != null) {                    positionSelector(mSelectorPosition, child);                }            } else {                // Otherwise, clear selection.                mSelectedTop = 0;                mSelectorRect.setEmpty();            }            // Even if there is not selected position, we may need to            // restore focus (i.e. something focusable in touch mode).            if (hasFocus() && focusLayoutRestoreView != null) {                focusLayoutRestoreView.requestFocus();            }        }        // Attempt to restore accessibility focus, if necessary.        if (viewRootImpl != null) {            final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();            if (newAccessibilityFocusedView == null) {                if (accessibilityFocusLayoutRestoreView != null                        && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {                    final AccessibilityNodeProvider provider =                            accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();                    if (accessibilityFocusLayoutRestoreNode != null && provider != null) {                        final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(                                accessibilityFocusLayoutRestoreNode.getSourceNodeId());                        provider.performAction(virtualViewId,                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);                    } else {                        accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();                    }                } else if (accessibilityFocusPosition != INVALID_POSITION) {                    // Bound the position within the visible children.                    final int position = MathUtils.constrain(                            accessibilityFocusPosition - mFirstPosition, 0,                            getChildCount() - 1);                    final View restoreView = getChildAt(position);                    if (restoreView != null) {                        restoreView.requestAccessibilityFocus();                    }                }            }        }        // Tell focus view we are done mucking with it, if it is still in        // our view hierarchy.        if (focusLayoutRestoreView != null                && focusLayoutRestoreView.getWindowToken() != null) {            focusLayoutRestoreView.dispatchFinishTemporaryDetach();        }        mLayoutMode = LAYOUT_NORMAL;        mDataChanged = false;        if (mPositionScrollAfterLayout != null) {            post(mPositionScrollAfterLayout);            mPositionScrollAfterLayout = null;        }        mNeedSync = false;        setNextSelectedPositionInt(mSelectedPosition);        updateScrollIndicators();        if (mItemCount > 0) {            checkSelectionChanged();        }        invokeOnItemScrollListener();    } finally {        if (mFocusSelector != null) {            mFocusSelector.onLayoutComplete();        }        if (!blockLayoutRequests) {            mBlockLayoutRequests = false;        }    }}private View fillSpecific(int position, int top) {    boolean tempIsSelected = position == mSelectedPosition;    // ↓↓↓ 6.3 获取缓存View并显示    View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);    // Possibly changed again in fillUp if we add rows above this one.    mFirstPosition = position;    View above;    View below;    final int dividerHeight = mDividerHeight;    if (!mStackFromBottom) {        // ↓↓↓ 6.8 向上填满view        above = fillUp(position - 1, temp.getTop() - dividerHeight);        // This will correct for the top of the first view not touching the top of the list        adjustViewsUpOrDown();        // ↓↓↓ 6.9 向下填满view        below = fillDown(position + 1, temp.getBottom() + dividerHeight);        int childCount = getChildCount();        if (childCount > 0) {            correctTooHigh(childCount);        }    } else {        below = fillDown(position + 1, temp.getBottom() + dividerHeight);        // This will correct for the bottom of the last view not touching the bottom of the list        adjustViewsUpOrDown();        above = fillUp(position - 1, temp.getTop() - dividerHeight);        int childCount = getChildCount();        if (childCount > 0) {             correctTooLow(childCount);        }    }    if (tempIsSelected) {        return temp;    } else if (above != null) {        return above;    } else {        return below;    }}private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) {    View child;    if (!mDataChanged) {        // Try to use an existing view for this position        // ↓↓↓ 6.4 从RecycleBin里获取缓存的View        child = mRecycler.getActiveView(position);        if (child != null) {            // Found it -- we're using an existing child            // This just needs to be positioned            // ↓↓↓ 6.5 获取到view后执行该方法            setupChild(child, position, y, flow, childrenLeft, selected, true);            return child;        }    }    // Make a new view for this position, or convert an unused view if possible    child = obtainView(position, mIsScrap);    // This needs to be positioned and measured    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);    return child;}private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) {    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");    final boolean isSelected = selected && shouldShowSelector();    final boolean updateChildSelected = isSelected != child.isSelected();    final int mode = mTouchMode;    final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&            mMotionPosition == position;    final boolean updateChildPressed = isPressed != child.isPressed();    final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();    // Respect layout params that are already in the view. Otherwise make some up...    // noinspection unchecked    AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();    if (p == null) {        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();    }    p.viewType = mAdapter.getItemViewType(position);    p.isEnabled = mAdapter.isEnabled(position);    // ↓↓↓ 6.6 recycled = true 这是参数传进来的, 第一次onLayout时p.forceAdd被标记为false. (p.forceAdd = false;)    if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter            && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {        // ↓↓↓ 6.7 因此执行这句, 让所有View都处于attach状态, 即显示        attachViewToParent(child, flowDown ? -1 : 0, p);    } else {        p.forceAdd = false;        if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {            p.recycledHeaderFooter = true;        }        addViewInLayout(child, flowDown ? -1 : 0, p, true);    }    if (updateChildSelected) {        child.setSelected(isSelected);    }    if (updateChildPressed) {        child.setPressed(isPressed);    }    if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {        if (child instanceof Checkable) {            ((Checkable) child).setChecked(mCheckStates.get(position));        } else if (getContext().getApplicationInfo().targetSdkVersion                >= android.os.Build.VERSION_CODES.HONEYCOMB) {            child.setActivated(mCheckStates.get(position));        }    }    if (needToMeasure) {        final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,                mListPadding.left + mListPadding.right, p.width);        final int lpHeight = p.height;        final int childHeightSpec;        if (lpHeight > 0) {            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);        } else {            childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),                    MeasureSpec.UNSPECIFIED);        }        child.measure(childWidthSpec, childHeightSpec);    } else {        cleanupLayoutState(child);    }    final int w = child.getMeasuredWidth();    final int h = child.getMeasuredHeight();    final int childTop = flowDown ? y : y - h;    if (needToMeasure) {        final int childRight = childrenLeft + w;        final int childBottom = childTop + h;        child.layout(childrenLeft, childTop, childRight, childBottom);    } else {        child.offsetLeftAndRight(childrenLeft - child.getLeft());        child.offsetTopAndBottom(childTop - child.getTop());    }    if (mCachingStarted && !child.isDrawingCacheEnabled()) {        child.setDrawingCacheEnabled(true);    }    if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)            != position) {        child.jumpDrawablesToCurrentState();    }    Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
      • 第二次onLayout主要做的是: 将屏幕上的View全部缓存到RecycleBin里, 然后将他们从父容器中清除(detach);
      • 然后从RecycleBin里获取缓存的View, 并将他们添加到父容器(attach), 然后依次向上, 向下填充满屏幕
      • 另外 attachViewToParent 和 detachAllViewsFromParent 是组合使用的
  • ListView初始化过程图

ListView滑动加载逻辑

  • 两次的onLayout只是初始化了第一屏的view, 当我们手指上下滑的时候, ListView会重复利用滑出屏幕的条目, 这才是ListView的神奇之处. 想要了解滑动事件是如何处理的, 那么就要从 onTouchEvent() 入手.

    public boolean onTouchEvent(MotionEvent ev) {    if (!isEnabled()) {        // A disabled view that is clickable still consumes the touch        // events, it just doesn't respond to them.        return isClickable() || isLongClickable();    }    if (mPositionScroller != null) {        mPositionScroller.stop();    }    if (mIsDetaching || !isAttachedToWindow()) {        // Something isn't right.        // Since we rely on being attached to get data set change notifications,        // don't risk doing anything where we might try to resync and find things        // in a bogus state.        return false;    }    startNestedScroll(SCROLL_AXIS_VERTICAL);    if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {        return true;    }    initVelocityTrackerIfNotExists();    final MotionEvent vtev = MotionEvent.obtain(ev);    final int actionMasked = ev.getActionMasked();    if (actionMasked == MotionEvent.ACTION_DOWN) {        mNestedYOffset = 0;    }    vtev.offsetLocation(0, mNestedYOffset);    switch (actionMasked) {        case MotionEvent.ACTION_DOWN: {            onTouchDown(ev);            break;        }        // ↓↓↓ 1. 各种的触摸事件, 我们只需关心移动事件Move即可        case MotionEvent.ACTION_MOVE: {            onTouchMove(ev, vtev);            break;        }        case MotionEvent.ACTION_UP: {            onTouchUp(ev);            break;        }        case MotionEvent.ACTION_CANCEL: {            onTouchCancel();            break;        }        case MotionEvent.ACTION_POINTER_UP: {            onSecondaryPointerUp(ev);            final int x = mMotionX;            final int y = mMotionY;            final int motionPosition = pointToPosition(x, y);            if (motionPosition >= 0) {                // Remember where the motion event started                final View child = getChildAt(motionPosition - mFirstPosition);                mMotionViewOriginalTop = child.getTop();                mMotionPosition = motionPosition;            }            mLastY = y;            break;        }        case MotionEvent.ACTION_POINTER_DOWN: {            // New pointers take over dragging duties            final int index = ev.getActionIndex();            final int id = ev.getPointerId(index);            final int x = (int) ev.getX(index);            final int y = (int) ev.getY(index);            mMotionCorrection = 0;            mActivePointerId = id;            mMotionX = x;            mMotionY = y;            final int motionPosition = pointToPosition(x, y);            if (motionPosition >= 0) {                // Remember where the motion event started                final View child = getChildAt(motionPosition - mFirstPosition);                mMotionViewOriginalTop = child.getTop();                mMotionPosition = motionPosition;            }            mLastY = y;            break;        }    }    if (mVelocityTracker != null) {        mVelocityTracker.addMovement(vtev);    }    vtev.recycle();    return true;}private void onTouchMove(MotionEvent ev, MotionEvent vtev) {    if (mHasPerformedLongPress) {        // Consume all move events following a successful long press.        return;    }    int pointerIndex = ev.findPointerIndex(mActivePointerId);    if (pointerIndex == -1) {        pointerIndex = 0;        mActivePointerId = ev.getPointerId(pointerIndex);    }    if (mDataChanged) {        // Re-sync everything if data has been changed        // since the scroll operation can query the adapter.        layoutChildren();    }    final int y = (int) ev.getY(pointerIndex);    // ↓↓↓ 2. 这里的 mTouchMode = TOUCH_MODE_SCROLL    switch (mTouchMode) {        case TOUCH_MODE_DOWN:        case TOUCH_MODE_TAP:        case TOUCH_MODE_DONE_WAITING:            // Check if we have moved far enough that it looks more like a            // scroll than a tap. If so, we'll enter scrolling mode.            if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {                break;            }            // Otherwise, check containment within list bounds. If we're            // outside bounds, cancel any active presses.            final View motionView = getChildAt(mMotionPosition - mFirstPosition);            final float x = ev.getX(pointerIndex);            if (!pointInView(x, y, mTouchSlop)) {                setPressed(false);                if (motionView != null) {                    motionView.setPressed(false);                }                removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?                        mPendingCheckForTap : mPendingCheckForLongPress);                mTouchMode = TOUCH_MODE_DONE_WAITING;                updateSelectorState();            } else if (motionView != null) {                // Still within bounds, update the hotspot.                final float[] point = mTmpPoint;                point[0] = x;                point[1] = y;                transformPointToViewLocal(point, motionView);                motionView.drawableHotspotChanged(point[0], point[1]);            }            break;        case TOUCH_MODE_SCROLL:        case TOUCH_MODE_OVERSCROLL:            // ↓↓↓ 3. 于是也就执行该分支的代码            scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);            break;    }}private void scrollIfNeeded(int x, int y, MotionEvent vtev) {    int rawDeltaY = y - mMotionY;    int scrollOffsetCorrection = 0;    int scrollConsumedCorrection = 0;    if (mLastY == Integer.MIN_VALUE) {        rawDeltaY -= mMotionCorrection;    }    if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY, mScrollConsumed, mScrollOffset)) {        rawDeltaY += mScrollConsumed[1];        scrollOffsetCorrection = -mScrollOffset[1];        scrollConsumedCorrection = mScrollConsumed[1];        if (vtev != null) {            vtev.offsetLocation(0, mScrollOffset[1]);            mNestedYOffset += mScrollOffset[1];        }    }    final int deltaY = rawDeltaY;    int incrementalDeltaY =            mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;    int lastYCorrection = 0;    if (mTouchMode == TOUCH_MODE_SCROLL) {        if (PROFILE_SCROLLING) {            if (!mScrollProfilingStarted) {                Debug.startMethodTracing("AbsListViewScroll");                mScrollProfilingStarted = true;            }        }        if (mScrollStrictSpan == null) {            // If it's non-null, we're already in a scroll.            mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");        }        if (y != mLastY) {            // We may be here after stopping a fling and continuing to scroll.            // If so, we haven't disallowed intercepting touch events yet.            // Make sure that we do so in case we're in a parent that can intercept.            if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 && Math.abs(rawDeltaY) > mTouchSlop) {                final ViewParent parent = getParent();                if (parent != null) {                    parent.requestDisallowInterceptTouchEvent(true);                }            }            final int motionIndex;            if (mMotionPosition >= 0) {                motionIndex = mMotionPosition - mFirstPosition;            } else {                // If we don't have a motion position that we can reliably track,                // pick something in the middle to make a best guess at things below.                motionIndex = getChildCount() / 2;            }            int motionViewPrevTop = 0;            View motionView = this.getChildAt(motionIndex);            if (motionView != null) {                motionViewPrevTop = motionView.getTop();            }            // No need to do all this work if we're not going to move anyway            boolean atEdge = false;            // ↓↓↓ 4. 如果移动增量不为0, 说明用户进行了移动操作, 执行该方法            if (incrementalDeltaY != 0) {                // ↓↓↓ 4.1 执行该方法, 传递按下的Y坐标, 和Y轴移动增量                atEdge = trackMotionScroll(deltaY, incrementalDeltaY);            }            // Check to see if we have bumped into the scroll limit            motionView = this.getChildAt(motionIndex);            if (motionView != null) {                // Check if the top of the motion view is where it is                // supposed to be                final int motionViewRealTop = motionView.getTop();                if (atEdge) {                    // Apply overscroll                    int overscroll = -incrementalDeltaY - (motionViewRealTop - motionViewPrevTop);                    if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll, mScrollOffset)) {                        lastYCorrection -= mScrollOffset[1];                        if (vtev != null) {                            vtev.offsetLocation(0, mScrollOffset[1]);                            mNestedYOffset += mScrollOffset[1];                        }                    } else {                        final boolean atOverscrollEdge = overScrollBy(0, overscroll, 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);                        if (atOverscrollEdge && mVelocityTracker != null) {                            // Don't allow overfling if we're at the edge                            mVelocityTracker.clear();                        }                        final int overscrollMode = getOverScrollMode();                        if (overscrollMode == OVER_SCROLL_ALWAYS ||                                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {                            if (!atOverscrollEdge) {                                mDirection = 0; // Reset when entering overscroll.                                mTouchMode = TOUCH_MODE_OVERSCROLL;                            }                            if (incrementalDeltaY > 0) {                                mEdgeGlowTop.onPull((float) -overscroll / getHeight(), (float) x / getWidth());                                if (!mEdgeGlowBottom.isFinished()) {                                    mEdgeGlowBottom.onRelease();                                }                                invalidateTopGlow();                            } else if (incrementalDeltaY < 0) {                                mEdgeGlowBottom.onPull((float) overscroll / getHeight(), 1.f - (float) x / getWidth());                                if (!mEdgeGlowTop.isFinished()) {                                    mEdgeGlowTop.onRelease();                                }                                invalidateBottomGlow();                            }                        }                    }                }                mMotionY = y + lastYCorrection + scrollOffsetCorrection;            }            mLastY = y + lastYCorrection + scrollOffsetCorrection;        }    } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {        // ...    }}boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {    final int childCount = getChildCount();    if (childCount == 0) {        return true;    }    final int firstTop = getChildAt(0).getTop();    final int lastBottom = getChildAt(childCount - 1).getBottom();    final Rect listPadding = mListPadding;    // "effective padding" In this case is the amount of padding that affects    // how much space should not be filled by items. If we don't clip to padding    // there is no effective padding.    int effectivePaddingTop = 0;    int effectivePaddingBottom = 0;    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {        effectivePaddingTop = listPadding.top;        effectivePaddingBottom = listPadding.bottom;    }     // FIXME account for grid vertical spacing too?    final int spaceAbove = effectivePaddingTop - firstTop;    final int end = getHeight() - effectivePaddingBottom;    final int spaceBelow = lastBottom - end;    final int height = getHeight() - mPaddingBottom - mPaddingTop;    if (deltaY < 0) {        deltaY = Math.max(-(height - 1), deltaY);    } else {        deltaY = Math.min(height - 1, deltaY);    }    if (incrementalDeltaY < 0) {        incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);    } else {        incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);    }    final int firstPosition = mFirstPosition;    // Update our guesses for where the first and last views are    if (firstPosition == 0) {        mFirstPositionDistanceGuess = firstTop - listPadding.top;    } else {        mFirstPositionDistanceGuess += incrementalDeltaY;    }    if (firstPosition + childCount == mItemCount) {        mLastPositionDistanceGuess = lastBottom + listPadding.bottom;    } else {        mLastPositionDistanceGuess += incrementalDeltaY;    }    final boolean cannotScrollDown = (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0);    final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);    if (cannotScrollDown || cannotScrollUp) {        return incrementalDeltaY != 0;    }    // ↓↓↓ 5. 当 incrementalDeltaY < 0 时, 表示向下滑动, 否则为向上滑动    final boolean down = incrementalDeltaY < 0;    final boolean inTouchMode = isInTouchMode();    if (inTouchMode) {        hideSelector();    }    final int headerViewsCount = getHeaderViewsCount();    final int footerViewsStart = mItemCount - getFooterViewsCount();    int start = 0;    int count = 0;    // ↓↓↓ 6. 当向下滑动时    if (down) {        int top = -incrementalDeltaY;        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {            top += listPadding.top;        }        for (int i = 0; i < childCount; i++) {            // ↓↓↓ 6.1 获取子View            final View child = getChildAt(i);            if (child.getBottom() >= top) {                break;            } else {                // ↓↓↓ 6.2 如果子View的bottom < top 时, 则说明子View已经滑出屏幕了                count++;                int position = firstPosition + i;                if (position >= headerViewsCount && position < footerViewsStart) {                    // The view will be rebound to new data, clear any                    // system-managed transient state.                    child.clearAccessibilityFocus();                    // ↓↓↓ 6.3 将其添加到RecycleBin的废弃View里, 并count++                    mRecycler.addScrapView(child, position);                }            }        }    } else {        int bottom = getHeight() - incrementalDeltaY;        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {            bottom -= listPadding.bottom;        }        for (int i = childCount - 1; i >= 0; i--) {            final View child = getChildAt(i);            if (child.getTop() <= bottom) {                break;            } else {                start = i;                count++;                int position = firstPosition + i;                if (position >= headerViewsCount && position < footerViewsStart) {                    // The view will be rebound to new data, clear any                    // system-managed transient state.                    child.clearAccessibilityFocus();                    mRecycler.addScrapView(child, position);                }            }        }    }    mMotionViewNewTop = mMotionViewOriginalTop + deltaY;    mBlockLayoutRequests = true;    if (count > 0) {        // ↓↓↓ 7. 如果有需要被回收的View, 则将这些View从父容器中清除 (detach)        detachViewsFromParent(start, count);        mRecycler.removeSkippedScrap();    }    // invalidate before moving the children to avoid unnecessary invalidate    // calls to bubble up from the children all the way to the top    if (!awakenScrollBars()) {       invalidate();    }    // ↓↓↓ 8. 然后根据手指滑动的增量incrementalDeltaY, 移动这些View, 也就产生了listview里的itemView的移动效果    offsetChildrenTopAndBottom(incrementalDeltaY);    if (down) {        mFirstPosition += count;    }    final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);    // ↓↓↓ 9. 如果一个View的底部移入了屏幕, 或者 一个View的顶部移入了屏幕, 则执行该方法    if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {        fillGap(down);    }    mRecycler.fullyDetachScrapViews();    if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {        final int childIndex = mSelectedPosition - mFirstPosition;        if (childIndex >= 0 && childIndex < getChildCount()) {            positionSelector(mSelectedPosition, getChildAt(childIndex));        }    } else if (mSelectorPosition != INVALID_POSITION) {        final int childIndex = mSelectorPosition - mFirstPosition;        if (childIndex >= 0 && childIndex < getChildCount()) {            positionSelector(INVALID_POSITION, getChildAt(childIndex));        }    } else {        mSelectorRect.setEmpty();    }    mBlockLayoutRequests = false;    invokeOnItemScrollListener();    return false;}abstract void fillGap(boolean down);void fillGap(boolean down) {    final int count = getChildCount();    // ↓↓↓ 9.1 如果向下滑    if (down) {        int paddingTop = 0;        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {            paddingTop = getListPaddingTop();        }        final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight : paddingTop;        // ↓↓↓ 9.2 则执行该方法, 来填充View        fillDown(mFirstPosition + count, startOffset);        correctTooHigh(getChildCount());    } else {        int paddingBottom = 0;        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {            paddingBottom = getListPaddingBottom();        }        final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight : getHeight() - paddingBottom;        fillUp(mFirstPosition - 1, startOffset);        correctTooLow(getChildCount());    }}private View fillDown(int pos, int nextTop) {    // ...    while (nextTop < end && pos < mItemCount) {        // is this the selected item?        boolean selected = pos == mSelectedPosition;        // ↓↓↓ 9.3 就需要去创建或者获取需要的View        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);        nextTop = child.getBottom() + mDividerHeight;        if (selected) {            selectedView = child;        }        pos++;    }    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);    return selectedView;}private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) {    View child;    if (!mDataChanged) {        // Try to use an existing view for this position        // ↓↓↓ 9.4 获取缓存的View, 既然View都已经展示在屏幕上了, 那么就没有可用的缓存View 了        child = mRecycler.getActiveView(position);        if (child != null) {            // Found it -- we're using an existing child            // This just needs to be positioned            setupChild(child, position, y, flow, childrenLeft, selected, true);            return child;        }    }    // Make a new view for this position, or convert an unused view if possible    // ↓↓↓ 9.5 于是就会调用该方法去获取    child = obtainView(position, mIsScrap);    // This needs to be positioned and measured    // ↓↓↓ 9.8 获取到View后, 执行该方法    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);    return child;}View obtainView(int position, boolean[] isScrap) {    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");    isScrap[0] = false;    // ...    // ↓↓↓ 9.6 获取一个废弃的View, 可能获取到, 可能获取不到    final View scrapView = mRecycler.getScrapView(position);    // ↓↓↓ 9.7 从适配器里获取一个View, 这是必然能获取到的, 因为getView参数收了scrapView, 如果该scrapView的不为null, 我们将赋值, 为null, 我们将创建    final View child = mAdapter.getView(position, scrapView, this);    if (scrapView != null) {        if (child != scrapView) {            // Failed to re-bind the data, return scrap to the heap.            mRecycler.addScrapView(scrapView, position);        } else {            if (child.isTemporarilyDetached()) {                isScrap[0] = true;                // Finish the temporary detach started in addScrapView().                child.dispatchFinishTemporaryDetach();            } else {                // we set isScrap to "true" only if the view is temporarily detached.                // if the view is fully detached, it is as good as a view created by the                // adapter                isScrap[0] = false;            }        }    }    if (mCacheColorHint != 0) {        child.setDrawingCacheBackgroundColor(mCacheColorHint);    }    if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {        child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);    }    setItemViewLayoutParams(child, position);    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;}private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) {    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");    final boolean isSelected = selected && shouldShowSelector();    final boolean updateChildSelected = isSelected != child.isSelected();    final int mode = mTouchMode;    final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&            mMotionPosition == position;    final boolean updateChildPressed = isPressed != child.isPressed();    final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();    // Respect layout params that are already in the view. Otherwise make some up...    // noinspection unchecked    AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();    if (p == null) {        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();    }    p.viewType = mAdapter.getItemViewType(position);    p.isEnabled = mAdapter.isEnabled(position);    if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {        // 9.9 然后就将View添加到父容器中 (attach), 基本流程就到这里结束了        attachViewToParent(child, flowDown ? -1 : 0, p);    } else {        p.forceAdd = false;        if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {            p.recycledHeaderFooter = true;        }        addViewInLayout(child, flowDown ? -1 : 0, p, true);    }    if (updateChildSelected) {        child.setSelected(isSelected);    }    if (updateChildPressed) {        child.setPressed(isPressed);    }    if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {        if (child instanceof Checkable) {            ((Checkable) child).setChecked(mCheckStates.get(position));        } else if (getContext().getApplicationInfo().targetSdkVersion                >= android.os.Build.VERSION_CODES.HONEYCOMB) {            child.setActivated(mCheckStates.get(position));        }    }    if (needToMeasure) {        final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,                mListPadding.left + mListPadding.right, p.width);        final int lpHeight = p.height;        final int childHeightSpec;        if (lpHeight > 0) {            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);        } else {            childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),                    MeasureSpec.UNSPECIFIED);        }        child.measure(childWidthSpec, childHeightSpec);    } else {        cleanupLayoutState(child);    }    final int w = child.getMeasuredWidth();    final int h = child.getMeasuredHeight();    final int childTop = flowDown ? y : y - h;    if (needToMeasure) {        final int childRight = childrenLeft + w;        final int childBottom = childTop + h;        child.layout(childrenLeft, childTop, childRight, childBottom);    } else {        child.offsetLeftAndRight(childrenLeft - child.getLeft());        child.offsetTopAndBottom(childTop - child.getTop());    }    if (mCachingStarted && !child.isDrawingCacheEnabled()) {        child.setDrawingCacheEnabled(true);    }    if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)            != position) {        child.jumpDrawablesToCurrentState();    }    Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
    • 可见ListView在向下滑动时, 会将移出屏幕的View添加到RecycleBin的废弃View里, 然后将其标记为detach状态, 然后移动屏幕上的View, 让他们产生对应的滑动
    • 同时, 屏幕上方的View会进入屏幕, 首先从RecycleBin里获取缓存View, 获取不到就到Adapter里去获取
  • ListView滑动过程图

异步加载图片错位解决方案

  • 通过设置Tag的方式解决:
    • 方式一:
      • ImageView.setTag(imageUrl); // 对ImageView设置Tag
      • ImageView imageView = (ImageView) ListView.findViewWithTag(imageUrl); // 从ListVIew中寻找Tag,返回不为null说明该ImageView还在展示中,为其设置图片
      • 原理: 由于ImageView是被不断的回收利用的, 每次对同一个ImageView对象设置Tag都会被覆盖, 所以以前的ImageView的Tag是找不着的.
  • 案例:

    • 方式一实现代码:

      • Adapter代码, 跟往常一样

        public class Case1Adapter extends BaseAdapter {    private LayoutInflater inflater;    private List<String> mDatas;    public Case1Adapter(Context context, List<String> datas) {        this.inflater = LayoutInflater.from(context);        this.mDatas = datas;    }    @Override    public int getCount() {        return mDatas == null ? 0 : mDatas.size();    }    @Override    public Object getItem(int i) {        return mDatas.get(i);    }    @Override    public long getItemId(int i) {        return i;    }    @Override    public View getView(int i, View convertView, ViewGroup viewGroup) {        ViewHolder viewHolder;        if(convertView == null){            convertView = inflater.inflate(R.layout.item, null);            viewHolder = new ViewHolder();            viewHolder.sync = (TextView)convertView.findViewById(R.id.sync);            viewHolder.async = (TextView)convertView.findViewById(R.id.async);            convertView.setTag(viewHolder);        }else{            viewHolder = (ViewHolder) convertView.getTag();        }        viewHolder.sync.setText("当前位置为: " + i);        if (mDatas.size() != 0) {            AsyncUtil.ayncLoaderData((ListView) viewGroup, viewHolder.async, mDatas.get(i));        }        return  convertView;    }    class ViewHolder{        TextView sync;        TextView async;    }}
      • 这里主要看DataTask线程池里的操作

        public class AsyncUtil {    public static void ayncLoaderData(ListView listView, TextView textView, String s) {        if (textView == null || s == null) return;        textView.setEnabled(false);        new DataTask(listView, textView, s).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);    }    /**     * 线程池     * @author Luzhuo     */    private static class DataTask extends AsyncTask<Object, Void, Void>{        private ListView mListView;        private TextView mTextView;        private String mData;        public DataTask(ListView listView, TextView textView, String s) {            this.mListView = listView;            this.mTextView = textView;            this.mData = s;        }        @Override        protected void onPreExecute() {            // 设置Tag            mTextView.setTag(mData);        }        /**         * 设置延迟, 模拟图片的网络请求耗时         */        @Override        protected Void doInBackground(Object... params) {            SystemClock.sleep(500);            return null;        }        /**         * 设置文字, 模拟图片设置         * @param result         */        @Override        protected void onPostExecute(Void result) {            // 获取有该Tag的View            TextView tv = (TextView) mListView.findViewWithTag(mData);            if (tv == null) return;            mTextView.setText("异步数据: " + mData);            mTextView.setEnabled(true);        }    }}
      • 效果图:

        • 未做处理的效果
        • 使用方式一处理后的效果
阅读全文
0 0
原创粉丝点击