ListView的快速索引

在玩即时通讯app比如微信的联系人列表的时候,发现联系人列表的右侧有个竖排的字母索引,点击字母,可以快速索引到相应的联系人。本篇就是实现这个功能。

效果图:

快速索引
快速索引

需要实现:

  • 将右侧的字母画到屏幕上去;
  • 联系人列表排序;
  • 根据联系人名字的首字母为ListView打上TAG
  • 选中索引的某个字母,出现相应的TAG下的联系人;

    画索引字母到屏幕上

    新建一个类,继承自View
    public class QuickIndexBar extends View
    

并提供构造器方法,在构造器中完成相应的初始化工作:

private String[] indexArr;

    public QuickIndexBar(Context context) {
        this(context, null);
    }

    public QuickIndexBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public QuickIndexBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        indexArr = new String[]{"搜", "#", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.DKGRAY);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTextSize(20);

    }

下面开始画:

@Override
   protected void onDraw(Canvas canvas) {
       super.onDraw(canvas);
       float center = getWidth() / 2;

       height = getHeight() / indexArr.length;

       for (int i = indexArr.length - 1; i > -1; i--) {
           canvas.drawText(indexArr[i], center, height * (i + 1), paint);
       }
   }

此时运行,发现字母索引在屏幕的中间位置。好的,这是第一步。其中,索引的每个字母都是通过drawText(...)方法画上去的,每个字母的x坐标不变,y坐标进行相应的变化(不难理解),需要知道的是:屏幕的坐标是这样的,左上角是坐标原点,向右是x正轴,向下是y轴正轴。

填充虚拟数据

我们看到联系人列表的每个Item包括一个图标和名字,还有相应的TAG,新建实体类People,这里为了能够对最后的联系人排序,我们实现了Comparable接口,并实现了接口的compareTo(...)方法,这里用到了工具类HanziToPinyin.java,这个工具类是我从Android 4.2源代码中抽出来的,当然,如果你没有源码,也可以去github上找一个。

package com.dystu.quickindexbar;

/**
 * Created by Administrator on 2015/5/6.
 */
public class People implements Comparable<People> {

    private String name;

    private int imageId;

    private String header;


    public People() {
    }

    public People(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }


    @Override
    public int compareTo(People another) {
        return (HanziToPinyin.getInstance()
                .get(this.getName().substring(0, 1)).get(0).target.substring(0, 1).toUpperCase())
                .compareTo(HanziToPinyin.getInstance()
                        .get(another.getName().substring(0, 1)).get(0).target.substring(0, 1).toUpperCase());
    }
}

新建PeopleAdapter继承自ArrayAdapter,这里我们知道,联系人列表根据首字母有个相应的TAG,那么这个TAG如何处理,这个先放放。
核心代码如下:

public PeopleAdapter(Context context, int textViewResourceId, List objects) {
       super(context, textViewResourceId, objects);
       resourceId = textViewResourceId;
   }

   @Override
   public View getView(int position, View convertView, ViewGroup parent) {

       View view;
       ViewHolder viewHolder;
       if (convertView == null) {
           view = LayoutInflater.from(getContext()).inflate(resourceId, null);
           viewHolder = new ViewHolder();
           viewHolder.peopleImage = (ImageView) view.findViewById(R.id.people_image);
           viewHolder.peopleName = (TextView) view.findViewById(R.id.people_name);
           viewHolder.tvHeader = (TextView) view.findViewById(R.id.header);
           view.setTag(viewHolder);
       } else {
           view = convertView;
           viewHolder = (ViewHolder) view.getTag();
       }

       People people = getItem(position);

       String header = people.getHeader();

       if (position == 0||header != null && !header.equals(getItem(position-1).getHeader())) {

           if ("".equals(header)) {
               viewHolder.tvHeader.setVisibility(View.GONE);
           } else {

               viewHolder.tvHeader.setVisibility(View.VISIBLE);

               viewHolder.tvHeader.setText(header);
           }
       } else {
           viewHolder.tvHeader.setVisibility(View.GONE);
       }

       viewHolder.peopleName.setText(people.getName());
       viewHolder.peopleImage.setImageResource(people.getImageId());
       return view;
   }


   @Override
   public People getItem(int position) {
       return super.getItem(position);
   }

   @Override
   public int getCount() {
       return super.getCount();
   }

Activity中,初始化ListView的数据,还是先看下Activity的布局:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:id="@+id/rl_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/transparent"
            android:cacheColorHint="#00000000"
            android:descendantFocusability="afterDescendants"
            android:divider="@color/divider_list"
            android:dividerHeight="1px"
            android:fastScrollEnabled="false" />

        <com.dystu.quickindexbar.QuickIndexBar
            android:id="@+id/sidebar"
            android:layout_width="25dp"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:background="@android:color/transparent"
            android:clickable="true" />

        <TextView
            android:id="@+id/floating_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:background="@drawable/show_head_toast_bg"
            android:gravity="center"
            android:paddingLeft="25dp"
            android:paddingRight="25dp"
            android:textColor="@android:color/white"
            android:textSize="40sp"
            android:visibility="invisible" />

    RelativeLayout>

FrameLayout>

如果你用的是Android Studio的话,那么你可以看到效果了,索引列表已经显示在ListView之上了,那么TextView是手指按下哪个字母进行提示的,手指抬起,他就消失。

package com.dystu.quickindexbar;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


public class MainActivity extends Activity {

    private List peopleList;

    private PeopleAdapter adapter;


    private ListView list;

    private QuickIndexBar sideBar;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        peopleList = new ArrayList<>();
        peopleList.clear();

        initPeople();

        list = (ListView) findViewById(R.id.list);
        sideBar = (QuickIndexBar) findViewById(R.id.sidebar);
        adapter = new PeopleAdapter(MainActivity.this, R.layout.people_item, peopleList);
        list.setAdapter(adapter);
        sideBar.setListView(list);


        for (int i = 0; i < peopleList.size(); i++) {

            People people = peopleList.get(i);

            String userName = people.getName();

            people.setName(userName);

            setUserHeader(userName, people);

        }


    }

    private void setUserHeader(String userName, People people) {

        String headerName = null;

        headerName = people.getName();

        System.out.println("headerName:" + headerName);

        if (Character.isDigit(headerName.charAt(0))) {
            people.setHeader("#");
        } else {
            people.setHeader(HanziToPinyin.getInstance().get(headerName.substring(0, 1)).get(0).target.substring(0, 1).toUpperCase());
            char header = people.getHeader().toLowerCase().charAt(0);
            if (header < 'a' || header > 'z') {
                people.setHeader("#");
            }
        }

    }

    private void initPeople() {

        People people00 = new People("123d",R.mipmap.ic_launcher);
        peopleList.add(people00);

        People people0 = new People("阿多", R.mipmap.ic_launcher);
        peopleList.add(people0);
        People people1 = new People("阿杜", R.mipmap.ic_launcher);
        peopleList.add(people1);

        People people2 = new People("白云", R.mipmap.ic_launcher);
        peopleList.add(people2);

        People people3 = new People("白百何", R.mipmap.ic_launcher);
        peopleList.add(people3);

        People people4 = new People("白衣天使", R.mipmap.ic_launcher);
        peopleList.add(people4);

        People people5 = new People("陈超", R.mipmap.ic_launcher);
        peopleList.add(people5);

        People people6 = new People("杜小丽", R.mipmap.ic_launcher);
        peopleList.add(people6);

        People people7 = new People("宋娟", R.mipmap.ic_launcher);
        peopleList.add(people7);

        People people8 = new People("贾忆", R.mipmap.ic_launcher);
        peopleList.add(people8);

        People people9 = new People("张大炮", R.mipmap.ic_launcher);
        peopleList.add(people9);

        People people10 = new People("张伟", R.mipmap.ic_launcher);
        peopleList.add(people10);

        People people11 = new People("吕子乔", R.mipmap.ic_launcher);
        peopleList.add(people11);

        People people12 = new People("曾小贤", R.mipmap.ic_launcher);
        peopleList.add(people12);

        People people13 = new People("关谷", R.mipmap.ic_launcher);
        peopleList.add(people13);

        People people14 = new People("胡一菲", R.mipmap.ic_launcher);
        peopleList.add(people14);

        People people15 = new People("罗兰", R.mipmap.ic_launcher);
        peopleList.add(people15);

        People people16 = new People("美嘉", R.mipmap.ic_launcher);
        peopleList.add(people16);

        People people17 = new People("陆展博", R.mipmap.ic_launcher);
        peopleList.add(people17);


       /* Collections.sort(peopleList, new Comparator() {
            @Override
            public int compare(People lhs, People rhs) {
                return lhs.getName().compareTo(rhs.getName());
            }
        });*/

        Collections.sort(peopleList);

    }


}

TAG处理

TAG处理我们放在Adapter中处理,让其实现SectionIndexer接口,并实现该接口的三个方法。

@Override
   public Object[] getSections() {

       positionOfSection = new SparseIntArray();

       sectionOfPosition = new SparseIntArray();

       int count = getCount();

       list = new ArrayList<>();

       list.add("搜");

       positionOfSection.put(0, 0);

       sectionOfPosition.put(0, 0);

       for (int i = 1; i < count; i++) {
           String letter = getItem(i).getHeader();
           int section = list.size() - 1;
           if (list.get(section) != null && !list.get(section).equals(letter)) {
               list.add(letter);
               section++;
               positionOfSection.put(section, i);
           }
           sectionOfPosition.put(i, section);

       }

       return list.toArray(new String[list.size()]);
   }

   @Override
   public int getPositionForSection(int sectionIndex) {
       return positionOfSection.get(sectionIndex);
   }

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

ok,那么这三个方法是干嘛的?还有SparseIntArray是什么鸟玩意,老子怎么不知道这些!?
SectionIndexer的使用

触摸事件的处理

private int sectionForPoint(float y) {
       int index = (int) (y / height);
       if (index < 0) {
           index = 0;
       }
       if (index > indexArr.length - 1) {
           index = indexArr.length - 1;
       }
       return index;
   }

   private void setHeaderTextAndScroll(MotionEvent event){
       if (mListView == null){
           return;
       }

       String headString= indexArr[sectionForPoint(event.getY())];
       header.setText(headString);
       PeopleAdapter adapter = (PeopleAdapter) mListView.getAdapter();
       String[] adapterSections = (String[]) adapter.getSections();
       for (int i = adapterSections.length -1;i>-1;i--){
           if (adapterSections[i].equals(headString)){
               mListView.setSelection(adapter.getPositionForSection(i));
               break;
           }
       }
   }


   @Override
   public boolean onTouchEvent(MotionEvent event) {
       switch (event.getAction()){
           case MotionEvent.ACTION_DOWN:
               if (header == null){
                   header = (TextView)((View)getParent()).findViewById(R.id.floating_header);
               }
               setHeaderTextAndScroll(event);
               header.setVisibility(View.VISIBLE);
               setBackgroundResource(R.drawable.sidebar_background_pressed);
               return  true;
           case MotionEvent.ACTION_MOVE:
               setHeaderTextAndScroll(event);
               return  true;
           case MotionEvent.ACTION_UP:
               header.setVisibility(View.INVISIBLE);
               setBackgroundColor(Color.TRANSPARENT);
               return  true;
           case MotionEvent.ACTION_CANCEL:
               header.setVisibility(View.INVISIBLE);
               setBackgroundColor(Color.TRANSPARENT);
               return  true;


       }
       return super.onTouchEvent(event);
   }

源代码下载链接:源码

http://chenfuduo.me/2015/05/06/quick-index-listview/

你可能感兴趣的:(自定义控件)