android中初始化listview问题1
2016-01-20 17:27
531 查看
android中初始化listview问题1
问题:适配器中getView()重复调用多次问题,是在getcount的基础上多跑了几个周期
关键字
androidlistview初始化
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,我看了几遍源码,暂时还没有找到具体是在哪里调用,哪位博友知道,可以提示一下,谢谢!
源码下载
谢谢,大家,如果哪里有问题,请不吝指教!
相关文章推荐
- 框架模式 MVC 在Android中的使用
- Android动画学习笔记-Android Animation
- 【Android基础学习】Android设置Activity背景色透明
- Android Studio 打包时取消打某一个jar包
- android 省市县地址选择器
- Android开发笔记(五十四)数据共享接口ContentProvider
- Android-自定义PopupWindow
- Android开发
- Android程序点击启动后有黑屏或者白屏一闪而过解决办法
- Android inject input events 注入Touch 点(x, y) 触摸输入事件
- Android编译过程中预拷贝文件或文件夹
- Android的Handler几种常见的传值方式
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- android-Ultra-Pull-To-Refresh 源码解析
- ScaleGestureDetector SimpleOnScaleGestureListener缩放手势识别器
- Android循环广告的实现
- YY Java工程师/Android工程师 面经总结
- 安装android studio时,解决unable to access android sdk add-on list
- Android 漂浮类动效的分析与实现!
- android 分析内存泄露情况