转载请标明出处: 

http://blog.csdn.net/yujun411522/article/details/46226001
本文出自:【yujun411522的博客】

android组件中,listview和gridview的使用非常的多,尤其是listview。所以重点讲解一下,由于两者之间区别不大,主要是外观的差别,所以重点讲解一下listview。先看一下ListView的继承结构:
这里ExpanableListView是继承ListView   
13.1 基本使用方法
   listview形式,在浏览网络,qq,微信等软件中非常常见。
     使用方法一般有两个点:1.绘制每一个Listview item中如何展示 ;2.为listview绑定数据,通过adapter来实现。
其中 1很简单,就是绘制一个xml文件,其中展示item应该如何显示,而且可以定义不同类型的xml文件用来显示。  
(在xml文件中有一个属性:设置view的 visibility时,有三个值:visible,invisible,gone。 三者的区别:只有visible时才是可见的。invisible不可见,但是保留该view控件所占用的空间;gone不可见且view控件不占空间。)
重点关注第二部分:如何给listview绑定数据:
我们首先来看它的继承结构
     
  说明了listview是一个AdapterView类型的控件,其中AdapterView是一个抽象类。
接下来要介绍适配器,适配器就是连接数据源和AdapterView的桥梁。通过它可以实现AdapterView和数据的有效分离,数据可以是Cursor,ArryList,数组等类型,AdapterView可以是其子类ListView和GridView
三者的关系:
     
其中Adapter充当中间人的角色,再看有哪些常用Adapter:
    常用的就是上面几个,其中BaseAdapter是上面的父类或者超类,所以只说BaseAdapter。
    在开发自己的Adapter时继承BaseAdapter即可,下面讲解其中重要的方法。
     需要重写的几个方法都是BaseAdapter的超类Adapter定义的方法。一个一个的看:
1.getCount():数据集中在这个Adapter中要展示多少个item,一般都是全部展示出来,可以简单理解为数据集的size。
getItem(int position):数据集中特定位置所关联的数据item,可以理解为一个数据集item数据对应一个listview中一个展示数据的item。
getItemId(int position):listview特定位置item的行号,一般就是position本身
      4 getView(int postion,View converView,ViewGroup parent):这个方法时最重要的,也是着重讲解的。绘制一个在特定item的view界面。一般是使用xml进行设置。关于第二个参数converView,下面会详细介绍。
     继承BaseAdapter时一般保存一个数据集。这里我们使用:private ArrayList<String> mData = new ArrayList<String>();
     重点讲解如何实现getView方法。
     如果不考虑使用android系统提供的缓存机制的话,很好处理,每一次都得到一个view对象,然后设置view中各个控件的值。
     @Override
public View getView( int position, View convertView, ViewGroup parent) {          
   View view = LayoutInflater.from( mContext).inflate(R.layout. listview_item , null );          
TextView index = (TextView)view.findViewById(R.id.my_music_listview_item_index );
TextView title = (TextView)view.findViewById(R.id.my_music_listview_item_title );          
index.setText( "" +(position+1));
title.setText( mData.get(position));          
Log. v( "MyListViewBase", "getView " + position + " ," + convertView+" ," );
return view;
}
     这么写,乍一看没有问题。但是如果数据非常多(上万条很常见),且item中有图片的话就会出现明显的卡顿,每一次getView都会分配一个View,这样内存消耗会很多。针对这个问题google在设置listview之初就已经想过这个问题了。
    下面有必要重点介绍一个第二个参数convertView的作用。
     13.2 convertview的使用原理
     在设计之初,android已经想好了这种情况的发生,所以在android系统之中提供了cache的机制。如果有一个item滚出可视区域,就会被加入到recycler(RecyclerBin,下面会介绍)之中,就有可能下次在getView时被重复使用。
       这里先不管到底convertView是如何产生的,需要明确的一点就是,如果converView!=null,那么它就是一个可以复用的view,这样我们就不再需要在getView中为每item建立一个新的view。
     典型写法:
if (convertView== null ){              
//一开始的创建时为空,因为recycler中就没有可以复用的view,这时需要建立新的view对象。
  convertView= LayoutInflater.from(mContext).inflate(R.layout. listview_item , null );
}
//如果converview是新建的或者是复用的,都要重新渲染view中的数据。          
TextView index = (TextView)convertView.findViewById(R.id. my_music_listview_item_index );
TextView title = (TextView)convertView.findViewById(R.id. my_music_listview_item_title );
index.setText( "" +(position));
title.setText( mData.get(position));        
return convertView;
 注意:对我们来说,需要的仅仅是view结构,而不是view界面中的每一个控件的展示的数据。所以实际上converView既复用了结构又复用了数据,如果之前view对象中某个textview显示的是“1”,再次复用时显示的也是"1",所以convertView不仅复用了view的结构,还复用了之前的数据。而对我们来说,我们仅仅是需要结构,数据则希望是重新设置的。
所以每一次都要重新设置view中的数据,这样才能对应上。
  但是如果这么写的话
if (convertView== null ){              
//一开始的创建时为空,因为recycler中就没有可以复用的view,这时需要建立新的view对象。
convertView= LayoutInflater.from( mContext).inflate(R.layout. listview_item , null );
//如果converview是新建的或者是复用的,都要重新渲染view中的数据。          
TextView index = (TextView)convertView.findViewById(R.id. my_music_listview_item_index );
TextView title = (TextView)convertView.findViewById(R.id. my_music_listview_item_title );
index.setText( "" +(position));
title.setText( mData.get(position));       

}             
return convertView;
这样写的错误是在于只有在convertView==null时才设置数据,convertView!=null时使用的没有更新,所以肯定有错误。
    可以看出不是按照原来设想的显示的:
     正确的方式是:
     if (convertView== null ){              
//一开始的创建时为空,因为recycler中就没有可以复用的view,这时需要建立新的view对象。
convertView= LayoutInflater.from( mContext).inflate(R.layout. listview_item , null );                              
}   
//不管converview是新建的或者是复用的,都要重新渲染view中的数据。
TextView index = (TextView)convertView.findViewById(R.id. my_music_listview_item_index );
TextView title = (TextView)convertView.findViewById(R.id. my_music_listview_item_title );
 index.setText( "" +(position));
           title.setText( mData.get(position));                 
return convertView;
     所以要根据具体position的值来重新设置复用view中相应控件的数据。    
    上面已经说过了,convertView的作用是复用view,避免数据量很大时创建大量的view。这只是在内存方面进行优化,可以避免产生过多不需要的view对象。还有一个问题需要我们注意:每一个得到一个view时都需要通过findViewById方法将view中相应的控件取出再设置显示,findViewById(有必要看一下)这个函数解析对应的xml布局文件,显然这是一个耗时的操作,因此google提出一个概念:ViewHolder
     ViewHolder,保存xml文件中各个子view的引用,这样每次就不用通过findViewById来查询了。
     上代码:
     @Override
public View getView( int position, View convertView, ViewGroup parent) {
Log. v( "MyListViewBase", "getView " + position + " ," + convertView);
ViewHolder holder;
if (convertView== null){                         
convertView= LayoutInflater.from( mContext).inflate(R.layout. listview_item , null );              
//通过这个模式,将view中的子view的引用保存起来,下次直接能获得viewHolder对象即可,避免重复调用findViewById方法。
//现在viewHolder就等价于view中的各个子view了。
holder = new ViewHolder();
 holder. index = (TextView)convertView.findViewById(R.id. my_music_listview_item_index);
              holder. title =(TextView)convertView.findViewById(R.id. my_music_listview_item_title);              
//将viewHolder绑定到view中,使得下次可以通过view获取viewHolder,也就可以操作复用view的子view了
convertView.setTag(holder);              
}
//如果是复用的view,直接取出复用view中的viewHolder,这样就可以直接通过viewHolder来访问子view了。
else {
  holder = (ViewHolder)convertView.getTag();              
}         
holder. index.setText( "" +(position));
holder. title.setText( mData.get(position));
return convertView;
}          
static class ViewHolder{
TextView index;
TextView title;
}
     这是一种用空间换时间(创建一个对象避免耗时的findViewById方法)的方法。通过新建一个view中的一个类型为viewholder的tag,避免每次调用findViewById这种耗时的操作。至于优化效果因view的复杂度而定,如果非常多的子view需要展示,这种方式还是可取的。
    
     12.3优化
     其实上面已经说到了关于listview优化的两种方式:
    * - It reuses the convertView passed to getView() to avoid inflating View when it is not necessary
 1 重复使用getView中传递过来的convertView,不要重复创建不必要的view对象(节省内存)* - It uses the ViewHolder pattern to avoid calling findViewById() when it is not necessary
2 使用ViewHolder模式,避免不必要的调用findViewById(空间换时间,view很复杂时效果提示明显;如果view不复杂,不一定提升明显)
     12.3.1数据混乱
     产生这个混乱根本原因就是listview中convertView的缓存机制。如果我们在getView中都是新创建一个view,也就不会带来数据混乱的问题,但是如果这么做了那就是本末倒置了。google推荐使用convertView这种机制。总体来说是瑕不掩瑜
     数据混乱经常出现的情形就是和缓存中拿出来的view中的某一个控件还保持着上一次的状态。比如说一个view中出现了一个checkBox,第一个行数据选中,我们下拉到第一次出现的position位置之后发现该checkBox也是选中的状态,这就是复用convertView带来的问题。一般的方案是维护一个数组,保存每一个checkbox的状态,点击该checkbox时将相应的状态更新到该数组。显示时则按照每一个checkbox的状态显示。
     CompoundButton.OnCheckedChangeListener changeListener = new CompoundButton.OnCheckedChangeListener() {            
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log. v("MyListViewBase",  "setOnCheckedChangeListener:" +position + " ," + isChecked);
mCheckStatusData.put(position, isChecked);   
}
};
holder.cb1.setOnCheckedChangeListener(changeListener);          
if(mCheckStatusData .get(position)){
holder. cb1.setChecked( true);              
else{
holder. cb1.setChecked( false);
}

     即可解决。
     12.3.2多个类型的viewType
     如果listview中的view显示不仅仅只有一种,比如说有三种显示效果,这个时候该怎么办?
android设计者已经考虑过这个问题了,所以在Adatper.java中定义了两个方法 :
1 int getViewTypeCount():返回viewType的种类数量,如果adapter中总是一样的type,那么应该返回1。实际上在BaseAdapter中,此方法的返回值就是1,表明默认情况下类型是一种。
       public int getViewTypeCount() {
return 1;
     2 int getItemViewType(int position): 位于position的item的类型。主要返回值在0-getViewTypeCount-1之间,否则报错ArrayIndexOutOfBoundsException,后面会有解释。
     下面介绍典型用法:
     这里有三个listview的item布局文件,对应的就是不同的viewType。重点看getItemViewType和getViewTypeCount方法:
    /**
      * 一共有多少个view类型
      * */
      private int viewTypeCount = 3;
      /**
      * 每一个 viewtype对应的值,便于getItemViewType函数返回
      * */
      private final int viewType1= 0;
      private final int viewType2=1;
      private final int viewType3=2;
    @Override
     public int getItemViewType(int position) {
          //一定要注意,这里的返回值在0至getViewTypeCount()-1之间。
           if (position % viewTypeCount == 0) {
               return viewType1 ;
          } else if (position % viewTypeCount == 1) {
               return viewType2 ;
          } else
               return viewType3 ;
     }
     @Override
     public int getViewTypeCount() {
           return viewTypeCount ;
     } 
   定义了三种viewType,就需要三种ViewHolder:
     //这里只做演示,所以每一个view中的子view差别不大。
     class ViewHolder1 {
          TextView index;
          TextView title;
     }
     class ViewHolder2 {
          TextView index;
          TextView title;
     }
     class ViewHolder3 {
          TextView index;
          TextView title;
     }
    关键的部分就是getView方法的实现了:
其实和前面一样,分成三个部分:
convertView =null 时利用viewHolder将view保存起来并通过view.setTag绑定到convertview中
convertView !=nul 通过convertView的getTag(),将viewHolder取出来
3 设置viewHolder中的数据
这里复杂在于viewHolder有多个,所以每次都要判断一下类型。
    public View getView(int position,View converView,ViewGroup parent){
          ViewHolder1 holder1 = null;
          ViewHolder2 holder2 = null ;
          ViewHolder3 holder3 = null ;
           // 返回此view是哪个viewType
           int viewType = getItemViewType(position);
           if (convertView == null) {
               switch (viewType) {
              caseviewType1 :
                   convertView = LayoutInflater.from( mContext).inflate( R.layout. listview_item1 , null );
                    // 通过这个模 式,将view中的子view的引用保存起来,下次直接能获得viewHolder对象即可,避免重复调用findViewById方法。
                    // 现在viewHolder就等价于view中的各个子view了。
                   holder1 = new ViewHolder1();
                   holder1. index = (TextView) convertView.findViewById(R.id. my_music_listview_item_index );
                   holder1. title = (TextView) convertView.findViewById(R.id. my_music_listview_item_title );
                   convertView.setTag(holder1);
                    break ;
              case viewType2:
                   convertView = LayoutInflater.from( mContext).inflate(R.layout. listview_item2 , null );
                   holder2 = new ViewHolder2();
                   holder2. index = (TextView) convertView.findViewById(R.id. my_music_listview_item_index );
                   holder2. title = (TextView) convertView.findViewById(R.id. my_music_listview_item_title );
                   convertView.setTag(holder2);
                    break ;
              case viewType3:
                   convertView = LayoutInflater.from( mContext).inflate(R.layout. listview_item3 , null );
                   holder3 = new ViewHolder3();
                   holder3. index = (TextView) convertView.findViewById(R.id. my_music_listview_item_index );
                   holder3. title = (TextView) convertView.findViewById(R.id. my_music_listview_item_title );
                   convertView.setTag(holder3);
                    break ;
              }
          }
           // 如果是复用的view,直接取出复用view中的viewHolder,这样就可以直接通过viewHolder来访问子view了。
           else {
               switch (viewType) {
              case viewType1:
                   holder1 = (ViewHolder1) convertView.getTag();
                    break ;
              case viewType2 :
                   holder2 = (ViewHolder2) convertView.getTag();
                    break ;
              case viewType3:
                   holder3 = (ViewHolder3) convertView.getTag();
                    break ;
              }
          }
          switch(viewType) {
          caseviewType1 :
              holder1. index.setText( "" + (position));
              holder1. title.setText( mData.get(position));
               break ;
          caseviewType2 :
              holder2. index.setText( "" + (position));
              holder2. title.setText( mData.get(position));
               break ;
          caseviewType3 :
              holder3. index.setText( "" + (position));
              holder3. title.setText( mData.get(position));
               break ;
          }
           return convertView;  
}
    效果:
刚才说,getItemViewType的值要在0至getViewTypeCount-1之间,当不符合这个要求的时候会发生什么呢?将viewType3的值从2改为3时
 一开始时没有出错的,当我们往下滑的时候就出现了错误(我的华为荣耀3c畅玩版没有出现问题系统是android 4.2.2,htc 4.0出现了问题,模拟器2.3的也出现了问题,我猜测应该是版本问题):
java.lang.ArrayIndexOutOfBoundsException:
at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:4540)
 这个问题一开始出来的时候还真是奇怪,怎么回出现数组越界的异常呢,在stackoverflow(http://stackoverflow.com/questions/25749486/android-arrayindexoutofboundsexception-and-abslistviewrecyclebin-addscrapview/25750609#25750609)中看到了这个解释:
ListView.java   
public void setAdapter(ListAdapter apater){         
...
//调用AbsListView.RecycleBin中的setViewTypeCount方法
 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());   mAdapter.getViewTypeCount()=3  
...
}  
AbsListView.RecycleBin
public void setViewTypeCount( int viewTypeCount) {//viewTypeCount=3
if (viewTypeCount < 1) {
throw new IllegalArgumentException( "Can't have a viewTypeCount < 1");
}
//noinspection unchecked
//创建了一个长度为3的ArrayList<View>[]类型的数组
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
AbsListView.RecycleBin.java
void addScrapView(View scrap,int position){
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null ) {
return ;
}
// Don't put header or footer views or views that should be ignored
// into the scrap heap
int viewType = lp.viewType ;         
if (mViewTypeCount == 1) {
scrap.dispatchStartTemporaryDetach();
mCurrentScrap .add(scrap);
} else {
scrap.dispatchStartTemporaryDetach();
正是这一句,导致了异常的发生。
mScrapViews [viewType].add(scrap);//mScrapViews[3]肯定是数字越界了(0-2才是合法的) 
}
}
    12.3.3 替代者recyclerview(后续更新)
   
    12.4 涉及到的观察者模式
     简要介绍一下观察者模式
     观察者模式是对象的行为模式,又称发布-订阅模式(publish/subscribe)、源-监听器模式(source/listener)。简单来说就是定义了一种一对多的依赖关系,其中多个观察者对象同时监听一个主题。如果主题状态发生变化,则通知所有的观察者执行相应的操作。
     其中涉及到的角色有四类:
     
 1subject:也成抽象主题,其中维护多个observer(多用Arraylist),提供两个接口,attach()添加一个observer,detach删除一个observer。
observer:也称为抽象观察者,用来观察主题,提供一个更新接口,当主题通知自己时,调用该方法。
concreteSubject:具体主题, 保存一个状态信息,当内部状态发生变化时向所有注册过的observer发出通知。
concreteobserver:具体观察者,存储主题的状态,并实现observer中的接口。
     具体代码:
     Subject.java
     public abstract class Subject {
/**
* 保存所有的observer
* */
private ArrayList<Observer> observers = new ArrayList<Observer>();
/**
* 为subject添加一个observer
* */
public void attach(Observer observer) {          
observers.add(observer);
System. out.println(observer+" is attached" );
}
/**
* 为subject删除一个observer    
* */
public void detach(Observer observer) {          
observers.remove(observer);
System. out.println(observer+" is detached" );
}
public void notifyObserver(String state) {
for (Observer temp : observers ) {
temp.update(state);
}
}
}
Observer.java
public interface Observer {     
/**
* 定义一个更新自己的方法,当subject发送消息时执行此方法
* @param state 主题的状态变化
* */     
public void update(String state);
}
ConcreteSubject.java
public class ConcreteSubject extends Subject {
private String state;
public String getState() {
return state ;
}
public void change(String newState){
state = newState;
System. out.println("主题新状态:" +state );
//状态发生了变化,通知所有观察者更新
super.notifyObserver(state );
}
}
ConcreteObserver.java

public class ConcreteObserver implements Observer {
/**
* 观察者的状态
* */
private String observerState;
/**
* 具体的update方法
* */
@Override
public void update(String state) {          
//记录主题的状态
observerState = state;          
System. out.println(this +" update itself" );
}
}
public static void main(String[] args) {
ConcreteSubject concreteSubject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver();
ConcreteObserver observer2 = new ConcreteObserver();
ConcreteObserver observer3 = new ConcreteObserver();
ConcreteObserver observer4 = new ConcreteObserver();
//一个subject可以注册多个observer
concreteSubject.attach(observer1);
concreteSubject.attach(observer2);
concreteSubject.attach(observer3);
concreteSubject.attach(observer4);
concreteSubject.notifyObserver( "新状态");
}     
     再看listView中出现观察者模式。
先看subject 抽象主题:Observable<T>.java
里面实现了注册和反注册observer。
     /**

* Provides methods for (un)registering arbitrary observers in an ArrayList.
          */
     public abstract class Observable<T> {
    /**
     * The list of observers.  An observer can be in the list at most once and will never be null.
     */
    protected final ArrayList<T> mObservers = new ArrayList<T>();
    /**
     * Adds an observer to the list. The observer cannot be null and it must not already be registered.
     * @param observer the observer to register
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is already registered
     */
    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

/**
     * Removes a previously registered observer. The observer must not be null and it must already have been registered.
     * @param observer the observer to unregister
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is not yet registered
     */
    public void unregisterObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            int index = mObservers.indexOf(observer);
            if (index == -1) {
                throw new IllegalStateException("Observer " + observer + " was not registered.");
            }
            mObservers.remove(index);
        }
    }   
    /**
     * Remove all registered observer
     */
    public void unregisterAll() {
        synchronized(mObservers) {
            mObservers.clear();
        }       
    }
}

     再看Observer:观察者:DataSetObserver.java
     定义了两种操作,一个是数据集发生变化时的onChange,一个整个数据集要无效时的onInvalidate
     publicabstractclassDataSetObserver{
/**
* This method is called when the entire data set has changed,
* most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
*/
public void onChanged() {
// Do nothing
}
/**
* This method is called when the entire data becomes invalid,
* most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
* {@link Cursor} .
*/
public void onInvalidated() {
// Do nothing
}
}
     
具体主题:DataSetObservable.java
实现了两个方法,一个是通知数据集发送变化时的notifyChanged,一个数据集变得无效的notifyInvalidate
/**
* A specialization of Observable for DataSetObserver that provides methods for
* invoking the various callback methods of DataSetObserver.
*/
public class DataSetObservable extends Observable<DataSetObserver> {

// Invokes onChanged on each observer. Called when the data set being observed has changed, and which when read contains the new state of the data.
    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 onInvalidated on each observer. Called when the data set being monitored has changed such that it is no longer valid.
    public void notifyInvalidated() {
        synchronized (mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onInvalidated();
            }
        }
    }
}

具体观察者:AlphabetIndexer
  public class AlphabetIndexer extends DataSetObserver{
    @Override
    public void onChanged() {
        //观察到数据变化,观察者做自己该做的事情
        super.onChanged();
        mAlphaMap.clear();
    }
}
具体观察者:AdapterView.AdapterDataSetObserver以及AbsListView.AdapterDataSetObserver
AdapterView.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();
        }        
    }

AbsListView.AdapterDataSetObserver
    class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            if (mFastScroller != null) {
                mFastScroller.onSectionsChanged();
            }
        }

@Override
        public void onInvalidated() {
            super.onInvalidated();
            if (mFastScroller != null) {
                mFastScroller.onSectionsChanged();
            }
        }
    }

     
在BaseAdapter中维护了一个DataSetObserable对象,利用DataSetObserable实现了registerDataSetObserver和unregisterDataSetObserver方法(这两个方法在Adapter接口中定义)。当数据集发生变化时可以调用notifyDataSetChanged方法更新数据。这里实现的时候,BaseAdapter才是具体主题,Adapter才是主题
但是要注意一个问题,BaseAdapter并没有显示的调用registerDataSetObserver方法注册一个Observer,那么是如何实现Observer更新的呢?
     如果我们没有显示的调用,那么一定是系统在背地里面调用了,是的。就是在为listview设置Adapter时BaseAdapter自己调动的:
     ListView.setAdapter(ListAdapter)
     @Override

public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

...
        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }          
        super.setAdapter(adapter);

if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();

// 在这里为BaseAdapter注册一个AdapterDataSetObserver 对象。

所以可以通过notifyDataSetChanged中的方法操作
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);
          ..
            }
        } else {
           ...
        }
       
    }
 其中调用了registerDataSetObserver方法:
BaseAdapter中registerDataSetObserver方法
  public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable .registerObserver(observer);
}
   再看BaseAdapter中的notifyDataSetChanged方法
public void notifyDataSetChanged() {

mDataSetObservable .notifyChanged();
}

12.5 AbsListView.RecycleBin     
      AbsListView类中定义了一个RecycleBin的成员变量:
      // The data set used to store unused views that should be reused during the next layout to avoid creating new ones
        final RecycleBin mRecycler = new RecycleBin();//用来存储不再使用且可以重用的的view,避免下一个layout时重新创建一个新的。
     ListView 中的这种缓存机制的实现方式主要是通过ABSListView.RecycleBin类来实现的,具体分析一下该类。
     先看类说明:
      The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the start of a layout. By construction, they are displaying current information. At the end of layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that could potentially be used by the adapter to avoid allocating views unnecessarily.
     recyclebin方便了view的重用,有两级存储:ActiveViewScrapView。ActiveViews是屏幕上可见的view集合,ScrapViews则是可以被重用的旧view,避免没有必要的分配view。
     12.5.1 重要成员变量
private View[] mActiveViews=new View[0]:
       // Views that were on screen at the start of layout. This array is populated at the start of layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
    //Views in mActiveViews represent a contiguous range of Views, with position of the first view store in mFirstActivePosition.
        //一开始layout时在屏幕上的views,结束layout之后所有的view会移动到mScrapViews中。   
      
private ArrayList<View>[] mScrapViews ;
      // Unsorted views that can be used by the adapter as a convert view.
可以被用来当做convertView的view集合,注意,这里使用的是ArrayList<View>的数组。因为这里可以有多个不同viewType的view,每一种类的view用一个ArrayList保存。
private int mViewTypeCount://view的种类,前面已经介绍过了
private ArrayList<View> mCurrentScrap://一个ArrayList,主要是为了方便当mViewTypeCount==1时的操作,当mViewTypeCount==1时,mCurrentScarp=mScrapViews[0]
 private int mFirstActivePosition:mActiveViews中第一个view的位置。
     重要方法:
     1.public void setViewTypeCount(int viewTypeCount):
    public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
//根据viewTypeCount的值,将mViewTypeCount初始化为长度为viewTypeCount的ArrayList数组
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
       2. public void markChildrenDirty() 
       public void markChildrenDirty() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap ;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
scrap.get(i).forceLayout();
}
} else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews [i];
final int scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {
//mScrapViews 中的view.forceLayout(),forceLayout此方法只是做一个标示
scrap.get(j).forceLayout();
}
}
}
}
     调用的forceLayout()方法,此方法只是做了一个标志,设置该view为FORCE_LAYOUT 
    public void forceLayout() {
mPrivateFlags |= FORCE_LAYOUT ;
mPrivateFlags |= INVALIDATED ;
}
     3. clear()  
    /**
         * Clears the scrap heap.
         */
         //调用将mScrapViews 中所有的view清除,并调用removeDetachedView方法回收
void clear() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap ;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
removeDetachedView(scrap.remove(scrapCount - 1 - i), false );
}
} else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews [i];
final int scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {                         
removeDetachedView(scrap.remove(scrapCount - 1 - j), false );
}
}
}
}
4.fillActiveViews(int childCount, int firstActivePosition)       
         // Fill ActiveViews with all of the children of the AbsListView. 
       用ABSListView的所有child填充ActiveViews       
        void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews .length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;
final View[] activeViews = mActiveViews ;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
// Don't put header or footer views into the scrap heap
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER ) {
// Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
//        However, we will NOT place them into scrap views.
 activeViews[i] = child;// 用ABSListView的所有child填充ActiveViews       
}
}
}
     5. View getActiveView( int position)
      //Get the view corresponding to the specified position. The view will be removed from mActiveViews if it is found.
根据position返回mActiveViews中的view,如果找到,将该view从mActiveViews中移除。如果没找到return null
View getActiveView( int position) {
int index = position - mFirstActivePosition ;
final View[] activeViews = mActiveViews ;
if (index >=0 && index < activeViews. length) {
final View match = activeViews[index];
activeViews[index] = null ;//将该view从mActiveViews中移除,对应的位置设为null
returnmatch;
}
returnnull;
}
     6.View getScrapView( int position) 
// @return A view from the ScrapViews collection. These are unordered.
View getScrapView( int position) {
if (mViewTypeCount == 1) {
return retrieveFromScrap( mCurrentScrap , position);
} else {
int whichScrap = mAdapter .getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews .length ) {
return retrieveFromScrap( mScrapViews[whichScrap], position);
}
}
returnnull;
}
主要是调用retrieveFromScrap方法:
     7.View retrieveFromScrap(ArrayList<View> scrapViews, int position)
 static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
int size = scrapViews.size();
if (size > 0) {
// See if we still have a view for this position.
for (int i=0; i<size; i++) {
View view = scrapViews.get(i);
if (((AbsListView.LayoutParams)view.getLayoutParams()). scrappedFromPosition == position) {
scrapViews.remove(i);
// 一种情况:如果在mScrapViews中一个scarppedFromPosition和position一样返回的view,返回且从mScrapViews中删除该view。
return view;
}
}
// 第二种情况:如果在mScrapViews中找不到scarppedFromPosition==position的view,返回所在type的ArrayList最后一个View并删除该view
return scrapViews.remove(size - 1);
} else {
// 第三种情况:如果该type 的scrapView size为0,返回null。
return null ;
}
}

返回的三种情况:
1 view.scarppedFromPosition==position ,返回该view     
2 如果没有找到这样的view,返回该类型的最后一个view
3 如果没有该类型没有缓存,返回null
分别对应的情形:
先看第三种最简答:一开始listview显示时没有view滑出屏幕,这是mScarpViews也没有view。当我们向上移动一小段距离(第一个view不要滚出,同时下面的有新的view要显示出来),这时调用getView方法,这是的convertView就是null,需要我们自己创建view。
再看第二种:上面一种情况下继续上移,直到第一个view完全滚出,这是view被加入该类型的缓存ArrayList中。再往下移动直到又出来一个新的view,这是遍历该类型的缓存Arraylist,但是肯定没有view.scarppedFromPosition==position的,因为缓存的view的scarppedFromPosition==0,而新出来的view的position肯定不是0。这时返回该ArrayList最后一个view(也就是唯一的一个view),之后传值给convertView,这时convertView!=null。
最后看第一种情况:如果第一个view加入到该类型的ArrayList缓存之后,且没有新的item出现,这时再往滑动直到第一个view出现,这时找到了
scarppedFromPosition==position的view,返回即可。之后传值给convertView,convertView!=null。
8. 再看addScrapView方法
void addScrapView(View scrap, int position) {
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
..         
int viewType = lp.viewType ;
//将position赋值给 scrappedFromPosition属性
lp.scrappedFromPosition= position;
if (mViewTypeCount == 1) {
scrap.dispatchStartTemporaryDetach();
mCurrentScrap .add(scrap);
} else {
scrap.dispatchStartTemporaryDetach();
//添加到该type的ArrayList缓存数据中。
//这个地方也是之前发生数组越界的根源。
mScrapViews [viewType].add(scrap);
}           
}
上面介绍了RecycleBin中的主要方法和成员变量,那它在ABsListView中如果使用的呢?
 listView中有一个方法layoutChildren(),这个方法在会在ListView中多次调用
protected void 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);
}
}
如果数据变化了加入到scrapView中,否则加入到activeView中。
     我们还知道,在我们上下移动屏幕的时候可能发生时item的回收和复用,那么在哪里将不可见的view加入到scrapView中呢?
就在ListView中scrollListItemBy方法
private void scrollListItemsBy( int amount) {
final int listBottom = getHeight() - mListPadding.bottom;
final int listTop = mListPadding.top;
final AbsListView.RecycleBin recycleBin = mRecycler;
if (amount < 0) {
..
// top views may be panned off screen
View first = getChildAt(0);
while (first.getBottom() < listTop) {
//顶端item滚出屏幕时加入到scrapView中
AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
detachViewFromParent(first);
recycleBin.addScrapView(first, mFirstPosition);
} else {
removeViewInLayout(first);
}
first = getChildAt(0);
mFirstPosition++;
}
} else {         
// bottom view may be panned off screen
while (last.getTop() > listBottom) {
//底端item滚出屏幕时加入到scrapView中
AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
detachViewFromParent(last);
recycleBin.addScrapView(last, mFirstPosition+lastIndex);
} else {
removeViewInLayout(last);
}
last = getChildAt(--lastIndex);
}
}
}
 listview中另一个方法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) {               
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);        
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
  如果在activeView中找到,在直接返回,否则调用父类(AbsListView)的obtainView方法
再看obtainView方法
     // Get a view and have it show the data associated with the specified position. This is called when we have already discovered that the view is not available for reuse in the recycle bin. The only choices left are
    // converting an old view or making a new one.  
View obtainView( int position, boolean [] isScrap) {
isScrap[0] = false ;
View scrapView;
scrapView = mRecycler .getScrapView(position);
View child;
if (scrapView != null) {
...
//  如果从缓存view中取出来的view不为空,说可以拿到一个可以复用的view:scrapView 
这时convertView的值就是scrapView 
child = mAdapter.getView(position, scrapView, this );
..
if (child != scrapView) {
// 如果getView中convertView的值与getView的返回值不一样,依然将scrapView加入scrapView中
mRecycler .addScrapView(scrapView, position);
..               
} else {
getView中convertView的值与getView的返回值一样,(convertView!=null我们通常都是返回convertView),设置 isScrap[0] = true ;
 isScrap[0] = true ;
child.dispatchFinishTemporaryDetach();
}
} else {
如果在scarpView中没有找到可回收的,则convertView ==null,这时需要自己创建view。
child = mAdapter.getView(position, null , this );
}
return child;
}       
 在这里可以看出,如果在缓存可以找到一个可用的scrapView ,则convertView==scrapView,否则convertView==null。
     12.5分批加载
     在ListView中分批加载的使用场景很多,比如头条新闻上面看新闻,一般先显示一定数目(比如20条)的新闻条目,等到用户滑到20条的时候自动加载新的新闻信息,这个时候新闻变成了40条。这种方式也称为分页加载。每一次一页的数据加载完之后就会自动加载下一页的数据,避免一开始就加载很多数据,有时用户不一定全部都会看。
     下面来看怎么实现:
     先来介绍一个一个接口:AbsListView.OnScrollListener,这个接口主要用来监听listView或者gridView在滚动时的一些操作,定义了三个状态和两个方法:
       public interface OnScrollListener {
/**
* The view is not scrolling.
*/
//view此时没有滚动
public static int SCROLL_STATE_IDLE = 0;
/**
* The user is scrolling using touch, and their finger is still on the screen
*/
//view在滚动,且手指在还在屏幕上
public static int SCROLL_STATE_TOUCH_SCROLL = 1;
/**
* The user had previously been scrolling using touch and had performed a fling.
*/
//view在滚动,但是手指在还在屏幕。用力一划,迅速离开屏幕
public static int SCROLL_STATE_FLING = 2;
/**
* Callback method to be invoked while the list view or grid view is being scrolled. If the
* view is being scrolled, this method will be called before the next frame of the scroll is
* rendered. In particular, it will be called before any calls to
* {@link Adapter#getView(int, View, ViewGroup)} .
*
* @param view The view whose scroll state is being reported
//是哪一个View的scrollState发生了变化
*
* @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE} ,
* {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
*/该view对应的scrollState
//view的ScrollState状态发生变化时调用。在getView方法之前调用
public void onScrollStateChanged(AbsListView view, int scrollState);
/**
* @param view The view whose scroll state is being reported//同上               
* @param firstVisibleItem the index of the first visible cell // 第一个可见的Item的index
* @param visibleItemCount the number of visible cells//屏幕上可见的item个数
* @param totalItemCount the number of items in the list adaptor//adapter中的item数量,getCount值
*/
//view在Scroll调用
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount);
}
     我们如何判断已经滑到底端了:firstVisibleItem+visibleItemCount==totalItemCount

     下面是代码:
1.定义一个ABsListView.OnScrollListener
     private OnScrollListener onScrollListener = new OnScrollListener() {          
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) { }
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (view.getId() == R.id.lv1) {
//滑到底端时开启一个异步任务加载数据..
if(firstVisibleItem+visibleItemCount==totalItemCount){
new LoadMoreDataTask().execute();
}
}
}
};
     2.为listView设置OnScrollLisener:listview.setOnScrollListener(onScrollListener)
     3.异步加载数据,这里采用的是AsyncTask:
     class LoadMoreDataTask extends AsyncTask<Void, Void, Void> {
@Override
protected void onPreExecute() {
progressDialog.show();//给用户提示正在加载数据
}
@Override
protected Void doInBackground(Void... params) {
getMoreData();
try {
Thread. sleep(2000);//模拟耗时操作
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null ;
}
@Override
protected void onPostExecute(Void result) {
if (progressDialog != null) {
progressDialog.dismiss();
}
//更新adapter数据源,并提示数据集发生变化,观察者更新数据
adapter.setData(data);
adapter.notifyDataSetChanged();
}
}
运行结果:     
     到199条记录的是已经滑到底端了,这时加载新的数据,给用户提示,更新完毕后是这样:
     

13.ListView和GridView相关推荐

  1. Windows8 Metro应用开发之C#(3)- 数据控件ListView、GridView、FlipView

    Windows8 Metro应用开发之C#(3)- 数据控件ListView.GridView.FlipView 介绍: 本节主要对Windows 8 中对于Metro应用开发提供的数据控件ListV ...

  2. ListView和GridView的一些杂七八杂的内容

    2019独角兽企业重金招聘Python工程师标准>>> 今天加了一天班,算是做编程以来的第一次,也是正式工作以来最心甘情愿的一次,希望能开启勤奋模式,努力学习! 最近公司在做andr ...

  3. android listView嵌套gridview的使用心得

    在开发的过程中可能需要用到listview嵌套gridview的场景,但是在Android中, 不能在一个拥有Scrollbar的组件中嵌入另一个拥有Scrollbar的组件,因为这不科学,会混淆滑动 ...

  4. Flutter中用ListView嵌套GridView报错异常

    Flutter中用ListView嵌套GridView报错异常 参考文章: (1)Flutter中用ListView嵌套GridView报错异常 (2)https://www.cnblogs.com/ ...

  5. wp8.1 Study7: ListView 和GridView应用

    对于列表控件,WP8.1常用的是ListView.GridView.ListBox控件.其中前两个是从第三个继承来的. 1.ListView控件 它是展示垂直列表的,如下图所示.它十分适合展示数据. ...

  6. Android SimpleAdapter显示ListView、GridView

    SimpleAdapter作为一个数据集,主要向ListView.GridView容器填充数据,总结了几种填充方式,原理很简单,直接看代码和效果图 1. ListView单行显示(simple_lis ...

  7. 如何让listview或者gridview设置到底刷新新数据

    昨天简单的看了一下ScrollView的例子,虽然可以实现拉到底触发监听,但是Listview和gridview本身就自带了下拉属性,所以不需要ScrollView来实现. 那么在Listview和g ...

  8. ListView和GridView的缓存机制及measure过程

    目录 前言 1.View的Transient状态 2.RecycleBin 3.obtainView 4.getView的调用 5.GridView的onMeasure 6.ListView的onMe ...

  9. ScrollView内嵌ListView或GridView的滑动处理

    开发过程中经常会遇到使用scrollview嵌套listview或gridview的情况,这时由于scrollview拦截消费了滑动事件,所以在listview或gridview区域滑动时该区域无法滑 ...

最新文章

  1. 我眼中未来的计算机作文600字,我的电脑作文600字
  2. Linux操作系统下/etc/hosts文件配置方法
  3. Windbg程序调试--转载
  4. 在java程序中使用protobuf
  5. 【Android开发—智能家居系列】(二):用手机对WIFI模块进行配置
  6. 美检方或起诉刘强东;百度对呛微信规则;澳洲调查 Facebook | 极客头条
  7. hashMap的快速存取
  8. 如何设置Mac允许远程电脑SSH登录?
  9. selenium+python环境搭建
  10. java程序设计教程pdf_Java程序设计入门教程pdf
  11. 大数据技术如何影响企业决策?
  12. C语言/C++常见习题问答集锦(十九)之C语言与漫天飞雪
  13. 你连《Android高级UI与FrameWork源码》都搞不懂学什么Android?还敢面试阿里P7!
  14. 用AR Engine手部骨骼跟踪能力实现虚拟手表试戴
  15. 宏基因组分析-基于组装
  16. 从共射极放大电路到选频放大电路到相位测距上的应用
  17. (资讯)华为员工利用Bug越权访问机密卖给第三方,获利1.6万元,被判有期徒刑一年
  18. H3C防火墙基础配置3-配置对象策略
  19. [Dxperience.8.*]报表预览控件PrintControl设置
  20. AutoCAD 2012最新特性概览

热门文章

  1. Adobe XD 连不上网用不了插件的解决办法
  2. 成功解决:java.util.LinkedHashMap cannot be cast to com.zyz.bookshopmanage.pojo.GoodsInfo
  3. 收藏:安卓投屏大师TC Display直播常见问题详细解决方案
  4. Web开发人员需知的Web缓存知识
  5. MyBatis choose、when和otherwise标签简介说明
  6. jsp+ssm+mysql综合项目实战教程:开发通用进销存管理系统
  7. 【LabVIEW懒人系列教程-小白入门】1.2LabVIEW前面板控件讲解
  8. Verilog IIC AT24C128和LM75
  9. 每一个合格的家庭主妇都是生产厂长的有力竞争者——ERP库存管理pandas代码示例(面试题)
  10. 关于快速测试API接口的一个新技能