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<LinkSpec> links = new ArrayList<LinkSpec>();
拿电话号码为例,如果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<LinkSpec> 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. * * <p> The pattern matches the following: * <ul> * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes * may follow. * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes. * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes. * </ul> */ public static final Pattern PHONE = Pattern.compile( // sdd = space, dot, or dash "(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>* + "(\\([0-9]+\\)[\\- \\.]*)?" //(<digits>)<sdd>* + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit> 然后看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 <code>start…end</code> * 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了