自定义通讯录字母索引

1、先来看下布局的效果

布局的代码如下,其中LetterIndexView为我们将要自定义的控件,使用相对布局置于界面的右侧;

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.itemp.letterindexview.MainActivity">

    <ListView
        android:id="@+id/lvFriends"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <TextView
        android:id="@+id/tvCurrentLetter"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@drawable/shape_letterindexview_bg_pressed"
        android:gravity="center"
        android:textSize="50sp"
        android:layout_centerInParent="true"
        android:textColor="@color/white"
        android:textStyle="bold"
        android:visibility="visible"
        android:text="A" />

    <com.itemp.letterindexview.LetterIndexView
        android:id="@+id/liv"
        android:layout_width="35dp"
        android:layout_height="match_parent"
        android:layout_margin="5dp"
        android:layout_alignParentRight="true"
        />

</RelativeLayout>

2、继承于View并使用绘图法在画布上绘制字母:

public class LetterIndexView extends View

3、实现构造方法,在其中初始化画笔,并为控件设置背景图(shape资源制作的圆角矩形)

    public LetterIndexView(Context context) {
        this(context, null);
    }

    public LetterIndexView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LetterIndexView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //setBackgroundResourece();
        setBackgroundResource(R.drawable.shape_letterindexview_bg);

        paint = new Paint();
        paint.setAntiAlias(true);//抗锯齿
    }

4、shape资源的定义代码:res/drawable/shape_letterindexview_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#fff" />
    <stroke
        android:width="1dp"
        android:color="#ddd" />
    <corners android:radius="10dp" />
</shape>

这是一个白色实心的圆角矩形,按下后将其变为黄色实心的圆角矩形,文件为res/drawable/shape_letterindexview_bg_pressed.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ff0" />
    <stroke
        android:width="1dp"
        android:color="#ddd" />
    <corners android:radius="10dp" />
</shape>

5、定义字符串数组作为索引的文本:

String[] letters = new String[]{
    "A", "B", "C", "D", "E", "F", "G",
    "H", "I", "J", "K", "L", "M", "N",
    "O", "P", "Q", "R", "S", "T",
    "U", "V", "W", "X", "Y", "Z", "#",
};

6、覆写onDraw()方法,将字母纵向排列均匀地绘制在画布上:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (width == 0) {
            width = getWidth();
            height = getHeight();
        }

        //把字母画在控件上,【选中字母】用红色画笔,否则黑色
        for (int i = 0; i < letters.length; i++) {

            //计算startX,控件宽度的一半减去字母宽度的一半
            String letter = letters[i];
            float letterSize = paint.measureText(letter);
            float startX = (width - letterSize) / 2;

            //startY,上方所有单元格的高度之和+(单元格高度的一半+字母高度的一半)
            float unitHeight = (height - 40) / 27f;
            float startY = 20 + i * unitHeight + (unitHeight + letterSize) / 2;

            //高亮字母为红色,否则为黑色
            if(i == currentPosition){
                paint.setColor(Color.RED);
            }else {
                paint.setColor(Color.BLACK);
            }

            paint.setTextSize(35);
            paint.setStyle(Paint.Style.FILL_AND_STROKE);//使用加粗效果
            canvas.drawText(letter, startX, startY, paint);//绘制字母
        }
    }

7、接下来覆写onTouchEvent()定义手指在控件上的滑动响应,逻辑为:
·手指按下,整个控件的背景色变为黄色,并根据手指按下的y的位置,确认哪个字母为选中字母,并重绘以将该字母高亮显示,并通知外界响应按下事件(比如显示小窗口见本文末尾GIF)
·手指滑动,动态改变选中字母,并重绘以将该字母高亮显示
·手指抬起,控件背景恢复为默认的白色,并通知外界响应(比如隐藏小窗口见文章末尾GIF)

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //改变背景效果
                setBackgroundResource(R.drawable.shape_letterindexview_bg_pressed);

                //根据手指位置设置高亮字母并重绘
                invalidateCurrentPosition(y);

                //通知外界手指按下
                if (callback != null) {
                    callback.onFingerDown(true);
                }
                break;

            case MotionEvent.ACTION_MOVE:
                //根据手指位置设置高亮字母并重绘
                invalidateCurrentPosition(y);

                //通知外界字母变化
                if (callback != null) {
                    callback.onLetterChanged(letters[currentPosition]);
                }
                break;

            case MotionEvent.ACTION_UP:
                //恢复背景效果
                setBackgroundResource(R.drawable.shape_letterindexview_bg);

                //通知外界手指抬起
                if (callback != null) {
                    callback.onFingerDown(false);
                }
                break;
        }
        return true;
    }

    /**
     * 根据手指位置动态设置高亮字母并重绘
     * @param y
     */
    private void invalidateCurrentPosition(float y) {
        currentPosition = (int) ((y / height) * letters.length);
        if(currentPosition > 26){
            currentPosition = 26;
        }
        invalidate();
    }

11、以接口的方式通知外界:手指按下或抬起,高亮字母发生改变:

    LetterIndexCallback callback;

    public void setCallback(LetterIndexCallback callback) {
        this.callback = callback;
    }

    public interface LetterIndexCallback {
        void onFingerDown(boolean down);

        void onLetterChanged(String letter);
    }

12、最后当外界ListView主动滚动时,字母索引的选中字母也随之变化,我们为外界提供公共方法,用于更新选中字母的位置:

    /**
     * 供外界ListView滚动时通知到当前控件
     * @param firstLetter
     */
    public void setCurrentLetter(String firstLetter) {
        for (int i = 0; i < letters.length; i++) {
            if(letters[i].equals(firstLetter)){
                setCurrentPosition(i);
                return;
            }
        }
    }

13、Activity实现【索引控件】的回调接口,并将自身设置给【索引控件】:

public class MainActivity extends AppCompatActivity implements LetterIndexView.LetterIndexCallback 
@Override
    public void onLetterChanged(String letter) {
        tvCurrentLetter.setText(letter);
    }

    @Override
    public void onFingerDown(boolean fingerDown) {
        if(fingerDown){
            tvCurrentLetter.setVisibility(View.VISIBLE);
        }else {
            tvCurrentLetter.setVisibility(View.GONE);
        }
    }
letterIndexView.setCallback(this);

效果如下:
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖的乔布梭

你好我是秦始皇转世,资助请从速

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值