自定义控件--快速索引(一)

来源:互联网 发布:大数据时时彩极限方案 编辑:程序博客网 时间:2024/04/30 06:16

  经常我们在联系人等应用上会看到点击一个字母就会自动跳转到当前选项,这就是快速索引,但是怎么做呢?初期看到这个控件内心的想法就是将屏幕的的高分成26份每份对应一个字母,点击字母通过回调传递当前字母,然后根据获取的字母去定位到当前条目


想法很好,我们实际去操作下,看看会遇到什么坑!自定义控件一定要多去操作多去练习,慢慢就会有感觉了!


1 首先我们自定义view就先去继承View.


2我们直接复写onDraw方法,先去写出一个字母AB

这里要注意两点

       一是要注意不要再onDraw方法中new对象,会造成大量无用对象,在初始化是设置一个画笔

public FastIndexingView(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    mPaint = new Paint();    mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);//抗锯齿    mPaint.setColor(Color.WHITE);    mPaint.setTextSize(30);    mPaint.setTypeface(Typeface.DEFAULT_BOLD);//字体加粗.    mPaint.setTextAlign(Paint.Align.CENTER);//居中对齐,这个很重要,不居中对齐,会以基线往右画}

       二是要注意这个坐标值10,10不是以文字所在矩形的左上角,而是一条基线,待会我会以这个坐标画一个圆大家就知道了

@Overrideprotected void onDraw(Canvas canvas) {    canvas.drawText("AB",10,25,mPaint);}
如上简单的一句,就会如图所示的效果


同理,我只需要多写几次,就可以了?

@Overrideprotected void onDraw(Canvas canvas) {    canvas.drawText("A",10,25,mPaint);    canvas.drawText("B",10,55,mPaint);    canvas.drawText("C",10,85,mPaint);    canvas.drawText("D",10,115,mPaint);    canvas.drawText("E",10,145,mPaint);}

怎么样是不是有点效果了?作为程序员,能用代码简单实现的干嘛一个一个敲?代码这样写,用个for循环,这里因为不问题,我改了下文字的大小(不影响代码阅读),此外,这里说下基线,就是画文字的初始位置,我这里以蓝色画笔画一个圆来观看,大家就知道位置了

for (int i = 0; i < LETTERS.length; i++) {   int x=10;   int y=i*30+10;    canvas.drawCircle(x,y,3,mCirclePaint);    canvas.drawText(LETTERS[i],x,y,mPaint);}

看下蓝色点的位置,好像还蛮齐的哦,差不多都在文字的中间,这是因为我给文字画笔设置了居中,如果我注销掉这句代码看看是什么样子

/*mPaint.setTextAlign(Paint.Align.CENTER);//居中对齐*/

看看,取消掉居中后,文字都画在了圆的右边,这里就是我上面所说的文字的基线,画文字时提供的坐标就是基线坐标,而不是以左上角为基线


现在我们来获取控件的宽度,并定义画文字基线以宽度的一半为准

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    mMeasuredWidth = getMeasuredWidth();}
@Overrideprotected void onDraw(Canvas canvas) {    for (int i = 0; i < LETTERS.length; i++) {       int x=mMeasuredWidth/2;       int y=i*30+20;        canvas.drawCircle(x,y,3,mCirclePaint);        canvas.drawText(LETTERS[i],x,y,mPaint);    }}

现在看看是不是有点美感了?但是这个高度怎么搞呢?一样的,把高度分成26份,每个文字占用一份!但是真的就这么简单么?年轻人始终还是太年轻啊,我们往下分析

Y知怎么去获取呢?

首先我们要获取的是y基线的值,因为这个画文字都是在这个基线Y值上画出的

那就是当前文字所占区域的一半加上字母高度的一半就是y的值      但这是第一个y的值,那如果第二个第三个呢?  那就是加上之前字母区域的高度

公式  :

averageHeight =mHeight/26

y = averageHeight *0.5 +textHegiht * 0.5 + i * averageHeight 


这里大家要问了,文字的高度怎么求呢?根据面向对象思想,文字的高度画笔肯定知道,那么去找找画笔的api,我直接卸载代码中解释

@Overrideprotected void onDraw(Canvas canvas) {    for (int i = 0; i < LETTERS.length; i++) {        Rect rect = new Rect();        mPaint.getTextBounds(LETTERS[i],0,1,rect);//获取画出文字的一些属性保存到Rect               int x=mMeasuredWidth/2;       int y=mAverageHeight/2 + rect.height()+mAverageHeight*i;//rect.height()其实就是buttom-top        canvas.drawCircle(x,y,3,mCirclePaint);        canvas.drawText(LETTERS[i],x,y,mPaint);    }}
然后我们再看下我们的代码运行起来是什么样子


恩恩,不错!是我想要的,等下去掉圆形就很ok了,即使屏幕旋转也不会有影响

好了,现在我们完成了第一步,这仅仅是一个view,一个没有任何功能的view,我们还需要添加监听回调,触摸事件,让他真正可以与用户交互



响应触摸事件,获取用户触摸的字母,触摸事件肯定想到onTouchEvent,我们需要处理事件,所以返回true

@Overridepublic boolean onTouchEvent(MotionEvent event) {    switch (event.getAction()) {        case MotionEvent.ACTION_DOWN:        break;        case MotionEvent.ACTION_MOVE:            break;        case MotionEvent.ACTION_UP:            break;    }    return true;}

但是我们如何知道用户触摸的是哪个字母呢?如何确定触摸的范围在这个区域内呢?首先用户按下时的坐标,我们只关心Y,X我们时不关心的,至于为什么不用我多解释吧?

我们按下的高度Y除以平均高度,除数是几按下的就是什么字母,这之间要做几次健壮性判断

@Overridepublic boolean onTouchEvent(MotionEvent event) {    int index = -1;    switch (event.getAction()) {        case MotionEvent.ACTION_DOWN:            getCurrentIndex(event);            break;        case MotionEvent.ACTION_MOVE:            getCurrentIndex(event);            break;        case MotionEvent.ACTION_UP:            break;    }    return true;}//获取当前的按下的字母private void getCurrentIndex(MotionEvent event) {    int index;    index = (int) (event.getY() / mAverageHeight);    if (index>=0&&index<LETTERS.length){//这里的判断是防止越界,导致异常        if (currentIndext!=index){  //只有跟上一次不同才能进来,其实就是一个提高性能的判断            Log.e(TAG, "onTouchEvent "+LETTERS[index]);            currentIndext = index;//记录当前索引        }    }}

设置一个监听回调,讲字母传递出去,这里的监听回调,我打算用两种实现方式讲一下,让大家更加了解接口回调,以及代码的耦合性,以及接口的好处

第一种 紧耦合,复用性低  

MainActivity写一个方法用来接收回调的字母

public void onCurrentString(String  str){    //在这里打印传递来的字符,把自定义view的触摸事件的log注释掉    Log.e(TAG, "onTouchEvent "+str);}
FastIndexingView 如果要调用MainActivity的方法就要用
MainActivity mMainActivity;public void setOnCurrentListener(MainActivity mainActivity) {    mMainActivity=mainActivity;}
private void getCurrentIndex(MotionEvent event) {    int index;    index = (int) (event.getY() / mAverageHeight);    if (index>=0&&index<LETTERS.length){//这里的判断是防止越界,导致异常        if (currentIndext!=index){  //只有跟上一次不同才能进来,其实就是一个提高性能的判断            if (mMainActivity!=null)                mMainActivity.onCurrentString(LETTERS[index]);            currentIndext = index;//记录当前索引                   }    }}

看看这个写法是不是一样能实现,接口回调?这样写是不是耦合性太紧了?只有MainActivity能获取到字母,如果是其他人想获取是不是要频繁改这个方法参数?


看看方法二的写法,用接口就可以解耦并且提高扩展性

在自定义view中定义接口

OnUpdateListener mOnUpdateListener;public interface OnUpdateListener{   void OnUpdate(String str);}public void setOnUpdateListener(OnUpdateListener onUpdateListener) {    mOnUpdateListener=onUpdateListener;}

触摸的方法中调用接口

private void getCurrentIndex(MotionEvent event) {    int index;    index = (int) (event.getY() / mAverageHeight);    if (index>=0&&index<LETTERS.length){//这里的判断是防止越界,导致异常        if (currentIndext!=index){  //只有跟上一次不同才能进来,其实就是一个提高性能的判断            if (mOnUpdateListener!=null)                mOnUpdateListener.OnUpdate(LETTERS[index]);            currentIndext = index;//记录当前索引                   }    }}
需要获取到触摸按下的字母就去实现接口,MainActivity实现接口方法

@Overridepublic void OnUpdate(String str) {    Log.e(TAG, "onTouchEvent "+str);}
成功

03-20 02:17:12.847 24524-24524/com.xu.fastindexing E/ContentValues: OnUpdate H
03-20 02:17:14.717 24524-24524/com.xu.fastindexing E/ContentValues: OnUpdate L


现在大家明白接口回调为什么要这么写了吧?很多人都会用接口回调,肯定会说 : 不就是创建一个set方法接收接口对象,赋值给接口对象,然后再要回调的地方做下接口的为null判断调用接口方法,需要调用的类去实现接口复写方法即可,大部分人都知道这么模式,但真正知道这么用的意义和好处,可能很多人就不明白了,不知道上面的两种方式是否让你明白了呢




0 0
原创粉丝点击