前言
自定义View实现快速检索
应用场景
联系人、好友列表、商品列表的快速定位和搜索
实现步骤
1、自定义一个QuickIndexView继承View,得到控件宽高
@Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
mBlockHeight = mHeight * 1.0f / indexArr.length;
}
onSizeChanged方法是在其控件大小确定后调用,所以在该方法内获取该控件的宽高。这里的mBlockHeight其实就是每个字符所占的高度。
2、绘制。将准备好的字符绘制在该控件上
/**
* 绘制显示的文字信息
*
* @param canvas
*/
@Override
protected void onDraw (Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < indexArr.length; i++) {
int x = mWidth / 2;
float y = mBlockHeight / 2 + getTextHeight(indexArr[i]) / 2 + i * mBlockHeight;
mLetterPaint.setColor(i == mLastIndex ? Color.BLACK : Color.WHITE);
canvas.drawText(indexArr[i], x, y, mLetterPaint);
}
}
这里绘制的话需要注意的就是文字的位置。如何去获取呢?
因为这个控件是将所有字符都等分,所以每个字符的所占格子高度就是控件总高度/字符的个数:
mBlockHeight = mHeight * 1.0f / indexArr.length;
这里*1.0f转换为float类型是为了控制精度,避免损失。
因为使用canvas绘制文本默认是从文字左下角往上绘制文字的,这里我们需要改为从文字下方中间往上绘制:
mLetterPaint.setTextAlign(Paint.Align.CENTER);
看下Align源码:
public enum Align {
/**
* The text is drawn to the right of the x,y origin
*/
LEFT (0),
/**
* The text is drawn centered horizontally on the x,y origin
*/
CENTER (1),
/**
* The text is drawn to the left of the x,y origin
*/
RIGHT (2);
我们只要改为Center就好了,
绘制的文字X坐标就是控件宽/2,Y坐标就是上面求得格mBlockHeight的/2+ 文本高度/2 + position*格子高度;
那么如何获取一个文本的高度呢,需要借助Rect :
/**
* 获取文本的高度
*
* @param text
* @return
*/
private int getTextHeight (String text) {
Rect bounds = new Rect();
mLetterPaint.getTextBounds(text, 0, text.length(), bounds);
return bounds.height();
}
到了这里,我们已经把字符在控件上绘制出来了
3、得到用户触摸的字符
实现逻辑:计算触摸点对应的字母:根据触摸点的y坐标除以cellHeight,得到的值就是字母对应的索引;
在onTouchEvent方法中当用户按下、或者移动时都需要获取到索引值,所以在ACTION_DOWN中就没有break:
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
mDownY = event.getY();
//得到点下时的索引
int index = (int) (mDownY / mBlockHeight);
break;
这里的index就是字符的索引.
到了这一步已经完成了不管用户是点击还是移动都可以获取到字符了。接下来就是与外界的交互了,我们需要把这个获取到的字符暴露给外界,方便他们进行一系列的操作。这里就需要用到最基本的接口回调了:
private OnLetterIndexListener mListener;
public void setOnLetterIndexListener (OnLetterIndexListener listener) {
mListener = listener;
}
public interface OnLetterIndexListener {
/**
* @param letter 点击的字符
*/
void onLetterIndex (String letter);
}
定义了这样一个接口之后,我们就需要把在OnTouchEvent中获取到的字符传递出去:
case MotionEvent.ACTION_MOVE:
mDownY = event.getY();
//得到点下时的索引
int index = (int) (mDownY / mBlockHeight);
if (index >= 0 && index <= indexArr.length && mLastIndex != index) {
mLastIndex = index;
if (mListener != null) {
mListener.onLetterIndex(indexArr[index]);
}
}
break;
这里加了个mLastIndex来记录最后触摸的字符索引,这是为了避免重复的回调,在ACTION_UP中我们也需要将mLastIndex重新初始化:
case MotionEvent.ACTION_UP:
//松开是默认初始值
mLastIndex = -1;
break;
到了这里这个控件就已经完成了。看看效果吧