简介
在android开发的过程中,列表是相当常用的组件,所以美观简单易用的列表是必须的。尤其在数据比较多的时候,索引和搜索功能就变得极为重要。例如android自带的联系人页面和一些App带的城市选择页面。
如下图为某团购网的城市选择页面。
但是纵观网上的大部分分组实现,数据都是诸如以下的形式(摘自某篇博客,或者这里只是说明基本的原理),分组标签和数据分开,不够典型。
public void setData(){
list.add("A");
listTag.add("A");
for(int i=0;i<3;i++){
list.add("阿凡达"+i);
}
list.add("B");
listTag.add("B");
for(int i=0;i<3;i++){
list.add("比特风暴"+i);
}
list.add("C");
listTag.add("C");
for(int i=0;i<30;i++){
list.add("查理风云"+i);
}
}关于索引的实现大部分都不太完整,或者没有处理排序,或者假定是排序好的,对于一般的情况不太实用。本文将提供一种较为全面的实现。
功能分析
在开始实现之前,首先需要明确要实现的功能,才能进而一步步的实现。主要的功能有以下两个:
1. 所有的数据按照首字母排序并按首字母分组;
2. 右边的字母索引(以后也称为”尺子”)可以直接索引到相应的分组位置。
我们所要实现的也就是上面的两个功能。第一个功能需要把分组标签页插入到数据中,并设置分组标记,以便在生成列表的时候特殊处理;第二个功能则需要记录标签在数据中位置。可以细分为如下:
1. 汉字转拼音;
2. 按照首字母排序;
3. 所有的首字母集合;
4. 索引添加到数据集中,并记录索引在数据集合中的位置。
以上的功能分析中,分组标签也会添加到数据中,这个添加是动态完成的,不像简介中硬性添加。
实现思路
1. android提供的实现,HanziToPinyin.Java;
2. Collections.Sort()方法;
3. 遍历排序好的数据集,找出所有的首字母集合。
4. 可以和3同步进行。
实现
先加一张实现结果截图:
接下来一步步的实现。
1. 数据BO
首先是数据,为了简单,我们假定数据只有一个字段,那就是待排序和分组的字段。其他的均为辅助字段。
|
public class TestBo {
/** * 主要字段 */ private StringboStr =null;
/** * bo拼音缓存 */ private StringboPinYin =null; /** * Bo标签标记 */ private StringboTagFlag =null;
public TestBo() { super(); }
public TestBo(String str) { super(); this.boStr = str; } public String getSortStrPinyin() { returnboPinYin; }
public void setSortStrPinYin(String pinyin) { this.boPinYin = pinyin; }
public void setTag(String tag) { this.boTagFlag = tag; } public String getTag() { returnboTagFlag; } } |
2. 布局和activity
|
<?xmlversion="1.0"encoding="utf-8"?> <FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- 主列表 --> <ListView android:id="@+id/g_base_list" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> <!-- 无数据提示 --> <TextView android:id="@+id/g_base_list_nodata" android:layout_width="fill_parent" android:layout_height="fill_parent" android:visibility="gone" android:gravity="center" android:text="@string/g_nodata" android:textSize="16sp"/> <!-- 加载进度框 --> <com.wbx.testpub.wigdit.ProgressBarWithText android:id="@+id/g_base_progressbar_withtext" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="center" android:gravity="center"/>
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="30dp" android:layout_marginBottom="30dp" android:orientation="vertical" >
<!-- 索引被点击到的时候放大显示 --> <TextView android:id="@+id/g_ruler_tag" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center" android:background="@drawable/g_item_bk_normal" android:gravity="center" android:text="A" android:textColor="#FF58BD21" android:textSize="70sp" android:textStyle="bold" /> <!-- 字母索引导航--> <LinearLayout android:id="@+id/g_ruler" android:layout_width="30dp" android:layout_height="match_parent" android:layout_gravity="right" android:paddingLeft="5dp" android:paddingRight="5dp" android:orientation="vertical"/>
</FrameLayout> </FrameLayout> |
在onCreate 方法中初始化,主要做以下工作
|
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initView();//初始化view,设置各个View的可见性 initData(); }
private void initView() { noDataView = (TextView) findViewById(R.id.g_base_list_nodata); RulerTag = (TextView) findViewById(R.id.g_ruler_tag); progress = (ProgressBarWithText) findViewById(R.id.g_base_progressbar_withtext); listView = (ListView) findViewById(R.id.g_base_list); ruler = (RulerWidget) findViewById(R.id.g_ruler); progress.setVisibility(View.VISIBLE); RulerTag.setVisibility(View.GONE); noDataView.setVisibility(View.GONE); listView.setVisibility(View.GONE); ruler.setVisibility(View.GONE); }
/** *加载数据,处理数据,并在处理成功后 在界面上进行展示 */ private void initData() { originalData = getBoList();//这里可以从任何地方获取数据,然后回调处理结果; doWithData();//处理后数据放在resData; handleRes();//处理后续结果,初始化字母索引导航 } public List<TestBo> getBoList() { List<TestBo> res = new ArrayList<TestBo>(); TestBo a = new TestBo("aa"); TestBo a1 = new TestBo("阿姨"); TestBo b = new TestBo("bb"); TestBo b1 = new TestBo("爸爸"); res.add(b1); res.add(a); res.add(a1); res.add(b); return res; } |
3. 数据处理
数据处理为原始的数据添加字母分组标签,并记录每个标签在列表中的位置。当然得需要先排序。
生成的标签也是 TestBo 类型的,且tag字段不为null,正常的数据 tag字段为null。
|
/** * 处理数据,排序,添加标签,并记录标签的位置。 */ private void doWithData() {
resData.addAll(originalData); //首先排序 Collections.sort(resData,new Comparator<TestBo>() { @Override public int compare(TestBo lhs, TestBo rhs) { char firChar = checkAndGetPinyin(lhs); char secChar = checkAndGetPinyin(rhs); if (firChar < secChar) { return -1; } else if (firChar > secChar) { return 1; } else return 0; } });
int size =resData.size(); int i = 0; char nowTag ='\0';
for (i = 0; i < size; i++) { TestBo temp = resData.get(i); char tempTag = checkAndGetPinyin(temp); if(Arrays.binarySearch(letters, tempTag) < 0){ tempTag = '#'; } //生成新的TestBo,作为标签,并设置标签标记,以便数据展示的时候区别 if (nowTag != tempTag) { TestBo tagBO = new TestBo(); tagBO.setTag(tempTag+""); resData.add(i, tagBO); tagLoc.put(tempTag +"", i); i++; size++; nowTag = tempTag; } } tagLoc.put("#", 0);
}
/** * 获取首字母,并设置拼音缓存 */ private char checkAndGetPinyin(TestBo bo){ String pinyinStr = bo.getSortStrPinyin(); if (pinyinStr==null) { bo.setSortStrPinYin(HanziToPinyin.getPinYin(bo.getBoStr()).toUpperCase()); pinyinStr = bo.getSortStrPinyin(); } if(pinyinStr!=null&&pinyinStr.length()>=1){ return pinyinStr.charAt(0); } return'\0'; } |
4. Adptor
在实现字母索引导航之前先实现ListView的adaptor,主要是其getView方法。其实就是对于处理后的数据resData中的每一个元素,判断其类型是否为分组标签,如果为标签的话,则采用标签布局,否则采用正常布局。
代码如下:
首先是列表项布局:
|
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:imagecontrol="http://schemas.android.com/apk/res-auto" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/g_ruler_list_item_bg" android:gravity="center_vertical" android:orientation="vertical" android:paddingBottom="4dp" android:paddingLeft="10dp" android:paddingRight="20dp" android:paddingTop="4dp">
<TextView android:id="@+id/g_ruler_list_item_tag" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center_vertical" android:text="A" android:textColor="#FF58BD21" android:textSize="20sp" /> <!—空的布局,用来添加非标签布局 --!> <LinearLayout android:id="@+id/g_ruler_list_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="40dp" android:gravity="center_vertical" >
</LinearLayout>
</LinearLayout> |
然后是adaptor的getView方法,此adaptor基础BaseAdaptor
|
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder;
if (convertView ==null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.g_ruler_list_item,null); holder = new ViewHolder(); holder.tag = (TextView) convertView.findViewById(R.id.g_ruler_list_item_tag); holder.content = (LinearLayout) convertView.findViewById(R.id.g_ruler_list_item); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } final TestBo bo = getItem(position);
String tag = bo.getTag(); if(tag!=null){//是标签 holder.tag.setVisibility(View.VISIBLE); holder.content.setVisibility(View.GONE);
holder.tag.setText(tag); }else{//是内容 //首先取自定义视图,如果没的话就自定义一个TextView add到content中 holder.content.removeAllViews(); holder.tag.setVisibility(View.GONE); holder.content.setVisibility(View.VISIBLE); View contentView = getContentView(position, bo); if(contentView==null){ TextView textV = new TextView(mContext); textV.setText(bo.getBoStr()); textV.setTextColor(Color.BLACK); textV.setGravity(Gravity.CENTER_VERTICAL); textV.setTextSize(16); contentView = textV; } holder.content.addView(contentView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); }
return convertView; }
/** * @date 2014-9-3 * @Description:需要的时候覆盖此方法,提供自己的自定义非标签视图 * @param * @return View */ public View getContentView(int position,TestBo bo){ return null; }
static class ViewHolder{ TextView tag; LinearLayout content; }
|
5. 字母索引导航
基本思路就是在导航条中添加所有的字母标签,然后监听其onTouch事件,根据触摸的不同的位置,计算所触摸的字母,根据tagLoc让列表滚动到相应的位置代码如下:
|
/** *处理结束后的数据 */ private void handleRes() { initRuler();//初始化字母索引 showView();//展示 }
/** * 初始化字母索引 */ private void initRuler() { int color = getResources().getColor(R.color.g_ruler_letter_color);//绿色 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER_HORIZONTAL; ruler.bringToFront();
params.weight = 1; for (int i = 0; i <indexStr.length; i++) { final TextView tv =new TextView(this); tv.setLayoutParams(params); tv.setTextColor(color); tv.setGravity(Gravity.CENTER); tv.setText(RulerUtil.indexStr[i]); ruler.addView(tv); } ruler.setOnTouchListener(new OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) { int height = v.getHeight(); float pos = event.getY(); int sectionPosition = (int) ((pos / height) / (1f /indexStr.length)); if (sectionPosition < 0) { sectionPosition = 0; } else if (sectionPosition > indexStr.length-1) { sectionPosition = indexStr.length-1; }
switch (event.getAction()) { case MotionEvent.ACTION_DOWN: RulerTag.setVisibility(View.VISIBLE); RulerTag.setText(RulerUtil.indexStr[sectionPosition]); listView.setSelection(getPosition(sectionPosition)); ruler.setBackgroundResource(R.color.g_ruler_selected);//灰色 break; case MotionEvent.ACTION_MOVE: RulerTag.setText(RulerUtil.indexStr[sectionPosition]); listView.setSelection(getPosition(sectionPosition)); break; default:
RulerTag.setVisibility(View.GONE);
ruler.setBackgroundResource(R.color.g_blank); } return true; } }); }
private void showView() { progress.setVisibility(View.GONE); listView.setVisibility(View.VISIBLE); ruler.setVisibility(View.VISIBLE); }
|
完了么
写到这里,功能已经全部实现。貌似很完美,但是完了么?假如要在不同的地方使用类似的列表。上面的代码通用性很低。不得不复制和修改,并且上述代码具有代码侵入,想实现分钟列表其对象TestBo 有很多无用的字段,如
private StringboPinYin =null;
/**
* Bo标签标记
*/
private StringboTagFlag =null;
这些字段是为了提高排序效率和区分分组标签而加进去的,正常的数据不应该有这些字段。有办法去掉么。
还有优化的空间,把其写成一个通用的控件。
先到这里了。
本文详细介绍了如何在Android中实现ListView的分组和字母索引导航功能,包括数据BO处理、布局和Activity设置、数据排序、Adaptor实现以及字母索引的触摸事件监听,以达到高效、美观的列表效果。
2万+

被折叠的 条评论
为什么被折叠?



