您的位置:首页 > 移动开发 > Android开发

android中初始化listview问题1

2016-01-20 17:27 531 查看

android中初始化listview问题1

问题:

适配器中getView()重复调用多次问题,是在getcount的基础上多跑了几个周期

关键字

android

listview初始化

getview重复加载多次

ListView

ListView是Android软件开发中非常重要组件之一,使用频繁。对于上边的问题,大家很有可能遇到但是没有在意而已,下边我们就先看一下这个getview()加载实际测试结果,进而进行分析。

测试

listview配置

文件activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >

<ListView
android:id="@+id/activity_main_lv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="1dp"
/>

</RelativeLayout>


adapter 布局文件adapter_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
android:id="@+id/adapter_item_title_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/adapter_item_content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>


adapter 中getview() 代码

@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.e("itemadater---getview", "position:"+position+",count:"+parent.getChildCount());
ViewHolder holder =null;
if(convertView==null || position==0){
Log.e("itemadater---getview", "convertView==null:true");
holder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.adapter_item, null);
holder.titleTv = (TextView)convertView.findViewById(R.id.adapter_item_title_tv);
holder.contentTv = (TextView)convertView.findViewById(R.id.adapter_item_content_tv);
convertView.setTag(holder);
}else{
Log.e("itemadater---getview", "convertView==null:false");
holder = (ViewHolder)convertView.getTag();
}

Item item = list.get(position);
if(item!=null){
holder.titleTv.setText(item.getTitle());
holder.contentTv.setText(item.getContent());
}

return convertView;
}


测试结果



可以看出代码中getview并没有重复调用,但是用ViewHolder后并没有起到减少加载的效果,这是什么原因,稍后会解答。

测试

首先我们是要找到在什么情况下getview会重复调用多次

修改activity_main.xml,如下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >

<ListView
android:id="@+id/activity_main_lv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:dividerHeight="1dp"
/>

</RelativeLayout>


大家可以看到,唯一的区别就是layout_height的值wrap_content和match_parent的区别

测试结果



分析:重复调用,ViewHolder起到减少加载的效果

终于,重复调用了多次,唯一的区别就是listview的layout_height,为什么呢?

listview绘制中wrap_content和match_parent的区别

我们先来猜想一下,view绘制wrap_content和match_parent的区别

Created with Raphaël 2.1.0RootViewviewGroup layoutis view?view layoutyesno

view 绘制三步:measure—>layout—>draw

猜想:

前者是根据内容大小展示,measure listview的高度,无法确定,肯定会先measure listview中item的高度汇总,去确定viewparent的高度,getview第一个周期,然后再到listview中item view本身的measure,getview第二个周期,至于最后一个周期,明显是绘制完又再测量一次,确认高度准确。

后者,测量 listview 不需要测量item,可以直接得到,然后绘制子view,就到meaeure item view高度,调用一个周期getview().

源码分析

首先我们需要知道

wrap_parent -> MeasureSpec.AT_MOST

match_parent -> MeasureSpec.EXACTLY

具体值 -> MeasureSpec.EXACTLY

接下来看源码

ListView 的onMeasure()源码

@Override
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);

measureScrapChild(child, 0, widthMeasureSpec);

childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
childState = combineMeasuredStates(childState, child.getMeasuredState());

if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, -1);
}
}

if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState&MEASURED_STATE_MASK);
}

if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}

if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}

setMeasuredDimension(widthSize , heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}


仔细看完这个方法,我们发现当父布局为match_parent时,height是直接MeasureSpec.getSize(heightMeasureSpec)获取到的默认值。在倒数第5、6、7行,我们明显可以看到如果父布局是wrap_content会执行measureHeightOfChildren去获取子view高度

measureHeightOfChildren()源码

/**
* Measures the height of the given range of children (inclusive) and
* returns the height with this ListView's padding and divider heights
* included. If maxHeight is provided, the measuring will stop when the
* current height reaches maxHeight.
*
* @param widthMeasureSpec The width measure spec to be given to a child's
*            {@link View#measure(int, int)}.
* @param startPosition The position of the first child to be shown.
* @param endPosition The (inclusive) position of the last child to be
*            shown. Specify {@link #NO_POSITION} if the last child should be
*            the last available child from the adapter.
* @param maxHeight The maximum height that will be returned (if all the
*            children don't fit in this value, this value will be
*            returned).
* @param disallowPartialChildPosition In general, whether the returned
*            height should only contain entire children. This is more
*            powerful--it is the first inclusive position at which partial
*            children will not be allowed. Example: it looks nice to have
*            at least 3 completely visible children, and in portrait this
*            will most likely fit; but in landscape there could be times
*            when even 2 children can not be completely shown, so a value
*            of 2 (remember, inclusive) would be good (assuming
*            startPosition is 0).
* @return The height of this ListView with the given children.
*/
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
final int maxHeight, int disallowPartialChildPosition) {

final ListAdapter adapter = mAdapter;
if (adapter == null) {
return mListPadding.top + mListPadding.bottom;
}

// Include the padding of the list
int returnedHeight = mListPadding.top + mListPadding.bottom;
final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
// The previous height value that was less than maxHeight and contained
// no partial children
int prevHeightWithoutPartialChild = 0;
int i;
View child;

// mItemCount - 1 since endPosition parameter is inclusive
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
final boolean recyle = recycleOnMeasure();
final boolean[] isScrap = mIsScrap;

for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i, isScrap);

measureScrapChild(child, i, widthMeasureSpec);

if (i > 0) {
// Count the divider for all but one child
returnedHeight += dividerHeight;
}

// Recycle the view before we possibly return from the method
if (recyle && recycleBin.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
recycleBin.addScrapView(child, -1);
}

returnedHeight += child.getMeasuredHeight();

if (returnedHeight >= maxHeight) {
// We went over, figure out which height to return.  If returnedHeight > maxHeight,
// then the i'th position did not fit completely.
return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
&& (i > disallowPartialChildPosition) // We've past the min pos
&& (prevHeightWithoutPartialChild > 0) // We have a prev height
&& (returnedHeight != maxHeight) // i'th child did not fit completely
? prevHeightWithoutPartialChild
: maxHeight;
}

if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
prevHeightWithoutPartialChild = returnedHeight;
}
}

// At this point, we went through the range of children, and they each
// completely fit, so return the returnedHeight
return returnedHeight;
}


当父布局为wrap_content时,这里多调用了一个周期getview(),即,下边这个周期



可以看到viewgroup中并没有真正绘制view,count一直是0.

到此除了第三个周期,前边两次已经证实猜想没有问题!

对于第三个周期getview调用,看日志应该是检验,如下图,count已经绘制好 ,一直是3,我看了几遍源码,暂时还没有找到具体是在哪里调用,哪位博友知道,可以提示一下,谢谢!



源码下载

谢谢,大家,如果哪里有问题,请不吝指教!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: