很久没有写博客了,对自己又放松了很多。这篇博客本来早就要写了,迟迟拖到现在。今天这里要说的是,联系人列表,分组带索引,即联系人按字母顺序排列并分组,右边还有索引条。先看下效果:
就是这么一个效果,想必大家都想知道是怎么实现的吧。其实很简单,接下来我会慢慢讲解这个效果的实现。
首先我们分析这个联系人列表的组成:
1.联系人列表 ----- ListView
2.右边索引-------自定义View
3.浮动窗口------WindowManager
4.获取名称的首字母----PinYin4j(已经将github上的封装成了依赖包)
下面我将按照我的实现顺序进行说明:
1.先自定义右边索引。
2.设置浮动窗口。
3.添加列表数据,对数据进行排序,对列表进行分组。
4.添加列表与索引间的监听。
一、自定义索引
这里的索引,我把它命名为IndexView,IndexView 包含26个英文字母和“#”,每个字母所占据的区域位置,IndexView按下时显示黑色背景,每个字母都有文字颜色,触摸时浮动窗口显示不同的字母。这里,我们就有概念了,于是可以开始写代码了:
1.先定义基本的成员变量
/*字母表数组*/ private String letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ#"; private Paint mPaint; private int mTextColor; private int mTextSize; private int mPadding; private int mLetterWidth; private int mLetterHeight; //当被触摸时,绘制背景 private boolean isTouched; private int mBackgroundColor; private int mTransparentColor; //除了用于绘制外,更涉及到触摸事件 private ArrayList<Rect> mRects; private OnCharTouchEvent mListener; //上一次获取的字母 private String mPreLetter;
public class IndexView extends View
public IndexView(Context context) { super(context); init(context); } public IndexView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public IndexView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); }
private void init(Context context) { mPaint = new Paint(); mPaint.setAntiAlias(true); mTextColor = Color.parseColor("#666666"); mBackgroundColor = Color.parseColor("#bbbbbb"); mTransparentColor = Color.parseColor("#00000000"); mRects = new ArrayList<>(); }
因此这里能够得到每个字母的矩形位置:
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mRects.clear(); for (int i = 0; i < letter.length(); i++) { int x = 0; int y = mLetterHeight * i; Rect rect = new Rect(x, y, x + mLetterWidth, y + mLetterHeight); mRects.add(rect); } }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //widthMeasureSpec是父类中,调用该类的measure方法设置的 int heightSize = MeasureSpec.getSize(heightMeasureSpec); mLetterWidth = mLetterHeight = heightSize / letter.length(); mPadding = mLetterHeight / 5; mTextSize = mLetterHeight - mPadding * 2; setMeasuredDimension(mLetterWidth, heightSize); }
@Override protected void onDraw(Canvas canvas) { canvas.drawColor(isTouched ? mBackgroundColor : mTransparentColor); for (int i = 0; i < letter.length(); i++) { char c = letter.charAt(i); String s = String.valueOf(c); mPaint.setColor(mTextColor); mPaint.setTextSize(mTextSize); mPaint.setTextAlign(Paint.Align.CENTER); drawTextCenter(canvas, mPaint, s, mRects.get(i)); } }
/** * 将文本绘制在居中位置 * * @param canvas * @param paint * @param s * @param rect */ private void drawTextCenter(Canvas canvas, Paint paint, String s, Rect rect) { Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt(); int baseline = rect.top + (rect.bottom - rect.top - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top; paint.setTextAlign(Paint.Align.CENTER); canvas.drawText(s, rect.centerX(), baseline, paint); }
到这里,我们已经绘制完一个索引了。但是这是不够的,我们还需要为它添加触摸事件。
6.重写onTouchEvent方法。
/** * 接收到事件,进行处理,并消费掉 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: doDown(event); break; case MotionEvent.ACTION_MOVE: doMove(event); break; case MotionEvent.ACTION_UP: doCancelOrUp(); break; case MotionEvent.ACTION_CANCEL: doCancelOrUp(); break; } return true; }
ActionDown 事件:
private void doDown(MotionEvent event) { isTouched = true; int downX = (int) event.getX(); int downY = (int) event.getY(); String s = getTouchedLetter(downX, downY); Log.v("@s", s + ""); if (mListener != null) mListener.onTouch(s); invalidate(); }记录位置,根据位置获取对应的字母,然后设置监听事件。(这里,先理解,getTouchedLetter为通过坐标位置获取字母,mListener为监听事件,用于回调浮动窗口的)。
ActionMove事件:
/** * 移动时,判断选中的字母是否发生了变化,并进行回调 * * @param event */ private void doMove(MotionEvent event) { isTouched = true; int downX = (int) event.getX(); int downY = (int) event.getY(); String s = getTouchedLetter(downX, downY); if (s == null) return; if (mListener != null) { if (mPreLetter == null || (!mPreLetter.equalsIgnoreCase(s))) { mListener.onLetterChanged(mPreLetter, s); mPreLetter = s; } } invalidate(); }这里每次都要记录上一个的字母,如果字母发生变化,才回调。
ActionUp|ActionCancel事件:
isTouched = false; if (mListener != null) mListener.onRelease(); invalidate();触摸标志位设置为false,并回调关闭浮动窗口
通过位置获取字母,循环判断坐标位置,包含在哪个字母的Rect矩形位置中,通过这个位置获取字母。
private String getTouchedLetter(int x, int y) { for (int i = 0; i < mRects.size(); i++) { Rect rect = mRects.get(i); if (rect.contains(x, y)) { return String.valueOf(letter.charAt(i)); } } return null; }
最后一个是监听接口:
public interface OnCharTouchEvent { void onTouch(String s); void onLetterChanged(String preLetter, String letter); void onRelease(); }
到这里,我们的效果是这样的:
今天先到这里,源码提前放出
源码