效果图
导包:
compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'ca.barrenechea.header-decor:header-decor:0.2.6'
这里还用到一个Jar包
链接: https://pan.baidu.com/s/1X-dcGEomFlmMWu3QYx_iOg 密码: xi73
免费下载
1.直接复制拼音工具类
public class PinYinUtils {
public static String getPinYin(String text){
char[] chars = text.toCharArray();
StringBuilder sb = new StringBuilder();
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
//取消音调
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
//大写
format.setCaseType(HanyuPinyinCaseType.UPPERCASE);
for ( char ch : chars ) {
if(Character.isWhitespace(ch)){
//如果是空格
continue;
}
if(ch > 128 || ch < -127){
try{
//数组是有多音字
String[] array = PinyinHelper.toHanyuPinyinStringArray(ch, format);
sb.append(array[0]);
}catch (BadHanyuPinyinOutputFormatCombination e){
e.getMessage();
}
}else{
//#$%^
return "#";
}
}
return sb.toString();
}
}
2.ToastUtils 这个随意可以不复制
public class Utils {
private static Toast toast;
public static void showToast(Context context, String text){
if (toast == null)
toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
toast.setText(text);
toast.show();
}
}
3.我们先自定义一个右边的字母索引
public class QuickIndexBar extends View {
private Paint paint;
private float mCellHeight;
private int mWidth;
//26英文字母
private static final 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 int mHeight;
private float mTextHeight;
private int currentIndex = -1;
private OnLetterChangeListener onLetterChangeListener;
public OnLetterChangeListener getOnLetterChangeListener() {
return onLetterChangeListener;
}
public void setOnLetterChangeListener(OnLetterChangeListener onLetterChangeListener) {
this.onLetterChangeListener = onLetterChangeListener;
}
//暴露接口
public interface OnLetterChangeListener{
void onLetterChange(String letter);
void onReset();
}
public QuickIndexBar(Context context) {
this(context, null);
}
public QuickIndexBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QuickIndexBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
// 画笔默认是 黑色 设置为白色
//设置字体大小
paint.setTextSize(dip2px(context, 14));
//抗锯齿
paint.setAntiAlias(true);
// 获取字体的高度
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
// 下边界 - 上边界
//ceil 天花板 0.1 1
mTextHeight = (float) Math.ceil(fontMetrics.descent - fontMetrics.ascent);
}
// 测量完成 改变的时候调用
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//获取测量后的宽度和高度
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
//每个字母的高度
mCellHeight = mHeight * 1.0f / LETTERS.length;
}
// 怎么画
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//遍历并绘制26英文字母
for (int i = 0; i < LETTERS.length; i++) {
String text = LETTERS[i];
//测量字体宽度
float mTextWidth = paint.measureText(text);
//获取字母的xy坐标,坐标默认为字母左下角
float x = mWidth / 2 - mTextWidth / 2;
float y = mCellHeight / 2 + mTextHeight / 2 + mCellHeight * i;
//判断当前索引并绘制相应的颜色
if (currentIndex == i){
//当索引为当前的字母时绘制的颜色
paint.setColor(Color.parseColor("#000000"));
}else{
paint.setColor(Color.parseColor("#FF9696"));
}
// 字.画字();
canvas.drawText(text, x, y, paint);
}
}
//触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 计算当前点击的 字母
float downY = event.getY();
// 1.1 --- 1 1.4 --- 1 1.5 --- 1
currentIndex = (int) (downY / mCellHeight);
if (currentIndex < 0 || currentIndex > LETTERS.length - 1) {
} else {
// Utils.showToast(getContext(), LETTERS[currentIndex]);
if (onLetterChangeListener != null){
onLetterChangeListener.onLetterChange(LETTERS[currentIndex]);
}
}
//重新绘制
// invalidate();
break;
case MotionEvent.ACTION_MOVE:
// 计算当前点击的 字母
float moveY = event.getY();
currentIndex = (int) (moveY / mCellHeight); // 1.1 --- 1 1.4 --- 1 1.5 --- 1
if (currentIndex < 0 || currentIndex > LETTERS.length - 1) {
} else {
if (onLetterChangeListener != null){
onLetterChangeListener.onLetterChange(LETTERS[currentIndex]);
}
}
//重新绘制
// invalidate();
break;
case MotionEvent.ACTION_UP:
currentIndex = -1;
if (onLetterChangeListener != null){
onLetterChangeListener.onReset();
}
break;
}
//重新绘制
invalidate();
// 返回true 为了收到 move & up 事件
return true;
}
/**
* 根据手机的分辨率从 dip 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
4.然后到主函数
public class MainActivity extends AppCompatActivity {
private android.support.v7.widget.RecyclerView rv;
private QuickIndexBar QIBar;
public static final String[] NAMES = new String[] { "宋江", "卢俊义", "吴用",
"公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深",
"武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘",
"雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", "解珍",
" 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪",
"魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方",
"郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充",
"李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿",
"陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩",
"周通", "李忠", "杜兴", "汤隆", "邹渊", "邹润", "朱富", "朱贵", "蔡福", "蔡庆", "李立",
"李云", "焦挺", "石勇", "孙新", "顾大嫂", "张青", "孙二娘", " 王定六", "郁保四", "白胜",
"时迁", "段景柱" };
private List<ContactsBean> namelist = new ArrayList<>();
private LinearLayoutManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.rv = (RecyclerView) findViewById(R.id.id_recyclerview);
QIBar = (QuickIndexBar) findViewById(R.id.qib);
ContactsBean bean;
for (int i = 0; i < NAMES.length; i++) {
bean = new ContactsBean(NAMES[i]);
namelist.add(bean);
}
//对集合进行排序
Collections.sort(namelist);
//条目间的间隔线
DividerDecoration divider = new DividerDecoration.Builder(MainActivity.this)
.setHeight(R.dimen.default_divider_height)
.setColorResource(R.color.colorAccent)
.build();
manager = new LinearLayoutManager(MainActivity.this);
rv.setHasFixedSize(true);
rv.setLayoutManager(manager);
rv.addItemDecoration(divider);
final ContactsAdapter adapter = new ContactsAdapter(MainActivity.this, namelist);
adapter.setOnItemClickListener(new ContactsAdapter.OnItemClickListener() {
@Override
public void onClick(int position,String name) {
Toast.makeText(MainActivity.this,"您点击了"+position+"行"+name,Toast.LENGTH_SHORT).show();
}
@Override
public void onLongClick(int position,String name) {
Toast.makeText(MainActivity.this,"您长按点击了"+position+"行"+name,Toast.LENGTH_SHORT).show();
}
});
//设置悬浮索引
StickyHeaderDecoration decor = new StickyHeaderDecoration(adapter);
rv.setAdapter(adapter);
rv.addItemDecoration(decor, 1);
//侧拉索引改变监听
QIBar.setOnLetterChangeListener(new QuickIndexBar.OnLetterChangeListener() {
@Override
public void onLetterChange(String letter) {
for (int i = 0; i < namelist.size(); i++) {
if(letter.equals(namelist.get(i).pinyin.charAt(0) + "")) {
int position = adapter.getPositionForSection(namelist.get(i).pinyin.charAt(0));
if(position != -1){
//滑动到指定位置
manager.scrollToPositionWithOffset(position,0);
}
break;
}
}
}
@Override
public void onReset() {
}
});
QIBar.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
QIBar.setBackgroundColor(Color.parseColor("#ffe4e4"));
break;
case MotionEvent.ACTION_UP:
QIBar.setBackgroundColor(Color.argb(0,0,0,0));
break;
}
return false;
}
});
}
}
5.然后到到主布局
<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" >
<android.support.v7.widget.RecyclerView
android:id="@+id/id_recyclerview"
android:divider="#ffff0000"
android:dividerHeight="10dp"
android:background="#ffffff"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<fan.recyclerviewdemo.QuickIndexBar
android:layout_alignParentRight="true"
android:layout_width="23dp"
android:layout_height="match_parent"
android:padding="6dp"
android:id="@+id/qib" />
</RelativeLayout>
6.再写一个Recycler适配器
public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ViewHolder> implements
StickyHeaderAdapter<ContactsAdapter.HeaderHolder> {
private LayoutInflater mInflater;
private List<ContactsBean> namelist;
private OnItemClickListener mOnItemClickListener;
private char lastChar = '\u0000';
private int DisplayIndex = 0;
public ContactsAdapter(Context context, List<ContactsBean> namelist) {
mInflater = LayoutInflater.from(context);
this.namelist = namelist;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
final View view = mInflater.inflate(R.layout.item_contacts_item, viewGroup, false);
return new ViewHolder(view);
}
//条目文本填充
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int i) {
viewHolder.text_item.setText(namelist.get(i).name);
if( mOnItemClickListener!= null){
viewHolder.itemView.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.onClick(i,namelist.get(i).name);
}
});
viewHolder. itemView.setOnLongClickListener( new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mOnItemClickListener.onLongClick(i,namelist.get(i).name);
return false;
}
});
}
}
@Override
public int getItemCount() {
return namelist.size();
}
public long getHeaderId(int position) {
//这里面的是如果当前position与之前position重复(内部判断) 则不显示悬浮标题栏 如果不一样则显示标题栏
char ch = namelist.get(position).pinyin.charAt(0);
if(lastChar == '\u0000'){
lastChar = ch;
return DisplayIndex;
}else{
if(lastChar == ch){
return DisplayIndex;
}else{
lastChar = ch;
DisplayIndex ++ ;
return DisplayIndex;
}
}
}
public HeaderHolder onCreateHeaderViewHolder(ViewGroup parent) {
final View view = mInflater.inflate(R.layout.item_contacts_head, parent, false);
return new HeaderHolder(view);
}
//悬浮标题栏填充文本
public void onBindHeaderViewHolder(HeaderHolder viewholder, int position) {
viewholder.header.setText(namelist.get(position).pinyin.charAt(0) + "");
}
static class ViewHolder extends RecyclerView.ViewHolder {
public TextView text_item;
public ImageView imageView_item;
public ViewHolder(View itemView) {
super(itemView);
text_item = (TextView) itemView.findViewById(R.id.text_item);
imageView_item= (ImageView) itemView.findViewById(R.id.image_item);
}
}
static class HeaderHolder extends RecyclerView.ViewHolder {
public TextView header;
public HeaderHolder(View itemView) {
super(itemView);
header = (TextView) itemView;
}
}
/**
* 获得指定首字母的位置
* @param ch
* @return
*/
public int getPositionForSection(char ch){
for (int i = 0; i < getItemCount(); i++) {
char firstChar = namelist.get(i).pinyin.charAt(0);
if (firstChar == ch) {
return i;
}
}
return -1;
}
public interface OnItemClickListener{
void onClick( int position,String name);
void onLongClick( int position,String name);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener ){
this. mOnItemClickListener=onItemClickListener;
}
}
7.我们还需要头部和中间部分 然后创建这两个布局
item_contacts_head
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#F4F4F4"
android:textSize="12sp"
android:gravity="center_vertical"
android:paddingLeft="15dp"
android:paddingRight="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#333333"
tools:text="Sample text">
</TextView>
item_contacts_item
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="#ffffff">
<ImageView
android:id="@+id/image_item"
android:layout_width="38dp"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
android:layout_marginLeft="15dp"/>
<TextView
android:id="@+id/text_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="35dp"
android:gravity="center_vertical"
android:textSize="13sp"
tools:text="Sample text"
/>
</LinearLayout>
8.最后需要一个排序类
public class ContactsBean implements Comparable<ContactsBean>{
public String name;
public String pinyin;
private ImageView image;
public ContactsBean(String name){
this.name = name;
this.pinyin = PinYinUtils.getPinYin(name);
this.image = image;
}
@Override
public int compareTo(ContactsBean another) {
return this.pinyin.compareTo(another.pinyin);
}
}