android快速索引的实现


从图中可以看出,这种快速索引在客户端的运用非常多,例如:快速检索城市,快速检索联系人等。接下来就来看看如何实现这种效果吧。


画字母
        要实现这个效果, 先得把右侧的字母条画出来, 这里我们写个类, 继承自 View, 由于其内部不需要包含其他布局, 所以继承 View 即可, 无需继承 ViewGroup.

public class QuickIndexBar extends View {
    private static final String TAG = "TAG";
    private static final 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"};
    private Paint mPaint;
    
    public interface OnLetterChangeListener{
        void OnLetterChange(String letter);
    }
    public QuickIndexBar(Context context) {
        this(context, null);
    }
    public QuickIndexBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public QuickIndexBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle); 
        // 初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
        mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, getResources().getDisplayMetrics()));
        mPaint.setTypeface(Typeface.DEFAULT_BOLD);
    }  
}


既然要画字母, 就要有画笔, 这里在构造方法里完成画笔的初始化, 创建一个抗锯齿, 颜色为白色, 大小12sp, 粗体的画笔. 有了画笔, 就要开始画了, 画法如图所示。


        这里面要注意的是, 使用 Canvas 画文字的时候, 是从左下角开始的.


@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mHeight = getMeasuredHeight();
        mCellHeight = mHeight * 1.0f / LETTERS.length;
        mCellWidth = getMeasuredWidth();
    }  
    @Override
    protected void onDraw(Canvas canvas) {
        // 绘制字母
        for (int i = 0; i < LETTERS.length; i++) {
            String text = LETTERS[i];
            // 求x坐标
            int x = (int) (mCellWidth / 2.0f - mPaint.measureText(text) / 2.0f);
            // 求y坐标
            // 格子高度的一半 + 文字高度的一半 + 其上边所有格子高度
            Rect bounds = new Rect();
            mPaint.getTextBounds(text, 0, text.length(), bounds);
            int y = (int) (mCellHeight / 2.0f + bounds.height() / 2.0f + mCellHeight * i);
            canvas.drawText(text, x, y, mPaint);
        }
    }

        这样一来, 字母就画出来了, 如果想要更自由一些的话, 可以使用自定义属性传入字体颜色. 


触摸事件和回调
        界面效果有了, 接下来就是处理触摸事件以及回调了, 处理触摸事件肯定是重写 onTouchEvent 方法了, 回调的话, 定义一个回调接口, 提供 get/set 方法, 在 onTouchEvent 相应的位置调用. onTouchEvent 代码如下:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (MotionEventCompat.getActionMasked(event)) {
            case MotionEvent.ACTION_DOWN:
                // 根据y值获取当前触摸到的字母
                float y = event.getY();
                int index = (int) (y / mCellHeight);
                // 如果字母索引发生变化
                if(index != touchIndex){
                    if(index >= 0 && index < LETTERS.length){
                        Log.d(TAG, LETTERS[index]);
                        if(mLetterChangeListener != null){
                            // 执行回调
                            mLetterChangeListener.OnLetterChange(LETTERS[index]);
                        }
                    }
                    touchIndex  = index;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                // 根据y值获取当前触摸到的字母
                int i = (int) (event.getY() / mCellHeight);
                // 如果字母索引发生变化
                if(i != touchIndex){
                    if(i >= 0 && i < LETTERS.length){
                        Log.d(TAG, LETTERS[i]);
                        if(mLetterChangeListener != null){
                            mLetterChangeListener.OnLetterChange(LETTERS[i]);
                        }
                    }
                    touchIndex  = i;
                }
                break;
            case MotionEvent.ACTION_UP:
                // 恢复默认索引值
                touchIndex = -1;
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

回调接口:
    public interface OnLetterChangeListener{
        void OnLetterChange(String letter);
    }
    // 字母变化监听
    private OnLetterChangeListener mLetterChangeListener;
    public OnLetterChangeListener getLetterChangeListener() {
        return mLetterChangeListener;
    }
    public void setLetterChangeListener(
            OnLetterChangeListener mLetterChangeListener) {
        this.mLetterChangeListener = mLetterChangeListener;
    }

ListView 的处理

首先我们要获取一个所有名字的集合, 并且对它按照拼音顺序排序.
private void fillAndSort(ArrayList<Friend> names) {
        for (int i = 0; i < NAMES.length; i++) {
            names.add(new Friend(NAMES[i]));
        }
        Collections.sort(names);
    }  
Friend 类如下:
public class Friend implements Comparable<Friend>{
    private String name;
    private String pinyin;
    public Friend(String name) {
        super();
        this.name = name;
        // 获取拼音
        pinyin = PinyinUtils.getPinyin(name);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPinyin() {
        return pinyin;
    }
    public void setPinyin(String pinyin) {
        this.pinyin = pinyin;
    }
    @Override
    public int compareTo(Friend another) {
        return pinyin.compareTo(another.getPinyin());
    }
}
        那么接下来就是 ListView 的 Adapter 了, 这里要注意一点就是, 相同字母开头的名字, 只有第一个显示, 其他的不显示.
@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = convertView;
        if(convertView == null){
            view = View.inflate(mContext, R.layout.item_list, null);
        }
        ViewHolder mViewHolder = ViewHolder.getHolder(view);
        Friend friend = names.get(position);
        // 跟上一个进行比较,如果不同,则显示。
        String letter = null;
        String currentLetter = friend.getPinyin().charAt(0) + "";
        if(position == 0){
            // 第一个人直接显示
            letter = currentLetter;
        }else {
            // 获取上一个人的拼音
            String preLetter = names.get(position - 1).getPinyin().charAt(0) + "";
            if(!TextUtils.equals(preLetter, currentLetter)){
                letter = currentLetter;
            }
        }
        mViewHolder.mIndex.setVisibility(letter == null ? View.GONE : View.VISIBLE);
        if(letter != null){
            mViewHolder.mIndex.setText(letter);
        }
        mViewHolder.mName.setText(friend.getName());
        return view;
    }

每个 item 的布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/tv_index"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#555555"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:visibility="gone"
        android:text="A"
        android:textColor="#ffffff"
        android:textSize="20sp" /> 
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:text="宋江"
        android:textColor="#000000"
        android:textSize="22sp" />
</LinearLayout>
        也就是说, 其实每个条目都是有个字母索引, 有个名字, 只是首字母相同的名字, 只有第一个显示索引。最后就是在索引条上滑动的时候移动到 ListView 相应的位置了, 这个就是实现它提供的回调:
        QuickIndexBar mQuickIndexBar = (QuickIndexBar) findViewById(R.id.quickIndex);
        mQuickIndexBar.setLetterChangeListener(new OnLetterChangeListener() {
            @Override
            public void OnLetterChange(String letter) {
                Utils.showToast(getApplicationContext(), letter);
                // 执行ListView的定位方法
                for (int i = 0; i < names.size(); i++) {
                    Friend friend = names.get(i);
                    String l =friend.getPinyin().charAt(0) + "";
                    if(TextUtils.equals(letter, l)){
                        // 中断循环,快速定位
                        mListView.setSelection(i);
                        break;
                    }
                }
                
            }
        });

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值