常见的联系人列表 A—Z排序功能,获取数据首字母,仿照联系人实现A-Z字母排序,实现字母索引定位功能;监听字母滑动,使recycleview滑动到指定位置;
先上效果图:
下面介绍实现逻辑:
自定义 SideBar + Recycleview 实现此功能,
第一步 自定义SideBar:onDraw()方法里 画出字母 sidebar:
获取画布高度于宽度,计算每个字母的高度,for循环画字母,此时,计算每个字母所对应的位置:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();// 获取对应高度
int width = getWidth();// 获取对应宽度
int singleHeight = height / 28;// 获取每一个字母的高度 这儿可以是动态获取字母长度letterList.size()
for (int i = 0; i < letterList.size(); i++) {
paint.setColor(Color.parseColor("#ffffff"));
//paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(30);
// 选中的状态
if (i == choose) {
paint.setColor(Color.parseColor("#ff0000"));
paint.setFakeBoldText(true);
}
// x坐标等于中间-字符串宽度的一半.
float xPos = width / 2 - paint.measureText(letterList.get(i)) / 2;
//y坐标是 上边距 +每个高度*i+一个字母高度的一半
float yPos = (getHeight()-letterList.size()*singleHeight)/2+singleHeight * i + singleHeight / 2;
canvas.drawText(letterList.get(i), xPos, yPos, paint);
paint.reset();// 重置画笔
}
}
重写 dispatchTouchEvent()方法:监听SideBar点击位置,获取点击位置字母,以及点击背景颜色变化,触摸字母变化等
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
// 点击y坐标
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
//获取触摸位置是第几个字母
final int position = (int) (y -((getHeight()-letterList.size()*(getHeight()/28))/2))/(getHeight()/28);// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数.
switch (action) {
case MotionEvent.ACTION_UP:
//触摸抬起手恢复默认背景色
setBackgroundResource(R.drawable.sidebar_unselect_background);
choose = -1;
invalidate();
if (mTextDialog != null) {
mTextDialog.setVisibility(View.GONE);
}
break;
default:
//设置选中颜色(按下,滑动...)
setBackgroundResource(R.drawable.sidebar_background);
if (oldChoose != position) {
if (position >= 0 && position < letterList.size()) {
if (listener != null) {
//触摸字母变化监听
listener.onTouchingLetterChanged(letterList.get(position));
}
if (mTextDialog != null) {
mTextDialog.setText(letterList.get(position));
mTextDialog.setVisibility(View.VISIBLE);
}
choose = position;
invalidate();
}
}
break;
}
return true;
}
下面附上自定义SideBar 完整代码:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import com.ss.wavesidebardemo.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 右侧的字母索引View
*/
public class SideBar extends View {
private Context mcontext;
// 这个根据ZimuComparator 中顺序来的
public static String[] INDEX_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 OnTouchingLetterChangedListener onTouchingLetterChangedListener;
private List letterList;
private int choose = -1;
private Paint paint = new Paint();
private TextView mTextDialog;
public SideBar(Context context) {
this(context, null);
mcontext = context;
}
public SideBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
mcontext = context;
}
public SideBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mcontext = context;
init();
}
private void init() {
setBackgroundResource(R.drawable.sidebar_unselect_background);
letterList = Arrays.asList(INDEX_STRING);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();// 获取对应高度
int width = getWidth();// 获取对应宽度
int singleHeight = height / 28;// 获取每一个字母的高度 这儿可以是动态获取字母长度letterList.size()
for (int i = 0; i < letterList.size(); i++) {
paint.setColor(Color.parseColor("#ffffff"));
//paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(30);
// 选中的状态
if (i == choose) {
paint.setColor(Color.parseColor("#ff0000"));
paint.setFakeBoldText(true);
}
// x坐标等于中间-字符串宽度的一半.
float xPos = width / 2 - paint.measureText(letterList.get(i)) / 2;
//y坐标是 上边距 +每个高度*i+一个字母高度的一半
float yPos = (getHeight() - letterList.size() * singleHeight) / 2 + singleHeight * i + singleHeight / 2;
canvas.drawText(letterList.get(i), xPos, yPos, paint);
paint.reset();// 重置画笔
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
// 点击y坐标
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
//获取触摸位置是第几个字母
final int position = (int) (y - ((getHeight() - letterList.size() * (getHeight() / 28)) / 2)) / (getHeight() / 28);// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数.
switch (action) {
case MotionEvent.ACTION_UP:
//触摸抬起手恢复默认背景色
setBackgroundResource(R.drawable.sidebar_unselect_background);
choose = -1;
invalidate();
if (mTextDialog != null) {
mTextDialog.setVisibility(View.GONE);
}
break;
default:
//设置选中颜色(按下,滑动...)
setBackgroundResource(R.drawable.sidebar_background);
if (oldChoose != position) {
if (position >= 0 && position < letterList.size()) {
if (listener != null) {
//触摸字母变化监听
listener.onTouchingLetterChanged(letterList.get(position));
}
if (mTextDialog != null) {
mTextDialog.setText(letterList.get(position));
mTextDialog.setVisibility(View.VISIBLE);
}
choose = position;
invalidate();
}
}
break;
}
return true;
}
public void setIndexText(ArrayList indexStrings) {
this.letterList = indexStrings;
invalidate();
}
/**
* 为SideBar设置显示当前按下的字母的TextView
*
* @param mTextDialog
*/
public void setTextView(TextView mTextDialog) {
this.mTextDialog = mTextDialog;
}
/**
* 向外公开的方法:触摸监听
*
* @param onTouchingLetterChangedListener
*/
public void setOnTouchingLetterChangedListener(
OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}
/**
* 接口
*/
public interface OnTouchingLetterChangedListener {
void onTouchingLetterChanged(String s);
}
}
下面获取数据,对数据进行排序: 我这儿是一些地名数据:获取数据对应的首字母,对首字母进行排序
//获取数据所对应的拼音,拿到首字母排序 A - Z没有的不显示
private List filledData(List list){
List mSortList = new ArrayList<>();
ArrayList indexString = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
//设置数据
CityBean sortModel = new CityBean();
sortModel.setName(list.get(i).name);
sortModel.setId(list.get(i).id);
//获取拼音
String pinyin = PinyinUtils.getPingYin(list.get(i).name);
//获取拼音首字母
String sortString = pinyin.substring(0, 1).toUpperCase();
//因个人功能需求,第一个为 “热” 可为Recycleview头布局
if (!indexString.contains("热")) {
//对应的头布局
indexString.add("热");
}
//如果首字母为A-Z
if (sortString.matches("[A-Z]")) {
sortModel.setSortLetters(sortString.toUpperCase());
if (!indexString.contains(sortString)) {
indexString.add(sortString);
}
}else{
//剩下为# 或者 ☆
sortModel.setSortLetters("#");
if (!indexString.contains("#")) {
indexString.add("#");
}
}
mSortList.add(sortModel);
}
//对首字母列表进行排序
Collections.sort(indexString, new ZimuComparator());
if (indexString.size()>0){
//sidebar 设置字母列表
sidebar.setIndexText(indexString);
}
return mSortList;
}
字母排序所遵循的准则为:
/**
* 用来对ListView中的数据根据A-Z进行排序 这儿排序的是seekbar显示的字母顺序
*/
public class ZimuComparator implements Comparator {
public int compare(String o1, String o2) {
//这里主要是用来对ListView里面的数据根据ABCDEFG...来排序
if (o1.equals("@")
|| o2.equals("#")) {
return -1;
} else if (o1 .equals("#")
|| o2 .equals("@")) {
return 1;
}else if (o1 .equals("热")
|| o2 .equals("热")) {
return 1;
} else {
return o1. compareTo(o2);
}
}
}
对数据列表进行按字母排序:
Collections.sort(SourceDateList, new PinyinComparator());
/**
* 用来对ListView中的数据根据A-Z进行排序
*/
public class PinyinComparator implements Comparator {
public int compare(CityBean o1, CityBean o2) {
//这里主要是用来对ListView里面的数据根据ABCDEFG...来排序,前面两个if判断主要是将不是以汉字开头的数据放后面
if (o1.getSortLetters().equals("@")
|| o2.getSortLetters().equals("#")) {
return -1;
} else if (o1.getSortLetters().equals("#")
|| o2.getSortLetters().equals("@")) {
return 1;
} else {
return o1.getSortLetters().compareTo(o2.getSortLetters());
}
//这里主要是用来对ListView里面的数据根据ABCDEFG...来排序,前面两个if判断主要是将不是以汉字开头的数据放前面
/*if (o1.getSortLetters().equals("@")
|| o2.getSortLetters().equals("#")) {
return 1;
} else if (o1.getSortLetters().equals("#")
|| o2.getSortLetters().equals("@")) {
return -1;
} else {
return o1.getSortLetters().compareTo(o2.getSortLetters());
}*/
}
}
下面开始监听字母滑动,使recycleview滑动到指定位置:这儿我用的滚动效果,如果不需要,可以直接调用 scrollToPosition 到指定位置;
scrollToPosition: 定位到指定项
smoothScrollToPosition:平滑到指定项
scrollToPositionWithOffset:定位到指定项如果该项可以置顶就将其置顶显示
private void setListener() {
recycler_view.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (mShouldScroll && RecyclerView.SCROLL_STATE_IDLE == newState){
mShouldScroll = false;
smoothMoveToPosition(recycler_view, mToPosition);
}
}
});
//设置右侧触摸监听
sidebar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() {
@Override
public void onTouchingLetterChanged(String s) {
//该字母首次出现的位置
int position = listAdapter.getPositionForSection(s.charAt(0));
if (position != -1) {
smoothMoveToPosition(recycler_view,position);
}else {
smoothMoveToPosition(recycler_view,position+1);
}
}
});
}
//目标项是否在最后一个可见项之后
private boolean mShouldScroll;
//记录目标项位置
private int mToPosition;
/**
* 滑动到指定位置
*/
private void smoothMoveToPosition(RecyclerView mRecyclerView, final int position) {
// 第一个可见位置
int firstItem = mRecyclerView.getChildLayoutPosition(mRecyclerView.getChildAt(0));
// 最后一个可见位置
int lastItem = mRecyclerView.getChildLayoutPosition(mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1));
if (position < firstItem) {
//防止数据太多 滚动时间过长 可自行设置
if(firstItem-position > 100) {
mRecyclerView.scrollToPosition(position+50);
}
// 如果跳转位置在第一个可见位置之前
mRecyclerView.smoothScrollToPosition(position);
} else if (position <= lastItem) {
// 跳转位置在第一个可见项之后,最后一个可见项之前,调用smoothScrollBy来滑动到指定位置
int movePosition = position - firstItem;
if (movePosition >= 0 && movePosition < mRecyclerView.getChildCount()) {
int top = mRecyclerView.getChildAt(movePosition).getTop();
mRecyclerView.smoothScrollBy(0, top);
}
} else {
//防止数据太多 滚动时间过长 可自行设置
if(position-firstItem > 100) {
mRecyclerView.scrollToPosition(position-50);
}
// 如果要跳转的位置在最后可见项之后,则先调用smoothScrollToPosition将要跳转的位置滚动到可见位置
// 再通过onScrollStateChanged控制再次调用smoothMoveToPosition,执行上一个判断中的方法
mRecyclerView.smoothScrollToPosition(position);
mToPosition = position;
mShouldScroll = true;
}
}
下面附上demo下载地址:
https://download.csdn.net/download/shanshan_1117/10855995