Listview源码解析(一)

1, 概述

ListView继承关系如下,

ListView也是一个控件,但是不同于其他的简单的控件, 它处理大量的内容元素。那么,这些元素从哪里来呢, Adapter为ListView提供数据。

2 实现

这个例子主要是读取手机上的联系人并用listview显示。

1,listview 

sortListView = (ListView) findViewById(R.id.country_lvcountry); // Listview
sortListView.setOnItemClickListener(new OnItemClickListener() {  // item单击事件
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
	Toast.makeText(getApplication(), ((SortModel)adapter.getItem(position)).getName(), 
Toast.LENGTH_SHORT).show();}
});

布局如下,
<ListView
            android:id="@+id/country_lvcountry"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_gravity="center"
            android:divider="@null" />

2,adapter 填充数据
SourceDateList = getContactsData(); // 获取phone中的联系人
Collections.sort(SourceDateList, pinyinComparator);
adapter = new SortAdapter(this, SourceDateList); // 新建SortAdapter
sortListView.setAdapter(adapter); // 将SortAdapter和ListView联系在一起

private List<SortModel> getContactsData() {
List<SortModel> mSortList = new ArrayList<SortModel>();
ContentResolver resolver = getContentResolver();
Cursor phoneCursor = resolver.query(Phone.CONTENT_URI, PHONES_PROJECTION, 
ContactsContract.RawContacts.ACCOUNT_NAME + " = 'PHONE'", null, null);
if (phoneCursor.moveToFirst()) {
	do{
	String contactName = phoneCursor.getString(0);
	String phoneNumber = phoneCursor.getString(1);
	String lookfornumber = null;
	if (TextUtils.isEmpty(phoneNumber)) {
		lookfornumber = null;
	} else {
	if (phoneNumber.contains("+86")) {
		lookfornumber = phoneNumber.replace("+86", "").replaceAll("[^\\d]", "");
	} else {
		lookfornumber = phoneNumber.replaceAll("[^\\d]", "");
		}
	}
	SortModel sortModel = new SortModel();
	sortModel.setName(contactName);
	sortModel.setNumber(lookfornumber);
	String pinyin = characterParser.getSelling(contactName);// change to 
	String sortString = pinyin.substring(0, 1).toUpperCase();
	if (sortString.matches("[A-Z]")) {
	sortModel.setSortLetters(sortString.toUpperCase());
	} else {
	sortModel.setSortLetters("#");
	}
	mSortList.add(sortModel);
	}while (phoneCursor.moveToNext());
	phoneCursor.close();
	}
	return mSortList;
	}

SortAdapter主要部分如下,

public class SortAdapter extends BaseAdapter {
	private List<SortModel> list = null;
	private Context mContext;
	
	public SortAdapter(Context mContext, List<SortModel> list) {
		this.mContext = mContext;
		this.list = list;
	}
	
	public void updateListView(List<SortModel> list){
		this.list = list;
		notifyDataSetChanged(); // 更新数据库
	}

public View getView(final int position, View view, ViewGroup arg2) {
		ViewHolder viewHolder = null;
		final SortModel mContent = list.get(position);
		if (view == null) {
			viewHolder = new ViewHolder();
			view = LayoutInflater.from(mContext).inflate(R.layout.item, null);
			viewHolder.tvTitle = (TextView) view.findViewById(R.id.title);
			viewHolder.tvLetter = (TextView) view.findViewById(R.id.catalog);
			viewHolder.tvnumber = (TextView) view.findViewById(R.id.number);
			view.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) view.getTag();
		}
		
		int section = getSectionForPosition(position);
		if(position == getPositionForSection(section)){
			viewHolder.tvLetter.setVisibility(View.VISIBLE);
			viewHolder.tvLetter.setText(mContent.getSortLetters());
		}else{
			viewHolder.tvLetter.setVisibility(View.GONE);
		}
	
		viewHolder.tvTitle.setText(this.list.get(position).getName());
		viewHolder.tvnumber.setText(this.list.get(position).getNumber());
		return view;

	}
	final static class ViewHolder {
		TextView tvLetter;
		TextView tvTitle;
		TextView tvnumber;
	}
}

3 listview绘制解析

3.1 onMeasure

Listview中onMeasure部分方法如下,

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            ~~~
        mItemCount = mAdapter == null ? 0 : mAdapter.getCount(); // item 数量
        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
                || heightMode == MeasureSpec.UNSPECIFIED)) {
            final View child = obtainView(0, mIsScrap); // 获取第一个子view

            // Lay out child directly against the parent measure spec so that
            // we can obtain exected minimum width and height.
              // 仅测量在第一个位置的值,确定最小的高和宽。
            measureScrapChild(child, 0, widthMeasureSpec, heightSize); 
            ~~~
}

在measureScrapChild方法中会调用子View的measure方法。

为什么只获取第一个子View呢,如果一次性全部获取,在item数量巨大的情况下会导致内存泄漏,所以listview采取动态加载子view的机制,仅需要确定每个字view的宽和高就可以了。

3.2 第一次onLayout

ListView中并没有onLayout方法,而是在父类AbsListView中,

完整的流程图如下,


layoutChildren方法代码比较多,不要被吓到了,节选如下

switch (mLayoutMode) { // 在AbsListView中,int mLayoutMode = LAYOUT_NORMAL;
•••
default:
                if (childCount == 0) { //ListView刚开始是没有子部局的,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);
                    }
                }

fillDown方法如下,主要是有一个循环,循环加载子View

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;
        }

        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;
    }

这里使用了一个while循环来执行重复逻辑,一开始nextTop的值是第一个子元素顶部距离整个ListView顶部的像素值,pos则是刚刚传入的mFirstPosition的值,而end是ListView底部减去顶部所得的像素值,mItemCount则是Adapter中的元素数量。因此一开始的情况下nextTop必定是小于end值的,并且pos也是小于mItemCount值的。那么每执行一次while循环,pos的值都会加1,并且nextTop也会增加,当nextTop大于等于end时,也就是子元素已经超出当前屏幕了,或者pos大于等于mItemCount时,也就是所有Adapter中的元素都被遍历结束了,就会跳出while循环。

makeAndAddView方法如下,

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
            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
        child = obtainView(position, mIsScrap); // 获取子View
        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }
1 obtainView

父类的obtainView方法最后会调用Adapter的getView方法,自己实现的一个getView方法如下,

public View getView(final int position, View view, ViewGroup arg2) {
		ViewHolder viewHolder = null;
		final SortModel mContent = list.get(position);
		if (view == null) {
			viewHolder = new ViewHolder();
			view = LayoutInflater.from(mContext).inflate(R.layout.item, null);
			viewHolder.tvTitle = (TextView) view.findViewById(R.id.title);
			viewHolder.tvLetter = (TextView) view.findViewById(R.id.catalog);
			viewHolder.tvnumber = (TextView) view.findViewById(R.id.number);
			view.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) view.getTag();
		}
		
		int section = getSectionForPosition(position);
		if(position == getPositionForSection(section)){
			viewHolder.tvLetter.setVisibility(View.VISIBLE);
			viewHolder.tvLetter.setText(mContent.getSortLetters());
		}else{
			viewHolder.tvLetter.setVisibility(View.GONE);
		}
	
		viewHolder.tvTitle.setText(this.list.get(position).getName());
		viewHolder.tvnumber.setText(this.list.get(position).getNumber());
		return view;
	}

由源码可知,第一次layout过程当中,所有的子View都是动态调用LayoutInflater的inflate()方法加载出来的,这样就虽然比较耗时,但是后面就不会再有这种情况了。

2 setupChild

setupChild方法如下,

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;
            }
            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);
    }

刚才调用obtainView()方法获取到的子元素View,addViewInLayout()方法将它添加到了ListView当中。然后依次调用子View的measure和layout方法。根据fillDown()方法中的while循环,会让子元素View将整个ListView控件填满然后就跳出,也就是说即使我们的Adapter中有一千条数据,ListView也只会加载第一屏的数据,剩下的数据反正目前在屏幕上也看不到,所以不会去做多余的加载工作,这样就可以保证ListView中的内容能够迅速展示到屏幕上。

那么到此为止,第一次Layout过程结束。

3.3 第二次onLayout

第二次调用onLayout方法时,会涉及到子View的缓存

流程大体上和第一次一样,细节有些不同而已。

layoutChildren中关键方法,

final RecycleBin recycleBin = mRecycler;
            if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                }
            } else {
                recycleBin.fillActiveViews(childCount, firstPosition); // 保存所有子view
            }
detachAllViewsFromParent();//因为已经保存在mActiveViews中,所以在这儿清理所有子view完全无影响
•••
default:
                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 {
                    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;
            }

1,调用了RecycleBin的fillActiveViews方法保存所有子view

2,调用fillSpecific方法,源码如下,

private View fillSpecific(int position, int top) {
        boolean tempIsSelected = position == mSelectedPosition;
        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) {
            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();
            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;
        }
    }

fillSpecific()这算是一个新方法了,不过其实它和fillUp()、fillDown()方法功能也是差不多的,主要的区别在于,fillSpecific()方法会优先将指定位置的子View先加载到屏幕上,然后再加载该子View往上以及往下的其它子View。那么由于这里我们传入的position就是第一个子View的位置,于是fillSpecific()方法的作用就基本上和fillDown()方法是差不多的了,这里我们就不去关注太多它的细节,而是将精力放在makeAndAddView()方法上面。再次回到makeAndAddView()方法,代码如下所示,

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
            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
        child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

第一次layout时会调用obtainView来加载子view,第二次就可以直接调用getActiveView方法从mActiveViews中获取了。

3.4 dispatchDraw

View的ondraw方法会调用dispatchDraw方法来绘制子view,

Ondraw—>dispatchDraw--> drawChild--> draw

虽然listview 重写了dispatchDraw方法,但是逻辑上并没有大的改变。








  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值