欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android仿微信通讯录列表侧边栏效果

程序员文章站 2023-12-13 20:53:28
先看android仿微信通讯录列表侧边栏效果图 这是比较常见的效果了吧 列表根据首字符的拼音字母来排序,且可以通过侧边栏的字母索引来进行定位。 实现这样一个...

先看android仿微信通讯录列表侧边栏效果图

Android仿微信通讯录列表侧边栏效果

这是比较常见的效果了吧

列表根据首字符的拼音字母来排序,且可以通过侧边栏的字母索引来进行定位。

实现这样一个效果并不难,只要自定义一个索引view,然后引入一个可以对汉字进行拼音解析的jar包——pinyin4j-2.5.0即可

首先,先来定义侧边栏控件view,只要直接画出来即可。

字母选中项会变为红色,且滑动时背景会变色,此时sidebar并不包含居中的提示文本

public class sidebar extends view {

  private paint paint = new paint();

  private int choose = -1;

  private boolean showbackground;

  public static string[] letters = {"#", "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 onchooseletterchangedlistener onchooseletterchangedlistener;

  public sidebar(context context, attributeset attrs, int defstyle) {
    super(context, attrs, defstyle);
  }

  public sidebar(context context, attributeset attrs) {
    super(context, attrs);
  }

  public sidebar(context context) {
    super(context);
  }

  protected void ondraw(canvas canvas) {
    super.ondraw(canvas);
    if (showbackground) {
      canvas.drawcolor(color.parsecolor("#d9d9d9"));
    }
    int height = getheight();
    int width = getwidth();
    //平均每个字母占的高度
    int singleheight = height / letters.length;
    for (int i = 0; i < letters.length; i++) {
      paint.setcolor(color.black);
      paint.setantialias(true);
      paint.settextsize(25);
      if (i == choose) {
        paint.setcolor(color.parsecolor("#ff2828"));
        paint.setfakeboldtext(true);
      }
      float x = width / 2 - paint.measuretext(letters[i]) / 2;
      float y = singleheight * i + singleheight;
      canvas.drawtext(letters[i], x, y, paint);
      paint.reset();
    }
  }

  @override
  public boolean dispatchtouchevent(motionevent event) {
    int action = event.getaction();
    float y = event.gety();
    int oldchoose = choose;
    int c = (int) (y / getheight() * letters.length);
    switch (action) {
      case motionevent.action_down:
        showbackground = true;
        if (oldchoose != c && onchooseletterchangedlistener != null) {
          if (c > -1 && c < letters.length) {
            onchooseletterchangedlistener.onchooseletter(letters[c]);
            choose = c;
            invalidate();
          }
        }
        break;
      case motionevent.action_move:
        if (oldchoose != c && onchooseletterchangedlistener != null) {
          if (c > -1 && c < letters.length) {
            onchooseletterchangedlistener.onchooseletter(letters[c]);
            choose = c;
            invalidate();
          }
        }
        break;
      case motionevent.action_up:
        showbackground = false;
        choose = -1;
        if (onchooseletterchangedlistener != null) {
          onchooseletterchangedlistener.onnochooseletter();
        }
        invalidate();
        break;
    }
    return true;
  }

  @override
  public boolean ontouchevent(motionevent event) {
    return super.ontouchevent(event);
  }

  public void setontouchingletterchangedlistener(onchooseletterchangedlistener onchooseletterchangedlistener) {
    this.onchooseletterchangedlistener = onchooseletterchangedlistener;
  }

  public interface onchooseletterchangedlistener {

    void onchooseletter(string s);

    void onnochooseletter();

  }

}

sidebar只是画出了侧边栏索引条而已,不包含居中的提示文本,这个在另一个布局添加即可

public class hintsidebar extends relativelayout implements sidebar.onchooseletterchangedlistener {

  private textview tv_hint;

  private sidebar.onchooseletterchangedlistener onchooseletterchangedlistener;

  public hintsidebar(context context, attributeset attrs) {
    super(context, attrs);
    layoutinflater.from(context).inflate(r.layout.view_hint_side_bar, this);
    initview();
  }

  private void initview() {
    sidebar sidebar = (sidebar) findviewbyid(r.id.sidebar);
    tv_hint = (textview) findviewbyid(r.id.tv_hint);
    sidebar.setontouchingletterchangedlistener(this);
  }

  @override
  public void onchooseletter(string s) {
    tv_hint.settext(s);
    tv_hint.setvisibility(visible);
    if (onchooseletterchangedlistener != null) {
      onchooseletterchangedlistener.onchooseletter(s);
    }
  }

  @override
  public void onnochooseletter() {
    tv_hint.setvisibility(invisible);
    if (onchooseletterchangedlistener != null) {
      onchooseletterchangedlistener.onnochooseletter();
    }
  }

  public void setonchooseletterchangedlistener(sidebar.onchooseletterchangedlistener onchooseletterchangedlistener) {
    this.onchooseletterchangedlistener = onchooseletterchangedlistener;
  }
}

hintsidebar通过回调接口来更新居中textview的文本内容和可见性

使用到的布局

<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <com.czy.demo.sidebar
    android:id="@+id/sidebar"
    android:layout_width="30dp"
    android:layout_height="match_parent"
    android:layout_alignparentright="true" />

  <textview
    android:id="@+id/tv_hint"
    android:layout_width="70dp"
    android:layout_height="70dp"
    android:layout_centerinparent="true"
    android:background="#4b0e0e0e"
    android:gravity="center"
    android:textcolor="#ffffff"
    android:textsize="30sp"
    android:visibility="invisible" />

</relativelayout>

此时就完成了索引view的绘制,不过定位功能还需要再通过回调接口来完成。

引入jar包后,先来设定一个工具类,包含一个可以解析字符串的方法,返回值为首字符对应的拼音首字母或者为包含一个空格的char类型数据。

public class utils {

  /**
   * 如果字符串的首字符为汉字,则返回该汉字的拼音大写首字母
   * 如果字符串的首字符为字母,也转化为大写字母返回
   * 其他情况均返回' '
   *
   * @param str 字符串
   * @return 首字母
   */
  public static char getheadchar(string str) {
    if (str != null && str.trim().length() != 0) {
      char[] strchar = str.tochararray();
      char headchar = strchar[0];
      //如果是大写字母则直接返回
      if (character.isuppercase(headchar)) {
        return headchar;
      } else if (character.islowercase(headchar)) {
        return character.touppercase(headchar);
      }
      // 汉语拼音格式输出类
      hanyupinyinoutputformat hanyupinoutputformat = new hanyupinyinoutputformat();
      hanyupinoutputformat.setcasetype(uppercase);
      hanyupinoutputformat.settonetype(without_tone);
      if (string.valueof(headchar).matches("[\\u4e00-\\u9fa5]+")) {
        try {
          string[] stringarray = pinyinhelper.tohanyupinyinstringarray(headchar, hanyupinoutputformat);
          if (stringarray != null && stringarray[0] != null) {
            return stringarray[0].charat(0);
          }
        } catch (badhanyupinyinoutputformatcombination e) {
          return ' ';
        }
      }
    }
    return ' ';
  }

}

然后再定义一个实体类,包含用户名,电话,用户名首字符的拼音首字母等三个属性
需要实现comparable 接口,用于排序

public class user implements comparable {

  private string username;

  private string phone;

  private char headletter;

  public user(string username, string phone) {
    this.username = username;
    this.phone = phone;
    headletter = utils.getheadchar(username);
  }

  public string getusername() {
    return username;
  }

  public string getphone() {
    return phone;
  }

  public char getheadletter() {
    return headletter;
  }

  @override
  public boolean equals(object object) {
    if (this == object) {
      return true;
    }
    if (object == null || getclass() != object.getclass()) {
      return false;
    }
    user that = (user) object;
    return getusername().equals(that.getusername()) && getphone().equals(that.getphone());
  }

  @override
  public int compareto(object object) {
    if (object instanceof user) {
      user that = (user) object;
      if (getheadletter() == ' ') {
        if (that.getheadletter() == ' ') {
          return 0;
        }
        return -1;
      }
      if (that.getheadletter() == ' ') {
        return 1;
      } else if (that.getheadletter() > getheadletter()) {
        return -1;
      } else if (that.getheadletter() == getheadletter()) {
        return 0;
      }
      return 1;
    } else {
      throw new classcastexception();
    }
  }

}

主布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<framelayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v7.widget.recyclerview
    android:id="@+id/rv_userlist"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

  <com.czy.demo.hintsidebar
    android:id="@+id/hintsidebar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="right" />

</framelayout>

联系人列表使用的是recyclerview,还需要定义一个adapter

public class useradapter extends recyclerview.adapter<useradapter.userholder> {

  private list<user> userlist;

  private layoutinflater inflater;

  public useradapter(context context) {
    inflater = layoutinflater.from(context);
    userlist = new arraylist<>();
  }

  @override
  public userholder oncreateviewholder(viewgroup parent, int viewtype) {
    view view = inflater.inflate(r.layout.item_user, parent, false);
    return new userholder(view);
  }

  @override
  public void onbindviewholder(userholder holder, int position) {
    holder.tv_username.settext(userlist.get(position).getusername());
    holder.tv_phone.settext(userlist.get(position).getphone());
  }

  public void setdata(list<user> userlist) {
    this.userlist.clear();
    this.userlist = userlist;
  }

  public int getfirstpositionbychar(char sign) {
    if (sign == '#') {
      return 0;
    }
    for (int i = 0; i < userlist.size(); i++) {
      if (userlist.get(i).getheadletter() == sign) {
        return i;
      }
    }
    return -1;
  }

  @override
  public int getitemcount() {
    return userlist.size();
  }

  class userholder extends recyclerview.viewholder {

    public textview tv_username;

    public textview tv_phone;

    public userholder(view itemview) {
      super(itemview);
      tv_username = (textview) itemview.findviewbyid(r.id.tv_username);
      tv_phone = (textview) itemview.findviewbyid(r.id.tv_phone);
    }
  }

}

以下方法用于获取联系人列表中第一个首字符为sign的item的位置

public int getfirstpositionbychar(char sign)

主activity代码如下

public class mainactivity extends appcompatactivity implements sidebar.onchooseletterchangedlistener {

  private list<user> userlist;

  private useradapter adapter;

  private recyclerview rv_userlist;

  private linearlayoutmanager manager;

  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    supportrequestwindowfeature(window.feature_no_title);
    setcontentview(r.layout.activity_main);
    hintsidebar hintsidebar = (hintsidebar) findviewbyid(r.id.hintsidebar);
    rv_userlist = (recyclerview) findviewbyid(r.id.rv_userlist);
    hintsidebar.setonchooseletterchangedlistener(this);
    manager = new linearlayoutmanager(this, linearlayoutmanager.vertical, false);
    rv_userlist.setlayoutmanager(manager);
    userlist = new arraylist<>();
    adapter = new useradapter(this);
    initdata();
    adapter.setdata(userlist);
    rv_userlist.setadapter(adapter);
  }

  @override
  public void onchooseletter(string s) {
    int i = adapter.getfirstpositionbychar(s.charat(0));
    if (i == -1) {
      return;
    }
    manager.scrolltopositionwithoffset(i, 0);
  }

  @override
  public void onnochooseletter() {

  }
}

initdata()用于向adapter填充数据

public void initdata() {
    user user1 = new user("陈", "12345678");
    user user2 = new user("赵", "12345678");
    ...
    userlist.add(user1);
    userlist.add(user2);
    ...
    collections.sort(userlist);
    adapter.notifydatasetchanged();
  }

这样,整个效果就都完成了。

这里提供代码下载:android 仿微信通讯录列表侧边栏

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

上一篇:

下一篇: