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

本文详细探讨了Android ListView的基本使用,包括Adapter适配器模式,重点解析了ListView的RecycleBin机制和源码,特别是ListView的初始化与滑动加载逻辑。此外,还提出了针对异步图片加载错位问题的解决方案,通过设置Tag避免图片错位。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


本文由 Luzhuo 编写,转发请保留该信息.
原文: http://blog.youkuaiyun.com/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);
                }
            }
        }
      • 效果图:

        • 未做处理的效果
        • 使用方式一处理后的效果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值