android之ListView分組及字母索引導航

簡介

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

這些字段是爲了提高排序效率和區分分組標籤而加進去的,正常的數據不應該有這些字段。有辦法去掉麼。

還有優化的空間,把其寫成一個通用的控件。

先到這裏了。

 

 

發佈了35 篇原創文章 · 獲贊 18 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章