这个做起来需要注意的是,字母的居中摆放和滑出控件后的特效关闭.
本人写的代码添加完整注释,当然如果哪里有问题,希望可以和大家一起讨论,不胜荣幸.
主要核心:
自定义控件类:
//先写自定义控件 1.继承View类 2.实现三个构造方法(分别有1,2,3个参数)
public class LetterIndexerView extends View {
private String[] mArrLetters = 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 TextView textView_dialog;
private int mLineHeight;
private float mDensity = 0;
private Paint mPaint;
public OnLetterClickedListener mListener;//接口回调2
public LetterIndexerView(Context context) {
super(context);
init(null, 0);
}
public LetterIndexerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public LetterIndexerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs, defStyleAttr);
}
//2.写一个初始化方法,在三个构造中都调用一下,内部对画笔进行初始化,需要用到像素转换时写出像素转换
private void init(AttributeSet attrs, int defStyleAttr) {
mDensity = getResources().getDisplayMetrics().density;//像素转换
mPaint = new Paint();//初始化画笔
// paint.setAntiAlias(true);
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);//去掉画笔的毛边,抗锯齿.和上面注释的那句一个效果
mPaint.setColor(Color.GRAY);//将画笔设置为灰色
mPaint.setTextSize(14 * mDensity);//给画笔设置画文本的文字大小,这里叫做画是因为画笔生成的文字.
}
//3.用来识别布局文件中的 match_parent\wrap_content\和固定值\比例等宽高信息,比onDraw先执行
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//这四句话是用来获取设置的宽高的信息,最好都写上这四句
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获得宽高的模式AT_MOST\EXACTLY\UNSPECIFIED
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);//获得宽高的尺寸
int widthPx = (int) (35 * mDensity);
setMeasuredDimension(widthPx, heightSize);//这里直接设置了固定的宽度,因为此自定义控件不需要使用者自己定义宽度
}
//4.在onMeasure后执行,对所需要显示的内容进行绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int viewWidth = getWidth();
int viewHeight = getHeight();//获取LetterIndexerView控件的宽高
mLineHeight = viewHeight / mArrLetters.length;//通过控件高度除以显示行数得出每行高度
for (int i = 0; i < mArrLetters.length; i++) {//循环画出27个数组中元素
//下面的计算为了让字母在控件居中显示
int textWwidth = (int) mPaint.measureText(mArrLetters[i]);//获得画笔要画出的字母的宽度
//viewWidth - textWwidth) / 2算出字母在哪里画会居中显示 mLineHeight * (i + 1)用来算出Y轴坐标
canvas.drawText(mArrLetters[i], (viewWidth - textWwidth) / 2, mLineHeight * (i + 1), mPaint);//画字母
}
}//这个方法写完之后就可以在布局文件中定义一个LetterIndexerView控件了,下面的方法是为了让控件具有一些显示效果的点击监听
//设置点击事件
//点击事件分为ACTION_UP(抬起)/ACTION_MOVE(移动)/ACTION_DOWN(按下)
@Override
public boolean onTouchEvent(MotionEvent event) {
float y = event.getY();//获得点击的位置的Y轴坐标,用来算显示哪个字母的User信息
//获取点击位置对应的字母索引
int position = (int) (y / mLineHeight);
if (position >= 0 && position < mArrLetters.length) {//判断点击的位置在LetterIndexerView的有效范围内,不判断会出现下标越界异常
switch (event.getAction()) {
case MotionEvent.ACTION_UP://当手指抬起时候执行
setBackgroundColor(Color.TRANSPARENT);
if (textView_dialog != null && textView_dialog.getVisibility() == View.VISIBLE) {
textView_dialog.setVisibility(View.INVISIBLE);
}//如果textView_dialog在显示的情况并且不为空,那么隐藏它
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_DOWN://这里的移动和按下效果一样所以用switch穿透写在一起就好
setBackgroundColor(Color.parseColor("#959595"));
if (textView_dialog != null && textView_dialog.getVisibility() == View.INVISIBLE) {
textView_dialog.setVisibility(View.VISIBLE);
}//按照条件判断理解就好,不难
textView_dialog.setText(mArrLetters[position]);//设置textView_dialog的内容
//接口回调
if (mListener != null) {
mListener.onClick(mArrLetters[position]);//接口回调3,固定这样写法
}//这里是接口回调的地方
break;
}
}else{//当手指划出的时候放此控件特效都消失
setBackgroundColor(Color.TRANSPARENT);
textView_dialog.setVisibility(View.INVISIBLE);
}
return true;
}
//接口回调1
public interface OnLetterClickedListener {
void onClick(String letter);
}
//为自定义控件设置自定义的监听方法
public void setOnLetterClickedListener(TextView _textView_dialog, OnLetterClickedListener _listener) {
this.textView_dialog = _textView_dialog;
this.mListener = _listener;
}
}
MainActivity:
private Context mContext = this;//定义上下文
private TextView textView_dialog;
private RecyclerView recyclerView_main;
private LetterIndexerView letterIndexerView_main;//定义三个布局中的控件
private MyRecyclerAdapter mAdapter = null;//定义适配器
private List<ItemBean> mTotalList = new ArrayList<>();//定义数据源集合,这里吧ItemBean写好,需要啥属性就不说了,按自己需要来
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
sortData();//将需要在onCreate中写的方法都抽取为单独的方法编程顺序initView-->initData-->sortData
}
private void initData() {
String[] arrUserNames = getResources().getStringArray(R.array.arrUsernames);
String[] arrIconUrls = getResources().getStringArray(R.array.arrIconUrl);
for (int i = 0; i < arrUserNames.length; i++) {
ItemBean itemBean = new ItemBean();
itemBean.setUserName(arrUserNames[i]);
itemBean.setIconUrl(arrIconUrls[i]);
//获取拼音和首字母
String pinyin = ChineseToPinyinHelper.getInstance().getPinyin(arrUserNames[i]);
itemBean.setPinyin(pinyin);
String firstLetter = pinyin.substring(0, 1).toUpperCase();
if (firstLetter.matches("[A-Z]")) {
itemBean.setFirstLetter(firstLetter);
} else {
itemBean.setFirstLetter("#");
}
mTotalList.add(itemBean);
}
}
private void sortData() {
Collections.sort(mTotalList, new Comparator<ItemBean>() {
@Override
public int compare(ItemBean lhs, ItemBean rhs) {
return lhs.getPinyin().toLowerCase().compareTo(rhs.getPinyin().toLowerCase());
}
});
}
//先来写初始化控件方法
private void initView() {
textView_dialog = (TextView) findViewById(R.id.textView_dialog);
recyclerView_main = (RecyclerView) findViewById(R.id.recyclerView_main);
letterIndexerView_main = (LetterIndexerView) findViewById(R.id.letterIndexerView_main);//三个findViewById自不必说
recyclerView_main.setHasFixedSize(true);//为RecyclerView设置固定尺寸,一般设置上,毕竟没几个不希望固定的,而设置了当内容超出也就不固定了
recyclerView_main.setItemAnimator(new DefaultItemAnimator());//设置动画效果(默认效果)
recyclerView_main.addItemDecoration(new DividerItemDecoration(mContext,
DividerItemDecoration.VERTICAL_LIST));//设置分割线,VERTICAL_LIST--横着的分割线
mAdapter = new MyRecyclerAdapter(mContext, mTotalList);//初始化适配器,RecyclerView必须设置适配器 ,在这里去写适配器
// 先把shangxiawen和数据源参数写上,写到这的时候数据源内还是空的得到内容从initData可以看到
final LinearLayoutManager layoutManager = new LinearLayoutManager(mContext);//定义一个布局管理器,RecyclerView需要用
recyclerView_main.setLayoutManager(layoutManager);//为RecyclerView设置布局管理器,不设置它的话内容出不来
recyclerView_main.setAdapter(mAdapter);//RecyclerView设置适配器
//下面这个监听器是在写完自定义控件的监听方法之后才写的,建议看的时候先看自定义控件的监听方法
letterIndexerView_main.setOnLetterClickedListener(textView_dialog, new LetterIndexerView.OnLetterClickedListener() {
@Override
public void onClick(String letter) {
//让RecyclerView滚动到指定position
int position = mAdapter.getPositionForSection(letter.charAt(0));
// textView_dialog.setText(letter);
layoutManager.scrollToPositionWithOffset(position, 0);
}
});
}
适配器就不贴出来了,自己写也不麻烦就是普通的适配器