Android通讯录字母排序城市列表展示效果

本篇文章主要给大家介绍一个通讯录列表字母A——Z排序展示的效果,其实很多场景都会用到,今天一个同事做城市列表也需要类似这样的效果,于是乎我就给他简单写了一个demo,刚好借此机会将此demo和效果展示给正在学习和需要用到该效果的android开发者。个人认为学习android是一个日积月累的过程,见得多了,写的多了,自身能力自然会提升,所以建议广大朋友可以平时多积累一些项目开发中常用的demo,这样我相信在工作中能极大的提高开发效率。时间长了,也会慢慢发现自己可以写一些简单的demo,可能哪一天别人也在学习着自己写的demo,这样岂不是一件很开心的事情。

还是先上效果图:
这里写图片描述

这里写图片描述
文章最底部有本篇文章Demo下载地址!

注意本篇文章以下几点:

  1. Demo的展示数据保存在本地assets中的drugs.xml中,通过解析XML文件或许数据集,其实解析完成后依然是一个List,所以这点大可放心,数据库定会返回集合数据的。
  2. 界面右边A——Z通过自定义View实现一个SideBar,定义触摸事件接口OnTouchingLetterChangedListener并实现一个public void onTouchingLetterChanged(String s);方法,在用户点击触摸右边SideBar时判斷点击是否在SideBar控件上,然后设置点击的字母即可。
  3. 将获取到的List集合通过拼音来进行A——Z排序展示,本篇文章用到”pinyin4j-2.5.0.jar”工具来进行转换操作。
    pinyin4j-2.5.0.jar下载地址:http://download.csdn.net/detail/jaynm/9570829
  4. 自定义EditText,获取EditText的DrawableRight,假如没有设置我们就使用默认的图片。

自定义SideBar实现右侧A——Z列表,创建数组String[]b,重写onDraw()方法,获取View对应的高度和宽度,然后获取到每一个字母对应的高度,遍历String[]b,给26个字母设置颜色、字体、类型等属性并且进行绘制。

/**
 * 重写onDraw()这个方法
 */
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 获取焦点改变背景颜色.
    int height = getHeight();// 获取对应高度
    int width = getWidth(); // 获取对应宽度
    int singleHeight = height / b.length;/// 获取每一个字母的高度

    for (int i = 0; i < b.length; i++) {
        paint.setColor(Color.parseColor("#CDCDCD"));
        // paint.setColor(Color.WHITE);
        paint.setTypeface(Typeface.DEFAULT_BOLD);
        paint.setAntiAlias(true);
        paint.setTextSize(20);
        // 选中的状态
        if (i == choose) {
            paint.setColor(Color.parseColor("#29333d"));
            paint.setFakeBoldText(true);
        }
        // x坐标等于中间-字符串宽度的一半.
        float xPos = width / 2 - paint.measureText(b[i]) / 2;
        float yPos = singleHeight * i + singleHeight;
        canvas.drawText(b[i], xPos, yPos, paint);
        paint.reset();// 重置画笔
    }

}

最重要的是需要重写dispatchTouchEvent(MotionEvent event)方法进行用户点击监听。event.getAction()获取手势状态,这里多说一个事件传递机制的知识点内容:
MotionEvent 常见的动作常量:

  • public static final int ACTION_DOWN = 0;单点触摸动作

  • public static final int ACTION_UP = 1;单点触摸离开动作

  • public static final int ACTION_MOVE = 2;触摸点移动动作

  • public static final int ACTION_CANCEL = 3;触摸动作取消

  • public static final int ACTION_OUTSIDE = 4;触摸动作超出边界

  • public static final int ACTION_POINTER_DOWN = 5;多点触摸动作

  • public static final int ACTION_POINTER_UP = 6;多点离开动作

Android事件传递机制前,明确android的两大基础控件类型:View和ViewGroup。View即普通的控件,没有子布局的,如Button、TextView. ViewGroup继承自View,表示可以有子控件,如Linearlayout、Listview这些。而事件即MotionEvent,最重要的有3个:
(1)MotionEvent.ACTION_DOWN 按下View,是所有事件的开始
(2)MotionEvent.ACTION_MOVE 滑动事件
(3)MotionEvent.ACTION_UP 与down对应,表示抬起

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    final int action = event.getAction();
    final float y = event.getY();// 点击y坐标
    final int oldChoose = choose;
    final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
    // 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数.
    final int c = (int) (y / getHeight() * b.length);

    switch (action) {
        case MotionEvent.ACTION_UP:
            setBackgroundDrawable(new ColorDrawable(0x00000000));
            choose = -1;//
            invalidate();
            if (mTextDialog != null) {
                mTextDialog.setVisibility(View.INVISIBLE);
            }
            break;
        default:
            setBackgroundResource(R.drawable.sidebar_background);
            if (oldChoose != c) {
                if (c >= 0 && c < b.length) {
                    if (listener != null) {
                        //回调接口
                        listener.onTouchingLetterChanged(b[c]);
                    }
                    if (mTextDialog != null) {
                        mTextDialog.setText(b[c]);
                        mTextDialog.setVisibility(View.VISIBLE);
                    }
                    choose = c;
                    invalidate();
                }
            }
            break;
    }
    return true;
}

对于View来说,事件传递机制有两个函数:dispatchTouchEvent负责分发事件,在dispatchTouchEvent里又会调用onTouchEvent表示执行事件,或者说消费事件,结合源码分析其流程。事件传递的入口是View的dispatchTouchEvent()函数:
本demo中只区分MotionEvent.ACTION_UP事件和默认状态,在默认状态下判断当前点击触摸的字母,然后OnTouchingLetterChangedListener接口回调当前点击的字母listener.onTouchingLetterChanged(b[c]);在ListView中更新列表数据。

// 设置右侧SideBar触摸监听
sideBar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() {

    @Override
    public void onTouchingLetterChanged(String s) {
        // 该字母首次出现的位置
        int position = adapter.getPositionForSection(s.charAt(0));
        if (position != -1) {
            sortListView.setSelection(position);
        }

    }
});

自定义EditText,添加删除按钮的引用Drawable mClearDrawable,然后获取EditText的DrawableRight,假如没有设置我们就使用默认的图片。
因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件,当我们按下的位置 在 EditText的宽度 - 图标到控件右边的间距 - 图标的宽度和EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向没有考虑。

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (getCompoundDrawables()[2] != null) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            boolean touchable = event.getX() > (getWidth()
                    - getPaddingRight() - mClearDrawable.getIntrinsicWidth())
                    && (event.getX() < ((getWidth() - getPaddingRight())));
            if (touchable) {
                this.setText("");
            }
        }
    }
    return super.onTouchEvent(event);
}

当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏,这里没有什么实际操作,就是在输入框右边添加一个删除按钮,当输入框内有内容时可以进行删除操作,内容为空时不显示删除引用图片。
设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去。

@Override
public void onFocusChange(View v, boolean hasFocus) {
    if (hasFocus) {
        setClearIconVisible(getText().length() > 0);
    } else {
        setClearIconVisible(false);
    }
}

protected void setClearIconVisible(boolean visible) {
    Drawable right = visible ? mClearDrawable : null;
    setCompoundDrawables(getCompoundDrawables()[0],
            getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
}

// 根据输入框输入值的改变来过滤搜索,并根据输入框中的值来过滤数据并更新ListView,注意这边开启一个新的线程来操作更新,因为大家都知道android4.0之后不允许主线程更新UI,所以如果直接更新可能会导致程序崩溃,所以还是建议大家开启一个新的线程或者异步来操作过滤数据并更新ListView的步骤

mClearEditText.addTextChangedListener(new TextWatcher() {

    @Override
    public void onTextChanged(CharSequence s, int start, int before,
            int count) {
        // 当输入框里面的值为空,更新为原来的列表,否则为过滤数据列表
        final String str = s.toString();
        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                filterData(str);
            }
        }) {

        }.start();
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {

    }

    @Override
    public void afterTextChanged(Editable s) {
    }
});
/**
 * 根据输入框中的值来过滤数据并更新ListView
 * 
 * @param filterStr
 */
private void filterData(String filterStr) {
    filterStr = filterStr.replace(" ", "");
    List<SortModel> filterDateList = new ArrayList<SortModel>();
    if (TextUtils.isEmpty(filterStr.replace(" ", ""))) {
        filterDateList = SourceDateList;
    } else {
        filterDateList.clear();
        String letter = Chinese.getInstance()
                .getCharacterPinYin(filterStr.charAt(0)).charAt(0)
                + "";
        String key = letter.toUpperCase();
        List<SortModel> list = map.get(key);
        for (SortModel sortModel : list) {
            String name = sortModel.getName();
            String pinYin = sortModel.getPinYin();
            if (!"".equals(pinYin)) {
                if (name.indexOf(filterStr.toString()) != -1
                        || pinYin.indexOf(filterStr.toLowerCase()) != -1) {
                    filterDateList.add(sortModel);
                }
            }
        }
    }
    Message msg = new Message();
    msg.obj = filterDateList;
    msg.what = 0;
    handler.sendMessage(msg);
}

大家可以看到将输入框中的数据拿到之后filterStr = filterStr.replace(” “,
“”);进行清空空格的操作,防止第一个字符用户输入空格搜索不到无法更新ListView的情况。
通过Chinese.getInstance().getCharacterPinYin(filterStr.charAt(0)).charAt(0)+
“”;获取到输入框的内容并转换成拼音,然后从Map中得到List,重新填充filterDateList数据,使用handler发送handler.sendMessage(msg)更新数据adapter.updateListView((List)
msg.obj)

好了,今天的分享就到这里,其实个人觉得很简单,使用起来也很容易,能够很轻松的应用到自己项目中。
可能其中有很多地方写的不太到位或者文章方式有待提高,还希望各位朋友能给我提出一些意见,在日后的分享博客中能够提高自身的能力。欢迎互相交流!
我是一个不喜欢读书的人,更别提写文章了。以前上CSDN找文章就是hongyang大神、郭霖大神、夏大神等等,因为之前刚开始工作的时候遇到问题找demo,基本上也就是看着他们的文章成长的。所以现在自己下定决心也要坚持写博客,坚持写一些有用的东西,希望能帮助所有android初学者和爱好者们!同事也是为了自己做记录,防止日后需要该功能或效果时忘记。所以鼓励每一位开发者都能养成写博客的习惯!
爱学习,爱编程,爱生活!

Demo下载地址:http://download.csdn.net/detail/jaynm/9570828

发布了37 篇原创文章 · 获赞 35 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/jaynm/article/details/51861137