ListView原理简单介绍(着重介绍getView被调用的一系列过程)

今天出去面试,被面试官问到一个问题,说是如果使用LayoutInflate.inflate(int resource, ViewGroup root, boolean attachToRoot);这个方法与AbsListView的实现类结合使用的话,会出现什么问题,先看简单的使用过程:
@Override
			public View getView(int position, View convertView, ViewGroup parent) {
				View view = inflater.inflate(R.layout.activity_main, parent, true);
				TextView textView = (TextView) view.findViewById(R.id.title);
				textView.setText(datas[position]);
				return view;
			}

好了,重点在第三行,我将Adapter的getView方法所传回的ViewGroup parent对象放置到了inflate的第二个参数中使用,inflate的第三个参数为true,面试官当时问的就是会出现什么问题,现在运行一下,看Log:

ListView原理简单介绍(着重介绍getView被调用的一系列过程)

出了java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView的异常,我们看一下问题出在哪:

首先,要看从getView第三个参数回调传回来的是什么,我们来看源码:

既然是adapter与AbsListView结合使用,那getView方法一定是在AbsListView中被使用的,来找一找:

首先该怎么找呢?咱们都知道AbsListView通过setAdapter方法使两者结合,那么入口就在这里:

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        resetList();
        mRecycler.clear();

        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }

        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;

        // AbsListView#setAdapter will update choice mode states.
        super.setAdapter(adapter);
通过第13行可以知道adapter对象是赋给了mAdapter,通过查看mAdapter是父类的属性,那咱们就需要在父类中看什么时候使用了mAdaper.getView方法:

果然找到了,在AbsListView的obtainView方法中找到了getView方法被使用的情况:

View obtainView(int position, boolean[] isScrap) {
        isScrap[0] = false;
        View scrapView;

        scrapView = mRecycler.getTransientStateView(position);
        if (scrapView != null) {
            return scrapView;
        }

        scrapView = mRecycler.getScrapView(position);

        View child;
        if (scrapView != null) {
            child = mAdapter.getView(position, scrapView, this);

            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            if (child != scrapView) {
                mRecycler.addScrapView(scrapView, position);
                if (mCacheColorHint != 0) {
                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
                }
            } else {
                isScrap[0] = true;
                child.dispatchFinishTemporaryDetach();
            }
        } else {
            child = mAdapter.getView(position, null, this);
通过第14行和最后一行可知,它是将AbsListView的实现类传了过来。

那好,就回到 inflater.inflate(R.layout.activity_main, parent, true);这里,继续向下看:

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        if (DEBUG) System.out.println("INFLATING from resource: " + resource);
        XmlResourceParser parser = getContext().getResources().getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
这里调用了重载方法
 inflate(parser, root, attachToRoot);
在重载方法内部我们看到:

  // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

也就是说把自定义的这个Item附加到了AbsListView上,好。接下来看getView被返回的View被用作在了什么地方,它目前已经有parent了。

还是需要回到AbsListView.obtainView方法,通过第14行可以看到这个通过getView方法返回的View最终被obtainView弹了出去,继续看,由于在AbsListView中没有找到使用obtainView的地方,所以使用obtainView的地方应该在其子类中,果不其然(这里通过ListView做演示):

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int childWidth = 0;
        int childHeight = 0;
        int childState = 0;

        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
                heightMode == MeasureSpec.UNSPECIFIED)) {
            final View child = obtainView(0, mIsScrap);

我们在最后一行看到了obtainView的身影,它被用来做什么呢?既然是onMeasure方法,那就是测量呗,没什么好说的,再继续看,在ListView中发现5处obtainView被调用的地方,其中两处用于测量,剩余3处通过:
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
            boolean selected, boolean recycled)
这个方法将obtainView返回的View传了进来,最终我们可以在该方法内部看到这么一段代码,是属于ViewGroup的:

   attachViewToParent(child, flowDown ? -1 : 0, p);

---未完待续---

上一篇:如何下载Android源码(非常详细,含自动恢复下载,编译,运行模拟器说明)


下一篇:如何轻松搞定SAP HANA数据库备份?