听音乐一直用的是网易云音乐,原来我就吐槽为什么列表没有字母索引,导致找一首歌很麻烦。不过最近滑动列表时,发现右边有了字母索引条,不知是原来没发现呢,还是网易新加的。既然酱紫,那就动手来实现一下这个字母索引条效果吧
网易云音乐字母索引条效果
国际惯例,先上我们自己实现的效果图
- 实现了网易云音乐字母索引的效果基础上,添加了选中字母变色的效果
效果实现分析
其实这个效果和我自定义View第二篇 实现一个简单的RatingBar评分条的效果大同小异,一点区别就是RatingBar那个是用图片绘制并且是水平方向的,而这个效果是垂直方向的画文字。实现思路可以去看下上一篇。
Talk is Cheap,下面直接上代码。看一下关键的实现
-
定义自定义属性
<declare-styleable name="LetterIndexView"> <!--字母文字颜色--> <attr name="defaultTextColor" format="color"/> <!--选中时字母文字颜色--> <attr name="selectTextColor" format="color"/> <!--字母文字大小--> <attr name="letterTextSize" format="dimension"/> </declare-styleable>
-
确定控件的宽高
- 宽度值为左右padding值 + 字母的宽度
- 高度值一般为match_parent,这里不做处理
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int oneLetterWidth = (int) mDefaultPaint.measureText("A"); //字母的宽度取决于画笔 int width = getPaddingLeft() + getPaddingRight() + oneLetterWidth; int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width, height); }
-
循环绘制26个英文字母
- itemHeight是每个字母区域的高度,控件高减去padding值 / 字母个数
- 绘制字母时要让字母都水平居中显示,这里要计算每个字母绘制的x坐标
- 触摸的字母和未触摸的字母使用两种颜色的画笔绘制,实现触摸变色
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); itemHeight = (getHeight() - getPaddingBottom() - getPaddingTop()) / letters.length; for (int i = 0; i < letters.length; i++) { //每个字母x的坐标 int x = (int) (getWidth() / 2 - mDefaultPaint.measureText(letters[i]) / 2); Paint.FontMetricsInt fontInt = mDefaultPaint.getFontMetricsInt(); //字母中心位置 int letterCenterY = itemHeight * i + itemHeight / 2 + getPaddingTop(); int dy = (fontInt.bottom - fontInt.top) / 2 - fontInt.bottom; int baseLine = letterCenterY + dy; if (i == mTouchIndex) { canvas.drawText(letters[i], x, baseLine, mSelectPaint); } else { canvas.drawText(letters[i], x, baseLine, mDefaultPaint); } Log.d("m1Ku", letters[i]); } }
-
定义接口回调,回调结果和状态
- letter表示选中的字母
- isShow表示用户是否在触摸滑动
public interface OnLetterTouchListener { void onTouchIndex(String letter, boolean isShow); } public void setOnLetterTouchListener(OnLetterTouchListener onLetterTouchListener) { this.onLetterTouchListener = onLetterTouchListener; }
-
监听用户的触摸和滑动,并回调结果
- 触摸滑动时isShow返回true,抬起时返回false。以便界面中处理提示字母显示和隐藏
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: float y = event.getY(); int index = (int) (y / itemHeight); if (index < 0) { index = 0; } if (index > letters.length - 1) { index = letters.length - 1; } Log.d("m1Ku", "index = " + index); if (index == mTouchIndex) { return true; } mTouchIndex = index; if (onLetterTouchListener != null) onLetterTouchListener.onTouchIndex(letters[mTouchIndex], true); Log.e("m1Ku", "mTouchIndex = " + mTouchIndex); invalidate(); break; case MotionEvent.ACTION_UP: if (onLetterTouchListener != null) onLetterTouchListener.onTouchIndex(letters[mTouchIndex], false); break; } return true; }
-
Activity中使用和测试
- 根据isShow控制界面提示字母显示和隐藏,并做了延迟消失
LetterIndexView letterIndexView = (LetterIndexView) findViewById(R.id.letterIndexView); letterIndexView.setOnLetterTouchListener(new LetterIndexView.OnLetterTouchListener() { @Override public void onTouchIndex(String letter, boolean isShow) { if (isShow) { tvIndicator.setVisibility(View.VISIBLE); tvIndicator.setText(letter); } else { handler.postDelayed(new Runnable() { @Override public void run() { tvIndicator.setVisibility(View.GONE); } }, 300); } } });
项目Github地址:https://github.com/m1Koi/CustomViewPractice
小结
类似这种效果主要是对宽高以及触摸位置的计算处理。当然,如果要实现和列表联动,还需要对列表数据进行排序等处理。不过在界面中我们已经可以拿到触摸的字母和索引,在进行下一步也不是难事了