最近在项目中遇到过一个很棘手的问题,就是ListView在滑动后就莫名其妙的显示错乱,网上查阅资料后问题很容易的就解决了,但是对于问题产生的原因仍是一知半解,所以不甘心的我定下心来,狠读源码,终于理清了其中的”奥秘“。
由来一般的关于Adapter中getView的写法不外乎以下形式:
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mLayout.inflate(R.layout....); holder = new ViewHolder(); holder.textView = (TextView) convertView .findViewById(R.id.textview); ... ... convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.textView.setText(mText + position); return convertView; }在Android源码中关于getView方法的实现就是采用的以上形式,如ArrayAdapter等。因为这种写法的好处也是显而易见的,如果该position的convertview曾经被加载过,在数据集合未被改动的前提下,系统会自动将该position的convertview缓存起来,避免重复加载耗费资源。
然后问题就来了,当时我就”自作小聪明“,觉得当convertview==null时只是做了item布局的加载以及相关控件ID的绑定操作,为什么连内容的加载操作也放入其中呢,这样下次加载缓存是就省去内容set的操作了,然后就出现了滑动ListView后数据显示错位的问题-。-。
原因后来看源码发现,原来AbListView中获取getView()和滑动操作是异步进行的,其中滑动操作在一个FlingRunnable的支线程中运行,所以这就导致了在ListView在滑动时可能已经滑动到了第十行,但可能第二行的数据这时就被直接使用了,这就是导致数据加载错乱的根本原因。
附上源码中对FlingRunnable的注释:
所以唯一的解决方法就是只在convertview中缓存该ChildView的layout,但ChildView 中的数据必须每次都重新获取并加载。其实ListView数据加载及数据缓存是比较复杂的(几个相关的类加起来上完行=。=),所以以后有机会还是要好好研读源码,这样才能更加透彻的理解原理。