先看效果图

这是比较常见的效果了吧
列表根据首字符的拼音字母来排序,且可以通过侧边栏的字母索引来进行定位

实现这样一个效果并不难,只要自定义一个索引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 仿微信通讯录列表侧边栏

更多相关文章

  1. Java读取Unicode文件(UTF-8等)时碰到的BOM首字符问题,及处理方法

随机推荐

  1. 艿艿连肝了几个周末,写了一篇贼长的 Sprin
  2. 艿艿周末又肝了一篇万字 Dubbo 文章,Sprin
  3. PMP备考经历-心理篇
  4. 又肝了下微服务 API 网关“金刚”,也是蛮
  5. 又一国产微服务网关 Soul,一起来肝一肝,Dub
  6. 硬核艿艿,新鲜出炉,直接带你弄懂 Spring Bo
  7. 44000 字 + 代码,艿艿肝的 Spring Securit
  8. Apollo 源码解析 —— Portal 发布配置
  9. 使用 Netty 实现 IM 聊天贼简单,看不懂就
  10. 帅气的 Spring Session 功能,基于 Redis