Android添加@联系人功能

Android添加@联系人功能

年后聊天项目加了个@联系人功能的需求,网上找了几个Demo都不尽人意,按照需求需要处理的细节很多觉得稍显繁复,后来想到@功能也就和短信的添加收件人的功能差不多,就自己动手写了一个,这里分享出来给以供参考!

效果图
@功能

  1. 需求分析

    • @联系人高亮
    • 数据保存和还原
    • 检测用户输入@跳转到联系人选择
    • @块删除时作为一个整体删除,用户点击时光标不可出现在@块内部
    • 点击@块跳转
  2. 思路整理

    看似功能并不复杂,但实际操作起来就会发现蜀道之难啊,数据高亮这点简单不必细说,数据的保存和还原只需要根据数据规则利用正则表达式处理即可,需求要求@块作为一个整体删除,同时光标不可以出现在@块内部,网上一般的思路是使用ForegroundColorSpan来高亮突出@块,同时重写EditeText的方法监听输入和删除,虽然也能达到效果但是需要写的代码有点多,这时候想到能不能像处理表情一样使用一个类似于ImageSpan的东西来做呢,但是@后的数据多变并不确定,所以需要自定义一个ImageSpan能根据内容变化,这样只需要自定义一个Span类型就可以,不用特意处理删除光标等问题。

  3. 代码实现

    自定义Span

    这个自定义的ViewSpan来自短信项目的收件人控件,继承自ReplacementSpan,当控件显示的时候会使用指定的View替代指定的字符串显示,而当getText().toString()的时候获得是原始字符串。

    public class ViewSpan extends ReplacementSpan {
    protected View view;
    private int maxWidth;

    public ViewSpan(View v, int maxWidth) {
        super();
        this.maxWidth = maxWidth;
        view = v;
        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    }

    private void prepView() {
        int widthSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST);
        int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);

        view.measure(widthSpec, heightSpec);
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
    }
    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom,@NonNull Paint paint) {
        prepView();

        canvas.save();
        //Centering the token looks like a better strategy that aligning the bottom
        int padding = (bottom - top - view.getBottom()) / 2;
        canvas.translate(x, bottom - view.getBottom() - padding);
        //这一句是重点,将view画在指定canvas上
        view.draw(canvas);
        canvas.restore();
    }
    @Override
    public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i2, Paint.FontMetricsInt fm) {
        prepView();

        if (fm != null) {
            //We need to make sure the layout allots enough space for the view
            int height = view.getMeasuredHeight();
            int need = height - (fm.descent - fm.ascent);
            if (need > 0) {
                int ascent = need / 2;
                //This makes sure the text drawing area will be tall enough for the view
                fm.descent += need - ascent;
                fm.ascent -= ascent;
                fm.bottom += need - ascent;
                fm.top -= need / 2;
            }
        }

        return view.getRight();
    }
}

数据的存储和还原

这里使用正则表达式匹配@字符串,规则是"@user:"+手机号,这个可以根据情况随便改
//匹配的正则
public static final String regEx = "(.|\\n)*@user:1[3,5,7,8]\\d{9}(.|\\n)*";
//分割字符串的正则
public static final String splitRegex = "@user:1[3,5,7,8]\\d{9}";
生成Span的方法
    public static SpannableString getSpan(final TextView textView, String usrStr){
        final String phone = usrStr.split(":")[1];
        String str;
        User user = findUser(phone);
        if(user == null){
            str = "@" + phone;
        }else {
            str = "@" + user.name;
        }
        SpannableString spanText = new SpannableString(usrStr);
        TextView spanTv = (TextView) LayoutInflater.from(textView.getContext()).inflate(R.layout.item_at, (ViewGroup) textView.getParent(), false);
        spanTv.setText(str);
        //第二个参数其实应该是用textView的最大宽度,这里的300是随便写的
        ViewSpan span = new ViewSpan(spanTv,300);
        spanText.setSpan(span, 0, spanText.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        //添加点击事件
        ClickableSpan clickableSpan = new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                Toast.makeText(textView.getContext(), phone, Toast.LENGTH_SHORT).show();
            }
        };
        spanText.setSpan(clickableSpan,0, spanText.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        return spanText;
    }

显示数据

根据正则表达式匹配分割字符串然后截取替换再重新组合

    public static void setText(TextView textView, String str){
        if(str.matches(regEx)){
            textView.setText("");
            String[] splitStrs = str.split(splitRegex);
            int num = splitStrs.length;
            int temp = 0;
            for(int i =0; i < num; i++){
                textView.append(splitStrs[i]);
                if((i+1) != num){
                    int n = splitStrs[i].length();
                    n += temp;
                    int m = str.indexOf(splitStrs[i+1], n);
                    String usr = str.substring(n, m);
                    temp = n + usr.length();
                    textView.append(getSpan(textView,usr));
                    Log.d("@Span",usr);
                }
            }
        }else{
            textView.setText(str);
        }
    }

如果要获取textView中的原始字符串只需要使用textView.getText().toString()即可。

监听EditText的输入事件当用户输入@的时候跳转到联系人选择

    editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Log.d("onTextChanged","start="+start+" before="+before+" count="+count);
                if(start==s.length()-1 && s.toString().endsWith("@")){
                    //TODO 用户输入@
                }
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

==注意事项==

  • 如果要给@块添加点击事件需要添加下面的代码
//TextView
textView.setMovementMethod(LinkMovementMethod.getInstance());
//EditText
editText.setMovementMethod(LinkMovementMethod.getInstance());
  • 如果有保存草稿的功能,当草稿以“@”结尾的时候,还原草稿不应该触发EditText的监听,所以应该在还原草稿之后再添加监听事件,这个时候如果已经添加了TextWatcher的话应该先调用editText.removeTextChangedListener()移除监听。

    Demo下载地地址 http://download.csdn.net/detail/w804518214/9758786
    下载到的文件是个AndroidStudio Module请在你自己的新建工程内选择import module;

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值