首页 > 系统 > Android > 正文

Android仿微信通讯录功能,好友排序+字母索引

2019-11-09 15:20:16
字体:
来源:转载
供稿:网友

一、效果图展示

二、功能特点

1.好友排序:按照拼音顺序对好友进行排序,兼容英文数字符号等

2.字母索引:右侧字母导航条,既可拖动也可点击,联动ListView滑动

三、实现

接下来就让我们一步步显示这个效果吧。

1.右侧字母索引的导航条

这个我们可以在网上找到很多类似的,你大可找一个自己喜欢的甚至自己写一个出来,这里我在网上找了一个带波浪效果的,看起来比较炫酷一点吧。

这是原地址:https://github.com/AlexLiuSheng/AnimSideBar

然后我把它导入到了我们项目中并修改了部分代码,以下是我项目中的SideBar.java

[java] view plain copypackage com.afei.indexlistview;    import android.content.Context;  import android.content.res.TypedArray;  import android.graphics.Canvas;  import android.graphics.Paint;  import android.util.AttributeSet;  import android.view.MotionEvent;  import android.widget.TextView;    public class SideBar extends TextView {      PRivate String[] letters = 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", "#"};      private Paint textPaint;      private Paint bigTextPaint;      private Paint scaleTextPaint;        private Canvas canvas;      private int itemH;      private int w;      private int h;      /**      * 普通情况下字体大小      */      float singleTextH;      /**      * 缩放离原始的宽度      */      private float scaleWidth;      /**      * 滑动的Y      */      private float eventY = 0;      /**      * 缩放的倍数      */      private int scaleSize = 1;      /**      * 缩放个数item,即开口大小      */      private int scaleItemCount = 6;      private ISideBarSelectCallBack callBack;        public SideBar(Context context) {          this(context, null);      }        public SideBar(Context context, AttributeSet attrs) {          this(context, attrs, 0);      }        public SideBar(Context context, AttributeSet attrs, int defStyleAttr) {          super(context, attrs, defStyleAttr);          init(attrs);      }        private void init(AttributeSet attrs) {          if (attrs != null) {              TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SideBar);              scaleSize = ta.getInteger(R.styleable.SideBar_scaleSize, 1);              scaleItemCount = ta.getInteger(R.styleable.SideBar_scaleItemCount, 6);              scaleWidth = ta.getDimensionPixelSize(R.styleable.SideBar_scaleWidth, dp(100));              ta.recycle();          }          textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);          textPaint.setColor(getCurrentTextColor());          textPaint.setTextSize(getTextSize());          textPaint.setTextAlign(Paint.Align.CENTER);          bigTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);          bigTextPaint.setColor(getCurrentTextColor());          bigTextPaint.setTextSize(getTextSize() * (scaleSize + 3));          bigTextPaint.setTextAlign(Paint.Align.CENTER);          scaleTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);          scaleTextPaint.setColor(getCurrentTextColor());          scaleTextPaint.setTextSize(getTextSize() * (scaleSize + 1));          scaleTextPaint.setTextAlign(Paint.Align.CENTER);      }        public void setDataResource(String[] data) {          letters = data;          invalidate();      }        public void setOnStrSelectCallBack(ISideBarSelectCallBack callBack) {          this.callBack = callBack;      }        /**      * 设置字体缩放比例      *      * @param scale      */      public void setScaleSize(int scale) {          scaleSize = scale;          invalidate();      }        /**      * 设置缩放字体的个数,即开口大小      *      * @param scaleItemCount      */      public void setScaleItemCount(int scaleItemCount) {          this.scaleItemCount = scaleItemCount;          invalidate();      }        private int dp(int px) {          final float scale = getContext().getResources().getDisplayMetrics().density;          return (int) (px * scale + 0.5f);      }        @Override      public boolean onTouchEvent(MotionEvent event) {          switch (event.getAction()) {              case MotionEvent.ACTION_DOWN:              case MotionEvent.ACTION_MOVE:                  if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {                      eventY = event.getY();                      invalidate();                      return true;                  } else {                      eventY = 0;                      invalidate();                      break;                  }              case MotionEvent.ACTION_CANCEL:                  eventY = 0;                  invalidate();                  return true;              case MotionEvent.ACTION_UP:                  if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {                      eventY = 0;                      invalidate();                      return true;                  } else                      break;          }          return super.onTouchEvent(event);      }          @Override      protected void onDraw(Canvas canvas) {          this.canvas = canvas;          DrawView(eventY);      }        private void DrawView(float y) {          int currentSelectIndex = -1;          if (y != 0) {              for (int i = 0; i < letters.length; i++) {                  float currentItemY = itemH * i;                  float nextItemY = itemH * (i + 1);                  if (y >= currentItemY && y < nextItemY) {                      currentSelectIndex = i;                      if (callBack != null) {                          callBack.onSelectStr(currentSelectIndex, letters[i]);                      }                      //画大的字母                      Paint.FontMetrics fontMetrics = bigTextPaint.getFontMetrics();                      float bigTextSize = fontMetrics.descent - fontMetrics.ascent;                      canvas.drawText(letters[i], w - getPaddingRight() - scaleWidth - bigTextSize, singleTextH + itemH * i, bigTextPaint);                  }              }          }          drawLetters(y, currentSelectIndex);      }        private void drawLetters(float y, int index) {          //第一次进来没有缩放情况,默认画原图          if (index == -1) {              w = getMeasuredWidth();              h = getMeasuredHeight();              itemH = h / letters.length;              Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();              singleTextH = fontMetrics.descent - fontMetrics.ascent;              for (int i = 0; i < letters.length; i++) {                  canvas.drawText(letters[i], w - getPaddingRight(), singleTextH + itemH * i, textPaint);              }              //触摸的时候画缩放图          } else {              //遍历所有字母              for (int i = 0; i < letters.length; i++) {                  //要画的字母的起始Y坐标                  float currentItemToDrawY = singleTextH + itemH * i;                  float centerItemToDrawY;                  if (index < i)                      centerItemToDrawY = singleTextH + itemH * (index + scaleItemCount);                  else                      centerItemToDrawY = singleTextH + itemH * (index - scaleItemCount);                  float delta = 1 - Math.abs((y - currentItemToDrawY) / (centerItemToDrawY - currentItemToDrawY));                  float maxRightX = w - getPaddingRight();                  //如果大于0,表明在y坐标上方                  scaleTextPaint.setTextSize(getTextSize() + getTextSize() * delta);                  float drawX = maxRightX - scaleWidth * delta;                  //超出边界直接花在边界上                  if (drawX > maxRightX)                      canvas.drawText(letters[i], maxRightX, singleTextH + itemH * i, textPaint);                  else                      canvas.drawText(letters[i], drawX, singleTextH + itemH * i, scaleTextPaint);              }          }      }        public interface ISideBarSelectCallBack {          void onSelectStr(int index, String selectStr);      }    }  然后还有3个自定义的属性

[html] view plain copy<?xml version="1.0" encoding="utf-8"?>  <resources>      <declare-styleable name="SideBar">          <attr name="scaleSize" format="integer"/>          <attr name="scaleItemCount" format="integer"/>          <attr name="scaleWidth" format="dimension"/>      </declare-styleable>  </resources>  

2.汉字转拼音工具类

我们知道,java中是没有提供接口和方法让我们直接将汉字转成拼音的。

这里,可以参见我的另一篇博客:Java/Android中汉字转拼音的两种方法,优劣比较然后在此我选择了使用第三方jar包的方式,因为它体积不大而且更加准确。以下是我的Cn2Spell.java

[java] view plain copypackage com.afei.indexlistview;    import net.sourceforge.pinyin4j.PinyinHelper;  import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;  import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;  import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;    /**  * 汉字转换位汉语拼音,英文字符不变  */  public class Cn2Spell {        public static StringBuffer sb = new StringBuffer();        /**      * 获取汉字字符串的首字母,英文字符不变      * 例如:阿飞→af      */      public static String getPinYinHeadChar(String chines) {          sb.setLength(0);          char[] chars = chines.toCharArray();          HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();          defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);          defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);          for (int i = 0; i < chars.length; i++) {              if (chars[i] > 128) {                  try {                      sb.append(PinyinHelper.toHanyuPinyinStringArray(chars[i], defaultFormat)[0].charAt(0));                  } catch (Exception e) {                      e.printStackTrace();                  }              } else {                  sb.append(chars[i]);              }          }          return sb.toString();      }        /**      * 获取汉字字符串的第一个字母      */      public static String getPinYinFirstLetter(String str) {          sb.setLength(0);          char c = str.charAt(0);          String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);          if (pinyinArray != null) {              sb.append(pinyinArray[0].charAt(0));          } else {              sb.append(c);          }          return sb.toString();      }        /**      * 获取汉字字符串的汉语拼音,英文字符不变      */      public static String getPinYin(String chines) {          sb.setLength(0);          char[] nameChar = chines.toCharArray();          HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();          defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);          defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);          for (int i = 0; i < nameChar.length; i++) {              if (nameChar[i] > 128) {                  try {                      sb.append(PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0]);                  } catch (Exception e) {                      e.printStackTrace();                  }              } else {                  sb.append(nameChar[i]);              }          }          return sb.toString();      }    }  

3.让你的好友可以根据拼音来排序

我们选择实现comparable接口,并重写comparaTo方法。以下是我的User.java

[java] view plain copypackage com.afei.indexlistview;    /**  * Created by Administrator on 2016/5/25.  */  public class User implements Comparable<User> {        private String name; // 姓名      private String pinyin; // 姓名对应的拼音      private String firstLetter; // 拼音的首字母        public User() {      }        public User(String name) {          this.name = name;          pinyin = Cn2Spell.getPinYin(name); // 根据姓名获取拼音          firstLetter = pinyin.substring(0, 1).toUpperCase(); // 获取拼音首字母并转成大写          if (!firstLetter.matches("[A-Z]")) { // 如果不在A-Z中则默认为“#”              firstLetter = "#";          }      }        public String getName() {          return name;      }        public String getPinyin() {          return pinyin;      }        public String getFirstLetter() {          return firstLetter;      }          @Override      public int compareTo(User another) {          if (firstLetter.equals("#") && !another.getFirstLetter().equals("#")) {              return 1;          } else if (!firstLetter.equals("#") && another.getFirstLetter().equals("#")){              return -1;          } else {              return pinyin.compareToIgnoreCase(another.getPinyin());          }      }  }  

原理很简单,就是先根据首字母判断,首字母为“#”都放在最后,都为“#”或者都是字母时才根据拼音来比较排序

4.万事俱备只欠东风,接下来就是组装这些东西了

activity_main.xml布局文件

[html] view plain copy<?xml version="1.0" encoding="utf-8"?>  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"      tools:context="com.afei.indexlistview.MainActivity">        <ListView          android:id="@+id/listView"          android:layout_width="match_parent"          android:layout_height="match_parent" />        <com.afei.indexlistview.SideBar          android:id="@+id/side_bar"          android:layout_width="match_parent"          android:layout_height="match_parent"          android:layout_alignParentRight="true"          android:paddingRight="10dp"          android:textColor="@color/colorAccent"          android:textSize="15sp" />    </RelativeLayout>  MainActivity.java

[java] view plain copypackage com.afei.indexlistview;    import android.support.v7.app.AppCompatActivity;  import android.os.Bundle;  import android.widget.ListView;    import java.util.ArrayList;  import java.util.Collections;    public class MainActivity extends AppCompatActivity {        private ListView listView;      private SideBar sideBar;      private ArrayList<User> list;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          initView();          initData();      }        private void initView() {          listView = (ListView) findViewById(R.id.listView);          sideBar = (SideBar) findViewById(R.id.side_bar);          sideBar.setOnStrSelectCallBack(new SideBar.ISideBarSelectCallBack() {              @Override              public void onSelectStr(int index, String selectStr) {                  for (int i = 0; i < list.size(); i++) {                      if (selectStr.equalsIgnoreCase(list.get(i).getFirstLetter())) {                          listView.setSelection(i); // 选择到首字母出现的位置                          return;                      }                  }              }          });      }        private void initData() {          list = new ArrayList<>();          list.add(new User("亳州")); // 亳[bó]属于不常见的二级汉字          list.add(new User("大娃"));          list.add(new User("二娃"));          list.add(new User("三娃"));          list.add(new User("四娃"));          list.add(new User("五娃"));          list.add(new User("六娃"));          list.add(new User("七娃"));          list.add(new User("喜羊羊"));          list.add(new User("美羊羊"));          list.add(new User("懒羊羊"));          list.add(new User("沸羊羊"));          list.add(new User("暖羊羊"));          list.add(new User("慢羊羊"));          list.add(new User("灰太狼"));          list.add(new User("红太狼"));          list.add(new User("孙悟空"));          list.add(new User("黑猫警长"));          list.add(new User("舒克"));          list.add(new User("贝塔"));          list.add(new User("海尔"));          list.add(new User("阿凡提"));          list.add(new User("邋遢大王"));          list.add(new User("哪吒"));          list.add(new User("没头脑"));          list.add(new User("不高兴"));          list.add(new User("蓝皮鼠"));          list.add(new User("大脸猫"));          list.add(new User("大头儿子"));          list.add(new User("小头爸爸"));          list.add(new User("蓝猫"));          list.add(new User("淘气"));          list.add(new User("叶峰"));          list.add(new User("楚天歌"));          list.add(new User("江流儿"));          list.add(new User("Tom"));          list.add(new User("Jerry"));          list.add(new User("12345"));          list.add(new User("54321"));          list.add(new User("_(:з」∠)_"));          list.add(new User("……%¥#¥%#"));          Collections.sort(list); // 对list进行排序,需要让User实现Comparable接口重写compareTo方法          SortAdapter adapter = new SortAdapter(this, list);          listView.setAdapter(adapter);      }  }  这里负责初始化UI和数据,并且实现滑动或选择字母索引时的回调接口。既然用到了ListView,我们就还需要一个适配器。

SortAdapter.java

[java] view plain copypackage com.afei.indexlistview;    import android.content.Context;  import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup;  import android.widget.BaseAdapter;  import android.widget.TextView;    import java.util.List;    public class SortAdapter extends BaseAdapter{        private List<User> list = null;      private Context mContext;        public SortAdapter(Context mContext, List<User> list) {          this.mContext = mContext;          this.list = list;      }        public int getCount() {          return this.list.size();      }        public Object getItem(int position) {          return list.get(position);      }        public long getItemId(int position) {          return position;      }        public View getView(final int position, View view, ViewGroup arg2) {          ViewHolder viewHolder;          final User user = list.get(position);          if (view == null) {              viewHolder = new ViewHolder();              view = LayoutInflater.from(mContext).inflate(R.layout.item, null);              viewHolder.name = (TextView) view.findViewById(R.id.name);              viewHolder.catalog = (TextView) view.findViewById(R.id.catalog);              view.setTag(viewHolder);          } else {              viewHolder = (ViewHolder) view.getTag();          }            //根据position获取首字母作为目录catalog          String catalog = list.get(position).getFirstLetter();            //如果当前位置等于该分类首字母的Char的位置 ,则认为是第一次出现          if(position == getPositionForSection(catalog)){              viewHolder.catalog.setVisibility(View.VISIBLE);              viewHolder.catalog.setText(user.getFirstLetter().toUpperCase());          }else{              viewHolder.catalog.setVisibility(View.GONE);          }            viewHolder.name.setText(this.list.get(position).getName());            return view;        }        final static class ViewHolder {          TextView catalog;          TextView name;      }        /**      * 获取catalog首次出现位置      */      public int getPositionForSection(String catalog) {          for (int i = 0; i < getCount(); i++) {              String sortStr = list.get(i).getFirstLetter();              if (catalog.equalsIgnoreCase(sortStr)) {                  return i;              }          }          return -1;      }    }  适配器还用到了一个布局,即

item.xml

[html] view plain copy<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="wrap_content"      android:gravity="center_vertical"      android:orientation="vertical" >        <TextView          android:id="@+id/catalog"          android:layout_width="match_parent"          android:layout_height="match_parent"          android:background="#E0E0E0"          android:textColor="#454545"          android:textSize="20sp"          android:padding="10dp"/>        <TextView          android:id="@+id/name"          android:layout_width="match_parent"          android:layout_height="match_parent"          android:gravity="center_vertical"          android:textColor="#336598"          android:textSize="16sp"          android:padding="10dp"/>    </LinearLayout>  布局有两部分,一个是目录,即A,B,C,D这样的索引,仅当该目录下的第一项出现时才显示;一个则是姓名

四、项目地址

Git地址:http://git.oschina.net/afei_/IndexListView


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表