效果图:
自定义view
的流程,具体请点此查看:自定义view套路
我们先重写构造器,然后重写onMeasure
函数进行测量设置宽高,在本例中,宽我是根据padding
和测量一个字母w
的宽度来设置的,高度就是默认的,因为一般是设置为match_parent
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public AlphabeticalIndexView(Context context) {
this(context, null);
}
public AlphabeticalIndexView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AlphabeticalIndexView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = (int) (getPaddingLeft() + getPaddingRight() + DisplayUtil.getTextWidth("W", mPaint));
int h = getMeasuredHeight();
setMeasuredDimension(w, h);
}
private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(DisplayUtil.sp2px(context, 15));
}
因为不是viewGroup
,所以不必重写onLayout
函数,我们直接进入onDraw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 每个字母所占用的高度
for (int i = 0; i < mLetters.length; i++) {
String letter = mLetters[i];
if (letter.equals(mCurrentTouchLetter))
mPaint.setColor(Color.BLUE);
else
mPaint.setColor(Color.GRAY);
// 获取字体的宽度
float measureTextWidth = mPaint.measureText(letter);
// 获取内容的宽度
int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
canvas.drawText(letter, getPaddingLeft() + (contentWidth - measureTextWidth) / 2, getPaddingTop() + mSingLetterHeight * i + DisplayUtil.getTextBaseLine(mPaint), mPaint);
}
}
这里可能有点疑惑,为什么要用内容的宽度减去字体的宽度然后除于2
,这是因为每个字母的宽度都不一样,如果不这样做的画,那么比如I
就会和A W
等字母左对齐,我们这样做的目的就是为了都居中。
这样我们就实现了一个静态的页面没我们要让他触摸改变,我们就重写onTouchEvent
进行操作,并且添加相应的回调。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 获取当前手指触摸的Y位置
float fingerY = ev.getY();
int pos = (int) (fingerY / mSingLetterHeight);
if (pos > -1 && pos < mLetters.length && !mLetters[pos].equals(mCurrentTouchLetter)) {
mCurrentTouchLetter = mLetters[pos];
triggerTouchListener(true);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
triggerTouchListener(false);
break;
}
return true;
}
private void triggerTouchListener(boolean isTouch) {
if (mTouchListener != null)
mTouchListener.onTouch(mCurrentTouchLetter, isTouch);
}
// 设置触摸监听
private SideBarTouchListener mTouchListener;
public void setOnSideBarTouchListener(SideBarTouchListener touchListener) {
this.mTouchListener = touchListener;
}
public interface SideBarTouchListener {
void onTouch(String letter, boolean isTouch);
}
这样我们就可以实现了这个功能点。
优化思路
一个view
绘制出来我们还需要进行优化,这一步是非常重要的。在本例中,我们的优化主要是在 onTouchEvent
里面,因为我们知道,invalidate
函数是一个一个做了很多事情的函数,具体请看:Android invalidate流程分析 我们要减少它的调用,所以我们应该先判断一下,如果当前的这个索引和触摸的这个不一样的话才调用invalidate
,这样的话就优雅很多了。在本例中我没有将需要改变的属性,比如字体大小、选中颜色、默认颜色等提取在attr
里面,需要的请自行添加。
参考:Android字母索引列表
源码github下载地址:
https://github.com/ChinaZeng/CustomView