Introduction
I recently added the capability to tag users in an Android application. If you have ever tagged a user in instagram, you get a list of users as you type in the "@
" sign and the list is filtered as you type in characters eventually narrowing down the list of users to choose from. This is the basic behavior I was able to replicate in my own app and I want to share it here on CodeProject.
Background
I won't go into the details of the backend web services, instead, I will focus on the Android implementation. Essentially, I want to be able to invoke a list of users as I type in the "@
" symbol. I then want to continue to filter the list as I type in each character of the username I'm tagging. I also want to be able to link the tagged username and show a user profile page if the user taps a tag.
Using the Code
Let's start with the activity layout file. We need an EditText
control that the user will use to type in some text. I prefer to use a RealtiveLayout
. Below the EditText
control, we have a ListView
control with the visibility set to "gone" and the width
and height
set to "match_parent
". This is key, as we find a user tag will make the ListView
visible and it will fill the screen allowing the user to select a user to tag.
<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"/>
Next, attach a TextWatcher
in the addTextChangedListener
for the EditText
control. In the TextWatcher
class, as the user types in the EditText
control, the "afterTextChanged
" method will fire passing in our editable parameter from our EditText
control. We use a regular expression to find lower and upper case alpha numeric string
following the "@
" character including the "-
" and ".
" characters as well. We do not allow spaces or other special characters. It's important to only execute on matches starting from the cursor's current position. This will allow multiple tags inside the EditText
control. Once we find the current tag, that is where you will want to get the substring excluding the "@
" and pass that as a parameter to your backend services that will return a list of users. This is also where you will want to set the visibility of your ListView
to VISIBLE
. In a Relative
layout, placing the ListView
at the bottom with "match_parent
" as the width
and height
value will place it over the top of all other controls in the layout. At this point, I'm assuming you have worked with ListView
s, Adapters, paging via scroll listeners, etc.
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();
loadUsersFromBackEnd(text.substring(s, e);
break;
}
}
}
});
Next, let's cover the reverse. You are looking at a post or comment from another user and we want to find the tags and make them clickable inside a TextView
. Once again, we will use the Pattern
and Matcher
classes to find our tags. The ClickableSpan
class
is used to make a substring of text clickable. As we find pattern matches for user tags, we create an instance of the clickableSpan
class, override the onClick
method. In this example, we are creating an Intent that will pass the users friendly name, excluding the "@
" character, to our user profile activity. Again, I'm not going to cover Intent's and passing values between activities. This code snippet will most likely be inside an Adapter
class that is binding data records to a ListView
. As the user scrolls the ListView
content, user tags will appear like hyper links. Clicking on them will execute the ClickableSpan
onClick
method.
Pattern p = Pattern.compile("[@][a-zA-Z0-9-.]+");
Matcher m = p.matcher(ss);
while(m.find())
{
final int s = m.start() + 1;
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);
}
Points of Interest
The main things to remember are using the Pattern
, Matcher
and ClickableSpan
classes to find user tags, both as the user submits a new post/reply and as users are reading the text another user posted. Overriding the onClick
method in the ClickableSpan
allows you to specify an action to take when the user taps a user tag. In my case, navigating to an activity that displays a user's profile. Another trick is to store data as encoded HTML on the backend which allows you to support emojis!
History
- 24th June, 2018: Initial version