android中初始化listview问题1

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并没有重复调用

可以看出代码中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的区别

测试结果

测试结果2
分析:重复调用,ViewHolder起到减少加载的效果
终于,重复调用了多次,唯一的区别就是listview的layout_height,为什么呢?

listview绘制中wrap_content和match_parent的区别

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

Created with Raphaël 2.1.0 RootView viewGroup layout is view? view layout yes no

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,我看了几遍源码,暂时还没有找到具体是在哪里调用,哪位博友知道,可以提示一下,谢谢!

这里写图片描述

源码下载

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值