在Instagram等社交媒体应用中,经常看到用户通过@符号来标记其他用户,这是一个非常实用的功能。本文将介绍如何在Android应用中实现类似的功能,包括用户输入时的筛选和点击标签跳转到用户资料页面的实现方法。
在本文中,不会深入讨论后端Web服务的细节,而是专注于Android端的实现。目标是在用户输入@符号时调用用户列表,然后根据用户输入的字符继续筛选用户列表。此外,希望在用户点击标签时能够链接到标记的用户名,并显示用户的个人资料页面。
首先,需要一个EditText控件供用户输入文本。更喜欢使用RelativeLayout。在EditText控件下方,有一个ListView控件,其可见性设置为"gone",宽度和高度设置为"match_parent"。这是关键,因为当找到一个用户标签时,ListView将变为可见,并填满屏幕,允许用户选择要标记的用户。
<EditText
    android:id="@+id/myEditText"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="textMultiLine"
    android:minLines="5"
    android:maxLines="5" />
<ListView
    android:id="@+id/myUsers"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="#E6E6E6"
    android:dividerHeight="2dip"
    android:paddingLeft="20dip"
    android:paddingRight="20dip"
    android:visibility="gone"
    android:background="#ffffff" />
    
接下来,为EditText控件添加TextWatcher。在TextWatcher类中,当用户在EditText控件中输入时,afterTextChanged方法将被触发,并向Editable参数传递来自EditText控件的值。使用正则表达式来查找跟随@字符的小写和大写字母数字字符串,包括-和.字符。不允许空格或其他特殊字符。重要的是仅在从光标当前位置开始的匹配上执行。这将允许在EditText控件中输入多个标签。一旦找到了当前的标签,那就是想要获取的子字符串,不包括@,并将其作为参数传递给后端服务,后端服务将返回用户列表。这也是想要将ListView的可见性设置为VISIBLE的地方。在RelativeLayout中,将ListView放置在底部,并将match_parent作为宽度和高度值,将使其覆盖布局中的所有其他控件。
this.mEditText.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) {
    }
    @Override
    public void afterTextChanged(Editable editable) {
        String text = editable.toString();
        Pattern p = Pattern.compile("[@][a-zA-Z0-9-.]+");
        Matcher m = p.matcher(text);
        int cursorPosition = mEditText.getSelectionStart();
        while (m.find()) {
            if (cursorPosition >= m.start() && cursorPosition <= m.end()) {
                final int s = m.start() + 1;
                final int e = m.end();
                // add 1 to ommit the "@" tag
                loadUsersFromBackEnd(text.substring(s, e));
                break;
            }
        }
    }
});
    
当查看其他用户的帖子或评论时,希望找到标签并使其在TextView中可点击。再次使用Pattern和Matcher类来找到标签。ClickableSpan类用于使文本的子串可点击。当找到用户标签的模式匹配时,创建ClickableSpan类的实例,覆盖onClick方法。在这个例子中,创建了一个Intent,将用户的友好名称(不包括@字符)传递给用户资料活动。同样,不会覆盖Intent和在活动之间传递值。这段代码片段很可能会在一个Adapter类中,该类将数据记录绑定到ListView。当用户滚动ListView内容时,用户标签将像超链接一样出现。点击它们将执行ClickableSpan的onClick方法。
Pattern p = Pattern.compile("[@][a-zA-Z0-9-.]+");
Matcher m = p.matcher(ss);
while (m.find()) {
    final int s = m.start() + 1; // add 1 to omit the "@" tag
    final int e = m.end();
    ClickableSpan clickableSpan = new ClickableSpan() {
        @Override
        public void onClick(View textView) {
            Intent intent = new Intent(mContext, com.myawesomeapp.UserActivity.class);
            intent.putExtra(UserActivity.EXTRA_FRIENDLY_NAME, wallPost.getMessage().substring(s, e));
            mContext.startActivity(intent);
        }
    };
    ss.setSpan(clickableSpan, m.start(), m.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}