前言

  因为自己在做的一个小软件里面需要用到从A-Z排序的ListView,所以自然而然的想到了微信的联系人,我想要的就是那样的效果。本来没打算自己去写,想要第三方写好的东西,搜了几个之后发现有的太复杂了,有的简单是简单,但是不符合我的要求,所以我就来个整合,把复杂性和简单性合二为一。

实现

  先来看效果图吧:

要点分析

  要实现这样的效果需要考虑下面的几个问题:

  • 右边字母栏的绘制
  • 点击效果的实现
  • 汉字按A-Z的排序问题
  • 正常的Item和字母分隔符的Item的实现

  下面我们就解决这几个问题,然后就可以出现上面的效果了。

【第一步】

  我们需要先自定义一个类,就叫SlideBar吧,让它继承Button,然后我们覆盖onDraw方法,绘制字母a-z就可以出现右边字母栏的效果了。

看一下源码:

public class SlideBar extends Button{

 public interface OnTouchAssortListener{
 public void onTouchAssortListener(String s);
 }

 // 分类
 private static final String[] ASSORT_TEXT = {"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" ,"#"};

 private Paint mPaint = new Paint();
 private int mSelectIndex = -1;
 private OnTouchAssortListener mListener = null;
 private Activity mAttachActivity;
 PopupWindow mPopupWindow = null;
 View layoutView;
 TextView text;

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

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

 public SlideBar(Context context, AttributeSet attrs, int defStyle){
 super(context, attrs, defStyle);
 mAttachActivity = (Activity)context;
 init(context);
 }
 private void init(Context context) {
 layoutView = LayoutInflater.from(context).inflate(R.layout.alert_dialog_menu_layout, null);
 text = (TextView) layoutView.findViewById(R.id.content);
 }

 public void setOnTouchAssortListener(OnTouchAssortListener listener) {
 this.mListener = listener;
 }

 @Override
 protected void onDraw(Canvas canvas){
 super.onDraw(canvas);
 int nHeight = getHeight();
 int hWidth = getWidth();
 int nAssortCount = ASSORT_TEXT.length;
 int nInterval = nHeight / nAssortCount;

 for (int i = 0; i < nAssortCount; i++){
  mPaint.setAntiAlias(true); // 抗锯齿
  mPaint.setTypeface(Typeface.DEFAULT_BOLD); // 默认粗体
  mPaint.setColor(Color.parseColor("#5f5f5f")); // 白色
  if (i == mSelectIndex){
  // 被选择的字母改变颜色和粗体
  mPaint.setColor(Color.parseColor("#3399ff"));
  mPaint.setFakeBoldText(true);
  mPaint.setTextSize(30);
  }
  float xPos = hWidth / 2 - mPaint.measureText(ASSORT_TEXT[i]) / 2; // 计算字母的X坐标
  float yPos = nInterval * i + nInterval; // 计算字母的Y坐标
  canvas.drawText(ASSORT_TEXT[i], xPos, yPos, mPaint);
  mPaint.reset();
 }
 }

 @Override
 public boolean dispatchTouchEvent(MotionEvent event) {
 //判断是哪一个字母被点击了
 int nIndex = (int) (event.getY() / getHeight() * ASSORT_TEXT.length);
 if (nIndex >= 0 && nIndex < ASSORT_TEXT.length){
  switch (event.getAction()){
  case MotionEvent.ACTION_MOVE:
  // 如果滑动改变
  if (mSelectIndex != nIndex){
   mSelectIndex = nIndex;
   showCharacter(ASSORT_TEXT[mSelectIndex]);
   if (mListener != null){
   mListener.onTouchAssortListener(ASSORT_TEXT[mSelectIndex]);
   }
  }
  break;
  case MotionEvent.ACTION_DOWN:
  mSelectIndex = nIndex;
  showCharacter(ASSORT_TEXT[mSelectIndex]);
  if (mListener != null){
   mListener.onTouchAssortListener(ASSORT_TEXT[mSelectIndex]);
  }

  break;
  case MotionEvent.ACTION_UP:
  disShowCharacter();
  mSelectIndex = -1;
  break;
  }
 } else {
  mSelectIndex = -1;
  disShowCharacter();
 }
 invalidate();
 return true;
 }

 private void disShowCharacter() {
 if (mPopupWindow != null) {
  mPopupWindow.dismiss();
  mPopupWindow=null;
 }
 }

 /**
 * 显示弹出的字符
 * @param string
 */
 private void showCharacter(String string){

 if (mPopupWindow != null){
  text.setText(string);
 } else{
  mPopupWindow = new PopupWindow(layoutView, 100, 100, false);
  mPopupWindow.showAtLocation(mAttachActivity.getWindow().getDecorView(), Gravity.CENTER, 0, 0);
 }
 text.setText(string);
 }
}

  就先看onDraw方法,其他的内容先不看,首先得到控件的宽和高,然后计算每一个字母应该占据的高度为多少,然后在每一个字母所占空间的中间绘制该字母就可以了,代码比较简单,这一部分就不需要详解了。

【第二步】

  我们需要添加一个点击事件,当点击SlideBar的时候,首先可以看到的是右边栏被点击的字母变大来区别于没被点击的字母,然后弹出一个类似Dialog的东西,显示被点中的字母,这个效果也很好实现。
  在SlideBar里面我们还需要覆盖的一个方法是dispatchTouchEvent(),我们点中SlideBar之后,接下来的动作可能是滑动和抬起,我们需要对点击之后的动作进行响应,如果是抬起的话,那么显示出来的类似dialog的东西就要消失,变大的字母也要回复原样,如果是接着滑动,滑到某一个字母的时候,对应的字母就要变大和显示出来。
  从上面的源码可以看到,字母变大的效果很好实现,把绘制被选中的字母的Paint对象的textsize的值变大就可以了,然后那个类似dialog的东西是用PopupWindow来实现的,当点击和滑动的时候就显示PopupWindow,抬起或者滑出SlideBar范围的时候就让PopupWindow消失。最后需要注意的是invalidate()这个方法千万不要忘记调用了,这个是用来进行画面的重绘。

【第三步】

  我认为最重要也是最难的就是汉字按A-Z的排序了。不过还好,这个已经有人实现了,我们就来所谓的“拿来主义”吧。在工程里面有一个CharacterParser类,这个类封装了对汉字转拼音的操作,其中getSelling(String s)方法的作用是传入汉字字符串得到汉字的拼音,果然是好方法,我喜欢!!这样我们就得到了要显示的汉字字符串的拼音首字母,然后将所有的字符串按照字母进行排序就可以得到一个从A-Z的有序的列表了。因为ListView一般都是绑定一个List对象,然后List对象里面保存一系列的对象,这里我就用一个对象来说:

public class DataBean {

 public static final int TYPE_CHARACTER = 0;
 public static final int TYPE_DATA = 1;
 private int item_type;
 private String item_en;
 private String name;
 private String phone;
 /*其他成员*/

 public DataBean(String name,String phone,int type){
 CharacterParser parser = CharacterParser.getInstance();
 this.name = name;
 this.phone = phone;
 this.item_type = type;
 this.item_en = parser.getSelling(name).toUpperCase().trim();
 if(!item_en.matches("[A-Z]+")){
  item_en = "#"+item_en;
 }
 }

 /*
 *省略geter和seter方法
 */
}

  这个对象里面需要注意的是两个成员变量,item_type和item_en,分别表示该对象是要显示的正常对象还是字母分隔符对象,根据item_type的不同,我们在写Adapter的getView方法的时候就可以返回不同的View对象,然后就可以实现效果图中的正常的Item和字母分割符Item了。item_en表示的是name变量也就是汉字字符串的拼音字符串,主要是用来获取首字母和进行字符串之间的比较。

  现在假设已经有了一个List对象,里面保存了一些DataBean,那么问题来了,如何把这些DataBean对象按拼音字符串进行排序以及如何在List对象里面添加表示字母分隔符的DataBean对象呢?

  首先解决排序的问题,这个比较简单:

  这里用到了Collections的sort方法,这个方法有两个参数,一个就是带排序的List对象,另一个是实现了Comparator接口的类的对象,用来说明如何进行排序,用哪一个成员变量来进行排序。

  PinyinComparator这个类实现了Comparator:

public class PinyinComparator implements Comparator<DataBean>{
 public int compare(DataBean o1, DataBean o2){
 if (o1.getItem_en().equals("@")
  || o2.getItem_en().equals("#")){
  return -1;
 } else if (o1.getItem_en().equals("#")
  || o2.getItem_en().equals("@")) {
  return 1;
 } else {
  return o1.getItem_en().compareTo(o2.getItem_en());
 }
 }
}

  可以看到,两个DataBean对象按照变量item_en也就是拼音字符串来进行排序,这样实现起来比较方便,不需要自己去写排序的算法了,当然也不反对大家自己去实现排序。

  经过Collections的sort函数排序之后,现在List对象里面保存的DataBean对象已经是按照A-Z进行排序的了,现在我们要做的就是在这些对象里面插入一些用来表示字母分隔符DataBean的对象,这个实现应该比较简单,我用的方法比较笨/(ㄒoㄒ)/~~

public class ListUtil {
 public static void sortList(List<DataBean> list){
 List<DataBean> _List = new ArrayList<DataBean>();
 Collections.sort(list, new PinyinComparator());
 DataBean dataBean = new DataBean(getFirstCharacter(list.get(0).getItem_en()), "",DataBean.TYPE_CHARACTER);
 String currentCharacter = getFirstCharacter(list.get(0).getItem_en());
 _List.add(dataBean);
 _List.add(list.get(0));
 for(int i=1;i<list.size();i++){
 if(getFirstCharacter(list.get(i).getItem_en()).compareTo(currentCharacter)!=0){
 currentCharacter = getFirstCharacter(list.get(i).getItem_en());
 dataBean = new DataBean(currentCharacter, "",DataBean.TYPE_CHARACTER);
 _List.add(dataBean);
 }
 _List.add(list.get(i));
 }
 list.clear();
 for(DataBean bean:_List){
 list.add(bean);
 }
 }
 public static String getFirstCharacter(String str){
 return str.substring(0, 1);
 }
}

  这个类就实现了往List对象里面添加一些表示字母分隔符的对象,通过设置item_type变量的值不同,在Adapter里面根据这个值返回不同的View就可以实现不同的Item显示。

  好了,到现在只剩一个问题了,那就是点击了字母之后,ListView设置该字母对应的Item在第一个显示,这个实现也不难,得到了被点中的字母之后,遍历所有的DataBean对象,然后找到和当前字母匹配的第一个字母分隔符对象,然后得到该Item的position的值,设置ListView被选中的Item的position为找到的Item的position即可。

  终于,所有的问题解决了,看看现在自己能不能实现这样的效果了呢?如果不行的话可以参考一个我的源码。

小结

  本来我也是对这个不是太懂,但是强迫自己去看源码,因为网上的大神写的东西可能并不是100%满足自己的要求,所以自己能看懂源码的话就可以自己去修改了。

源码下载在这里

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

01-31 03:28