Linkify系统中的添加步骤:
先使用addLinks(Spannale text,int mask){};添加连接
首先会判断mask==0如果是,则直接跳出,否则继续进行判断:
if (mask == 0) {
return false;
}
接下来先将文本中的所有Span移除:
URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
for (int i = old.length - 1; i >= 0; i--) {
text.removeSpan(old[i]);
}
然后在新建一个List来保存链接数据
ArrayList links = new ArrayList();
拿电话号码为例,如果mask与电话号码相同的话,就
if ((mask & PHONE_NUMBERS) != 0) {
gatherLinks(links, text, Patterns.PHONE,
new String[] { "tel:" },
sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
}
将保存所以链接的links放入,要查看是否有该链接的文本,然后是Patterns,然后新建一个String加上了"tel:"字段方便后面对Intent进行处理.然后是MatchFilter和TransformFileter
下面我们就看看gatherLinks方法的实现:
private static final void gatherLinks(ArrayList links,
Spannable s, Pattern pattern, String[] schemes,
MatchFilter matchFilter, TransformFilter transformFilter) {
Matcher m = pattern.matcher(s);
while (m.find()) {
int start = m.start();
int end = m.end();
if (matchFilter == null || matchFilter.acceptMatch(s, start, end)) {
LinkSpec spec = new LinkSpec();
String url = makeUrl(m.group(0), schemes, m, transformFilter);
spec.url = url;
spec.start = start;
spec.end = end;
links.add(spec);
}
}
}
我们会发现他先使用pattern.matcher(Spannable s);s也就是我们要检测的文本,来创建一个matcher,然后使用matcher.find来依次发现符合正则表达式的内容,在每发现一个之后,如果matchFilter不为null(用户自定义了)或者通过了matchFilter.acceptMatch(Spannable s,m.start(),m.end())函数的验证,其确实为一个null,然后将其制作为一个utl然后将url添加到LinkSpec中。
LinkSpec的构造:
class LinkSpec {
String url;
int start;
int end;
}
看Patterns.PHONE的正则表达式如下:
/**
* This pattern is intended for searching for things that look like they
* might be phone numbers in arbitrary text, not for validating whether
* something is in fact a phone number. It will miss many things that
* are legitimate phone numbers.
*
* The pattern matches the following:
*
* - Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
* may follow.
*
- Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
*
- A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
*
*/
public static final Pattern PHONE
= Pattern.compile( // sdd = space, dot, or dash
"(\\+[0-9]+[\\- \\.]*)?" // +*
+ "(\\([0-9]+\\)[\\- \\.]*)?" //()*
+ "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // +
然后看MatchFilter通过其来过滤电话号码数字个数不到达一个电话号码的数字组合。
/**
* Filters out URL matches that don't have enough digits to be a
* phone number.
*/
public static final MatchFilter sPhoneNumberMatchFilter = new MatchFilter() {
public final boolean acceptMatch(CharSequence s, int start, int end) {
int digitCount = 0;
for (int i = start; i < end; i++) {
if (Character.isDigit(s.charAt(i))) {
digitCount++;
if (digitCount >= PHONE_NUMBER_MINIMUM_DIGITS) {
return true;
}
}
}
return false;
}
};
其中PHONE_NUMBER_MINIMUM_DIGITS是这样解释的:也就是说最小判断电话号码长度为五位数字
/**
* Don't treat anything with fewer than this many digits as a
* phone number.
*/
private static final int PHONE_NUMBER_MINIMUM_DIGITS = 5;
然后看makeUrl()方法如下:
private static final String makeUrl(String url, String[] prefixes,
Matcher m, TransformFilter filter) {
if (filter != null) {
url = filter.transformUrl(m, url);
}
boolean hasPrefix = false;
for (int i = 0; i < prefixes.length; i++) {
if (url.regionMatches(true, 0, prefixes[i], 0,
prefixes[i].length())) {
hasPrefix = true;
// Fix capitalization if necessary
if (!url.regionMatches(false, 0, prefixes[i], 0,
prefixes[i].length())) {
url = prefixes[i] + url.substring(prefixes[i].length());
}
break;
}
}
if (!hasPrefix) {
url = prefixes[0] + url;
}
return url;
}
接收的参数是:makeUrl(m.group(0), schemes, m, transformFilter);
group是针对()来说的,group(0)就是指的整个串,group(1) 指的是第一个括号里的东西,group(2)指的第二个括号里的东西。第一个参数也就是检测到的第一个符合要求的字符串,第二个就是new String[] { "tel:" }字符串,第三个是Matcher,第四个是TransformFilter具体里面是怎么撮合的utl自己看看就明白了
然后看TransformFilter格式转换可以将+1 (919) 555-1212的格式转换为再打电话时的+19195551212格式
/**
* Transforms matched phone number text into something suitable
* to be used in a tel: URL. It does this by removing everything
* but the digits and plus signs. For instance:
* '+1 (919) 555-1212'
* becomes '+19195551212'
*/
public static final TransformFilter sPhoneNumberTransformFilter = new TransformFilter() {
public final String transformUrl(final Matcher match, String url) {
return Patterns.digitsAndPlusOnly(match);
}
};
其中digitsAndPlusOnly的方法如下:
/**
* Convenience method to return only the digits and plus signs
* in the matching string.
*
* @param matcher The Matcher object from which digits and plus will
* be extracted
*
* @return A String comprising all of the digits and plus in
* the match
*/
public static final String digitsAndPlusOnly(Matcher matcher) {
StringBuilder buffer = new StringBuilder();
String matchingRegion = matcher.group();
for (int i = 0, size = matchingRegion.length(); i < size; i++) {
char character = matchingRegion.charAt(i);
if (character == '+' || Character.isDigit(character)) {
buffer.append(character);
}
}
return buffer.toString();
}
最后就是要撮合好的utl添加到LinkSpec中。
spec.url = url;
spec.start = start;
spec.end = end;
links.add(spec);
最后会通过applyLink方法来对文本添加Span
private static final void applyLink(String url, int start, int end, Spannable text) {
URLSpan span = new URLSpan(url);
text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
至于文本的setSpan方法,
/**
* Attach the specified markup object to the range start…end
* of the text, or move the object to that range if it was already
* attached elsewhere. See {
@link Spanned} for an explanation of
* what the flags mean. The object can be one that has meaning only
* within your application, or it can be one that the text system will
* use to affect text display or behavior. Some noteworthy ones are
* the subclasses of {
@link android.text.style.CharacterStyle} and
* {
@link android.text.style.ParagraphStyle}, and
* {
@link android.text.TextWatcher} and
* {
@link android.text.SpanWatcher}.
*/
public void setSpan(Object what, int start, int end, int flags);
//具体实现如下: 在android.text.SpannableStringBuilder下
private void setSpan(boolean send,
Object what, int start, int end, int flags) {
int nstart = start;
int nend = end;
checkRange("setSpan", start, end);
if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) {
if (start != 0 && start != length()) {
char c = charAt(start - 1);
if (c != '\n')
throw new RuntimeException(
"PARAGRAPH span must start at paragraph boundary");
}
}
if ((flags & END_MASK) == PARAGRAPH) {
if (end != 0 && end != length()) {
char c = charAt(end - 1);
if (c != '\n')
throw new RuntimeException(
"PARAGRAPH span must end at paragraph boundary");
}
}
if (start > mGapStart)
start += mGapLength;
else if (start == mGapStart) {
int flag = (flags & START_MASK) >> START_SHIFT;
if (flag == POINT || (flag == PARAGRAPH && start == length()))
start += mGapLength;
}
if (end > mGapStart)
end += mGapLength;
else if (end == mGapStart) {
int flag = (flags & END_MASK);
if (flag == POINT || (flag == PARAGRAPH && end == length()))
end += mGapLength;
}
int count = mSpanCount;
Object[] spans = mSpans;
for (int i = 0; i < count; i++) {
if (spans[i] == what) {
int ostart = mSpanStarts[i];
int oend = mSpanEnds[i];
if (ostart > mGapStart)
ostart -= mGapLength;
if (oend > mGapStart)
oend -= mGapLength;
mSpanStarts[i] = start;
mSpanEnds[i] = end;
mSpanFlags[i] = flags;
if (send)
sendSpanChanged(what, ostart, oend, nstart, nend);
return;
}
}
if (mSpanCount + 1 >= mSpans.length) {
int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
Object[] newspans = new Object[newsize];
int[] newspanstarts = new int[newsize];
int[] newspanends = new int[newsize];
int[] newspanflags = new int[newsize];
System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
mSpans = newspans;
mSpanStarts = newspanstarts;
mSpanEnds = newspanends;
mSpanFlags = newspanflags;
}
mSpans[mSpanCount] = what;
mSpanStarts[mSpanCount] = start;
mSpanEnds[mSpanCount] = end;
mSpanFlags[mSpanCount] = flags;
mSpanCount++;
if (send)
sendSpanAdded(what, nstart, nend);
}
看了上面的源码,我想,你就可以根据自己的需要做添加自己的 Linkify了