研究ListView里的FastScroller的一点心得

这几天用到ListView的FastScroll的功能,没想到啊居然有Bug(BTW: 经过我的研究, 这个问题在android 4.0版本里已经解决了,因此只考虑在4.0以上环境下工作的人可以无视我这篇文章了。其实我没有验证是不是4.0才改的,但反正2.X不行,4.0可以。下面我都会说是4.0怎么样怎么样,可能不一定准确)!滚动的那个倒是正常,就是中间显示的那个当前联系人首字母提示的不正常~!我Curosr都换了,结果那些首字母还是不变。再滚动的时候就跟当前的不匹配了。啊啊怎么会有这种问题啊,逼我这种懒人去研究源码啊研究的郁闷啊有木有啊?!

好了咆哮完了,进入正题。先放个图上来:



这个东东就是快速滚动了,主要由图里右边那个滚动按钮和中间这个字母提示组成。要想使用到这个功能也挺简单的,

首先在layout中给ListView设置android:fastScrollEnabled="true" ,代码里对应的方法是setFastScrollEnabled(boolean b)。然后让你的ListAdaper实现SectionIndexer接口。运行后对List进行滚动时就可出现类似上图的效果。 不过要提醒一下,List的数据不够多时是不会出现FastScroller的,就算按上面设置了实现了也不会有。貌似是要2屏以上的数据,在滚动时才会出现FastScroller。

哎看起来是挺好的,可惜呀,当我切换数据的时候那个FastScroller却死活也刷新不了。我@$$%^#$%@,。。。研究完了源码以后断定是源码的问题,因为它只在初始化的时候获取了这个Section(就是中间显示的那些个字母)的数据~!!拜托工作中的应用是经常要给List更新数据的呀,怎么设计成这样啊。。。太挫了。哎百度谷歌一下看有什么解决办法没,悲剧了,发现有关这个问题的现成资料并不多,只好自己研究了。下面是讲源码的,源码完了是讲我的曲线救国的解决方案的。不关心源码的可直接跳去解决方案。


源码篇:

先扔两个查看源码的链接

http://www.oschina.net/code/explore/android-2.2-froyo/android/widget/FastScroller.java

http://www.oschina.net/code/explore/android-2.2-froyo/android/widget/AbsListView.java

ListView中的快速滑动其实是它由的父类AbsListView来处理的,在这个类里保存了一个FastScroller类型的变量,FastScroller类封装了快速滑动的所有逻辑, 而AbsListView主要是创建FastScroller类型的实例并传给它一些数据和事件。让我们来看看这个FastScrolle.java,因为这里我主要关心的是中间那个当前section的显示和刷新,因此会围绕这个来讲。

private Object [] mSections;  //存放Section数据,对于我的联系人应用来说,就是一堆首字母。

private String mSectionText; //存放当前要显示的section文本。

private SectionIndexer mSectionIndexer; //这个其实是我们的实现了SectionIndexer的Adapter。

FastScroller实例被创建的时候会调用init(Context context)函数。然后在这个函数里会去调用getSectionsFromIndexer()函数。这个函数就是从我们的Adapter里获取那些section数据并保存到自己的变量中的。当然了,悲催的是它就只调用了这么一次,因此哪怕你的Adapter更新数据了,它也不会更新section.(代码中还有另一个地方会调用,但是我表示判断语句的那种情况真的制造不出来,因此就当没有好了)。

void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,//这是很重要的一个函数。如果你没有得到想要的效果,来这里找原因。

至于说4.0是怎么解决的,基本就是在AbsListView增加了一个AdapterDataSetObserver,当数据变化时调用FastScroller的onSectionsChanged()函数(这货也是新增的)。因此实现了数据更新时同步更新FastScroller的数据。感兴趣的朋友可以去看4.0的代码。


解决篇:

先放送一个链接,我是从这里得到别人的启发的。

http://stackoverflow.com/questions/3898749/re-index-refresh-a-sectionindexer

思路就是用一个由一定数量的对象(重要!)组成的固定长度的数组,FastScroller会在初始化的时候把这个数组获取过去。当我们的数据刷新时,就去刷这个数组里的数据,先贴代码吧。

	private SectionTitle[] mSectionDatas = new SectionTitle[27];
	
	for(int i = 0; i < 27; i++){
		mSectionDatas[i] = new SectionTitle();
		mSectionDatas[i].title = "#";
	}

	/**
	 * Process cursor to extract useful information into some data structure
	 */
	private ContactSectionMapper processCursor(Cursor c) {
		if (c == null || c.getCount() == 0 || c.isClosed())
			return null;

		/** define some variables */
		SparseIntArray sectionPositionMap = new SparseIntArray();
		SparseIntArray positionSectionMap = new SparseIntArray();

		for(int i = 0; i < mSectionDatas.length; i++){
			mSectionDatas[i].title = "";
		}
		
		String curtitle = "";

		int i = 0;
		int position = 0;
		while (c.moveToNext()) {
			position = c.getPosition();

			String curLetter = getTitle(getDisplayName(c));

			/** check if it is needed to insert a section title */
			if (TextUtils.isEmpty(curtitle) || !TextUtils.equals(curLetter, curtitle)) {
				mSectionDatas[i].title = curLetter;
				sectionPositionMap.put(i, position);
				curtitle = curLetter;
				i++;
			}
			positionSectionMap.put(position, i - 1);
		}

		for(; i < mSectionDatas.length; i++){
			mSectionDatas[i].title = curtitle;
			sectionPositionMap.put(i, position);
		}
		ContactSectionMapper sectionMapper = new ContactSectionMapper(mSectionDatas,
				sectionPositionMap, positionSectionMap);
		return sectionMapper;
	}

SectionTitle类:

    private class SectionTitle{
    	public String title;
    	
    	public String toString(){
    		return title;
    	}
    }
    
Adapter:

	protected final class ContactsAdapter extends BaseContactsAdapter implements SectionIndexer{

		private ContactSectionMapper mSectionMapper;

		@Override
		public Object[] getSections() {
			return mSectionMapper.getSections();
		}

		@Override
		public int getPositionForSection(int section) {
			return mSectionMapper.getPositionForSection(section);
		}

		@Override
		public int getSectionForPosition(int position) {
			return mSectionMapper.getSectionForPosition(position);
		}
	}

ContactSectionMapper是一个实现了SectionIndexer的内部类

	private class ContactSectionMapper implements SectionIndexer {

		private final SparseIntArray mSectionPositionMap;
		private final SparseIntArray mPositionSectionMap;

		public ContactSectionMapper(SparseIntArray sectionPositionMap,
				SparseIntArray positionSectionMap) {
			mSectionPositionMap = sectionPositionMap;
			mPositionSectionMap = positionSectionMap;
		}
		
		@Override
		public Object[] getSections() {
			return mSectionDatas;
		}

		@Override
		public int getPositionForSection(int section) {
			if (mSectionPositionMap == null)
				return -1;

			return mSectionPositionMap.get(section, -1);
		}

		@Override
		public int getSectionForPosition(int position) {
			if (mPositionSectionMap == null)
				return -1;

			return mPositionSectionMap.get(position, -1);
		}

		public boolean isSection(int position) {
			int sectionIdx = getSectionForPosition(position);
			int sectionPosition = getPositionForSection(sectionIdx);
			
			return (position == sectionPosition);
		}

		public String getSection(int position) {
			if (mSectionDatas == null)
				return NONE_ENGLISH_LETTER_TITLE;

			int sectionIndex = getSectionForPosition(position);
			if(sectionIndex < 0 || sectionIndex >= mSectionDatas.length)
				return NONE_ENGLISH_LETTER_TITLE;
			
			return mSectionDatas[sectionIndex].toString();
		}
	}
SectionTitle就是一个很简单的类,主要的好处就是它的实例是一个对象,可以通过设置title来改变它包含的字符串,这样也就间接地解决了FastScroller不刷新SectionTitles列表的问题。

ProcessCursor 是一个很重要的函数,

1.直接在类变量mSectionDatas上操作。

2.设置title要按顺序来,最后一个有效的title(比如你只有A,B,F,T开头的联系人。那T就是最后一个有效的)。记得要把之后的对象的值也都设为这个title。代码里是这一段:

		for(; i < mSectionDatas.length; i++){
			mSectionDatas[i].title = curtitle;
			sectionPositionMap.put(i, position);
		}

其实还有一个方法就是自己实现中间字母的显示,不过自己实现只能做一有滚动就显示字母。但是我们的Product Owner要求必须是按在那个快速滑动的按钮上滑动时,才出现中间那个字母。所以如果也有这种要求的朋友就可以参考我这个实现了。还有呢,因为我这个是联系人的,比较简单,横竖就27种分组(包括#组),所以这种定长的数组也可以解决。如果有其它不定长要求的,就只能用安卓的4.0版本啦~!

你可能感兴趣的:(研究ListView里的FastScroller的一点心得)