listview adapter使用,及与recycleView数据更新的比较分析

本文深入分析了ListView的源码,详细介绍了setAdapter方法的工作流程,包括Adapter的注册、状态重置及视图更新机制。此外,还对比了ListView与RecyclerView在数据集变更通知机制上的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

listview 源码分析

在日常的工作中,我们会经常和listview打交道,无论是直接使用,还是经过封装使用。其中最重要的步骤就是给listview设置一个adapter。

listView = new ListView(this);
listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1,getData()));

setAdapter()方法中都做了什么?

/**
     * Sets the data behind this ListView.
     *
     * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
     * depending on the ListView features currently in use. For instance, adding
     * headers and/or footers will cause the adapter to be wrapped.
     *
     * @param adapter The ListAdapter which is responsible for maintaining the
     *        data backing this list and for producing a view to represent an
     *        item in that data set.
     *
     * @see #getAdapter() 
     */
    @Override
    public void setAdapter(ListAdapter adapter) {
    
        //首先我们看到每次给一个listview设置adapter时,adapter会注销之前注册的mDataSetObserver
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        //重置list
        resetList();
        mRecycler.clear();

    //当有headerView,footerView时,给headerView,footerView设置一个新的HeaderViewListAdapter
    //listView设置的adapter并不是我们设置的adapter,而是包装过的HeaderViewListAdapter
        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);
        
        ///当adapter不为空时,我们看看下面又干了什么
        if (mAdapter != null) {
        ///所有的状态重置
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();

            //new 出一个新的mDataSetObserver,并且和adapter绑定,
            //采用观察者设计模式,目的是对绑定Adapter的数据进行监测
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

           //通过参数viewTypeCount的值,创建多少可复用View的List对象
           //
            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
            
            //重置选中的位置
            int position;
            if (mStackFromBottom) {
                position = lookForSelectablePosition(mItemCount - 1, false);
            } else {
                position = lookForSelectablePosition(0, true);
            }
            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);

            if (mItemCount == 0) {
                // Nothing selected
                checkSelectionChanged();
            }
        } else {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
            checkSelectionChanged();
        }

        //重新绘制布局
        requestLayout();
    }

因此,从上面的源码注释可以看出,在一个界面中,重复的new adapter,并给listview设置是会造成很大的资源浪费,不仅仅是new一个adapter的问题,而是把所有状态重置,我们在使用中的过程中最好避免这种使用方式。


notifyDataSetChanged()方法是如何实现view更新的?

我们在上面的代码中可以看到这样一段话

mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);

其实我们常用的adapter.notifyDataSetChanged();view的绘制和更新就是由它实现的。那我们平时使用中view的绘制是都全部绘制还是都更新呢?? 我们现看下更新的原理实现把。在日常使用中,listview经常是和baseAdapter一起使用的,BaseAapter就是一个观察者模式。

我们先看下数据观察者底层是如何实现的。

public class DataSetObservable extends Observable<DataSetObserver> {
   /**
    * Invokes {@link DataSetObserver#onChanged} on each observer.
    * Called when the contents of the data set have changed.  The recipient
    * will obtain the new contents the next time it queries the data set.
    */
   public void notifyChanged() {
       synchronized(mObservers) {
           // since onChanged() is implemented by the app, it could do anything, including
           // removing itself from {@link mObservers} - and that could cause problems if
           // an iterator is used on the ArrayList {@link mObservers}.
           // to avoid such problems, just march thru the list in the reverse order.
           for (int i = mObservers.size() - 1; i >= 0; i--) {
               mObservers.get(i).onChanged();
           }
       }
   }
   
    /**
    * Invokes {@link DataSetObserver#onInvalidated} on each observer.
    * Called when the data set is no longer valid and cannot be queried again,
    * such as when the data set has been closed.
    */
   public void notifyInvalidated() {
       synchronized (mObservers) {
           for (int i = mObservers.size() - 1; i >= 0; i--) {
               mObservers.get(i).onInvalidated();
           }
       }
   }

我们Listview中用到的 AdapterDataSetObserver 是在 AbsListview中定义的一个内部类,是继承AdapterView<ListAdapter>.AdapterDataSetObserver

class AdapterDataSetObserver extends DataSetObserver {

       private Parcelable mInstanceState = null;

       @Override
       public void onChanged() {
           mDataChanged = true;
           mOldItemCount = mItemCount;
           mItemCount = getAdapter().getCount();

           // Detect the case where a cursor that was previously invalidated has
           // been repopulated with new data.
           if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                   && mOldItemCount == 0 && mItemCount > 0) {
               AdapterView.this.onRestoreInstanceState(mInstanceState);
               mInstanceState = null;
           } else {
               rememberSyncState();
           }
           checkFocus();
           //重绘布局
           requestLayout();
       }

       @Override
       public void onInvalidated() {
           mDataChanged = true;

           if (AdapterView.this.getAdapter().hasStableIds()) {
               // Remember the current state for the case where our hosting activity is being
               // stopped and later restarted
               mInstanceState = AdapterView.this.onSaveInstanceState();
           }

           // Data is invalid so we should reset our state
           mOldItemCount = mItemCount;
           mItemCount = 0;
           mSelectedPosition = INVALID_POSITION;
           mSelectedRowId = INVALID_ROW_ID;
           mNextSelectedPosition = INVALID_POSITION;
           mNextSelectedRowId = INVALID_ROW_ID;
           mNeedSync = false;

           checkFocus();
           requestLayout();
       }

       public void clearSavedState() {
           mInstanceState = null;
       }
   }

通过源码我们可以知道,当Listview中数据变化时,通过adapter的notifyDataSetChanged,触发父类DataSetObervable方法,这个会调用所有观察者的onChanged,这个里面会重新绘制布局,刷新界面。这里就可以看出,一旦调用notifyDataSetChanged就会将整个界面重新绘制,而不是将更新的部分界面重绘。例如我们在listview数据变化的时候都会调用。 因此,我们在使用notifyDataSetChanged方法时应该考虑到重绘界面带来的性能消耗,在平时的使用中仅在必须的时候使用。

现在大家都逐渐使用reycleView来替代listview,而reycleView更新数据集不是用adapter.notifyDataSetChanged(),而是 notifyItemInserted(position) 与notifyItemRemoved(position),只有用后两种才会有动画。下面是源码:

public final void notifyItemChanged(int position, Object payload) {
        this.mObservable.notifyItemRangeChanged(position, 1, payload);
    }
    
public final void notifyItemInserted(int position) {
        this.mObservable.notifyItemRangeInserted(position, 1);
    }
    
public final void notifyItemRemoved(int position) {
        this.mObservable.notifyItemRangeRemoved(position, 1);
    }

当然,这只是部分代码,我们普通的adpater是没有这样的方法,所有reycleView需要用自己特定的adapter,与此对应的是,它也有专属AdapterDataObservable,与我们通常的区别如下,增加了一些专属方法:

public boolean hasObservers() {
        return !this.mObservers.isEmpty();
    }
    
     public void notifyItemRangeChanged(int positionStart, int itemCount) {
        this.notifyItemRangeChanged(positionStart, itemCount, (Object)null);
    }

    public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
        for(int i = this.mObservers.size() - 1; i >= 0; --i) {
            ((AdapterDataObserver)this.mObservers.get(i)).onItemRangeChanged(positionStart, itemCount, payload);
        }
    }

    public void notifyItemRangeInserted(int positionStart, int itemCount) {
        for(int i = this.mObservers.size() - 1; i >= 0; --i) {
            ((AdapterDataObserver)this.mObservers.get(i)).onItemRangeInserted(positionStart, itemCount);
        }
    }

    public void notifyItemRangeRemoved(int positionStart, int itemCount) {
        for(int i = this.mObservers.size() - 1; i >= 0; --i) {
            ((AdapterDataObserver)this.mObservers.get(i)).onItemRangeRemoved(positionStart, itemCount);
        }
    }

    public void notifyItemMoved(int fromPosition, int toPosition) {
        for(int i = this.mObservers.size() - 1; i >= 0; --i) {
            ((AdapterDataObserver)this.mObservers.get(i)).onItemRangeMoved(fromPosition, toPosition, 1);
        }
    }

这么比较起来,recycleView要比listview强大些。但是平时的工作中我们可以根据自己项目的需求选择控件,同时注意使用过程中减少不必要的资源浪费。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值