android之ListView分组及字母索引导航

本文详细介绍了如何在Android中实现ListView的分组和字母索引导航功能,包括数据BO处理、布局和Activity设置、数据排序、Adaptor实现以及字母索引的触摸事件监听,以达到高效、美观的列表效果。

简介

在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 addcontent

           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;

这些字段是为了提高排序效率和区分分组标签而加进去的,正常的数据不应该有这些字段。有办法去掉么。

还有优化的空间,把其写成一个通用的控件。

先到这里了。

 

 

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值