【问题标题】:Android Linkify both web and @mentions all in the same TextViewAndroid 在同一个 TextView 中链接 web 和 @mentions
【发布时间】:2011-01-05 00:17:23
【问题描述】:

好吧,我昨天问了这个问题:

AutoLink @mentions in a twitter client

我的@mentions 链接正确。但为了让它工作,我不得不将 android:autoLink="web" 从我的 xml 中取出用于 TextView。所以现在我得到了@mentions 的链接,但它不再链接 URL。我尝试像这样进行两个单独的 Linkify.addLinks() 调用:

mentionFilter = new TransformFilter() {
    public final String transformUrl(final Matcher match, String url) {
        return match.group(1);
    }
};

// Match @mentions and capture just the username portion of the text.
//pattern = Pattern.compile("@([A-Za-z0-9_-]+)");
pattern = Pattern.compile("(@[a-zA-Z0-9_]+)");
scheme = "http://twitter.com/";

tweetTxt = (TextView) v.findViewById(R.id.tweetTxt);


Linkify.addLinks(tweetTxt, pattern, scheme, null, mentionFilter);
Linkify.addLinks(tweetTxt, Linkify.WEB_URLS);

但是最后被调用的是被应用的那个。谁能告诉我如何让它链接@提及并仍然自动链接 URL?

编辑以澄清更多代码。

【问题讨论】:

  • 在使用“Linkify.addLinks”时,不要在 XML 布局文件的 TextView 中使用“android:autoLink”。希望这有助于以任何方式为某些人节省一些时间您将从下面给出的答案中达到这一点

标签: android textview linkify


【解决方案1】:

这是我链接所有 Twitter 链接(提及、主题标签和 URL)的代码:

TextView tweet = (TextView) findViewById(R.id.tweet);

TransformFilter filter = new TransformFilter() {
    public final String transformUrl(final Matcher match, String url) {
        return match.group();
    }
};

Pattern mentionPattern = Pattern.compile("@([A-Za-z0-9_-]+)");
String mentionScheme = "http://www.twitter.com/";
Linkify.addLinks(tweet, mentionPattern, mentionScheme, null, filter);

Pattern hashtagPattern = Pattern.compile("#([A-Za-z0-9_-]+)");
String hashtagScheme = "http://www.twitter.com/search/";
Linkify.addLinks(tweet, hashtagPattern, hashtagScheme, null, filter);

Pattern urlPattern = Patterns.WEB_URL;
Linkify.addLinks(tweet, urlPattern, null, null, filter);

【讨论】:

  • 另外,请参阅@Suraj c 的回答
  • 另外,不要在 XML 布局文件的 TextView 中使用 'android:autoLink'
【解决方案2】:

我必须先 Linkify.ALL,然后再对 @ 提及的内容进行 Linkify 才能使其正常工作。

另外,不要在 XML 布局文件的 TextView 中使用 'android:autoLink'

TextView textView = (TextView) findViewById(R.id.tweet);

Pattern atMentionPattern = Pattern.compile("@([A-Za-z0-9_]+)");
String atMentionScheme = "http://twitter.com/";

TransformFilter transformFilter = new TransformFilter() {
        //skip the first character to filter out '@'
        public String transformUrl(final Matcher match, String url) {
                return match.group(1);
        }
};

Linkify.addLinks(textView, Linkify.ALL);
Linkify.addLinks(textView, atMentionPattern, atMentionScheme, null, transformFilter);

我的 TextView 如下所示:

<TextView 
    android:id="@+id/tweet"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textStyle="bold"
    android:textColorLink="#449def"/>

【讨论】:

    【解决方案3】:

    好的,终于有时间将@mention 和#hashtags 正确放入Linkify 类。而不是仅仅覆盖一些其他类型的链接以使其正常工作。

    这个类就像普通的 Linkify 一样工作,但也可以做这两件事。

    /*
     * Copyright (C) 2007 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package com.use.your.own.package;
    
    
    import android.text.method.LinkMovementMethod;
    import android.text.method.MovementMethod;
    import android.text.style.URLSpan;
    import android.text.Spannable;
    import android.text.SpannableString;
    import android.text.Spanned;
    import android.webkit.WebView;
    import android.widget.TextView;
    
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     *  Linkify take a piece of text and a regular expression and turns all of the
     *  regex matches in the text into clickable links.  This is particularly
     *  useful for matching things like email addresses, web urls, etc. and making
     *  them actionable.
     *
     *  Alone with the pattern that is to be matched, a url scheme prefix is also
     *  required.  Any pattern match that does not begin with the supplied scheme
     *  will have the scheme prepended to the matched text when the clickable url
     *  is created.  For instance, if you are matching web urls you would supply
     *  the scheme <code>http://</code>.  If the pattern matches example.com, which
     *  does not have a url scheme prefix, the supplied scheme will be prepended to
     *  create <code>http://example.com</code> when the clickable url link is
     *  created.
     */
    
    
    public class LinkifyWithTwitter {
         /**
         *  Bit field indicating that web URLs should be matched in methods that
         *  take an options mask
         */
        public static final int WEB_URLS = 0x01;
    
        /**
         *  Bit field indicating that email addresses should be matched in methods
         *  that take an options mask
         */
        public static final int EMAIL_ADDRESSES = 0x02;
    
        /**
         *  Bit field indicating that phone numbers should be matched in methods that
         *  take an options mask
         */
        public static final int PHONE_NUMBERS = 0x04;
    
        /**
         *  Bit field indicating that twitter @mentions should be matched in methods that
         *  take an options mask
         */
        public static final int AT_MENTIONS = 0x05;
    
        /**
         *  Bit field indicating that #hash-tags should be matched in methods that
         *  take an options mask
         */
        public static final int HASH_TAGS = 0x06;
    
        /**
         *  Bit field indicating that street addresses should be matched in methods that
         *  take an options mask
         */
        public static final int MAP_ADDRESSES = 0x08;
    
        /**
         *  Bit mask indicating that all available patterns should be matched in
         *  methods that take an options mask
         */
        public static final int ALL = WEB_URLS | EMAIL_ADDRESSES | PHONE_NUMBERS | AT_MENTIONS| HASH_TAGS |MAP_ADDRESSES;
    
        /**
         * Don't treat anything with fewer than this many digits as a
         * phone number.
         */
        private static final int PHONE_NUMBER_MINIMUM_DIGITS = 5;
    
        /**
         *  Filters out web URL matches that occur after an at-sign (@).  This is
         *  to prevent turning the domain name in an email address into a web link.
         */
        public static final MatchFilter sUrlMatchFilter = new MatchFilter() {
            public final boolean acceptMatch(CharSequence s, int start, int end) {
                if (start == 0) {
                    return true;
                }
    
                if (s.charAt(start - 1) == '@') {
                    return false;
                }
    
                return true;
            }
        };
    
        /**
         *  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;
            }
        };
    
        /**
         *  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:
         *  &apos;+1 (919) 555-1212&apos;
         *  becomes &apos;+19195551212&apos;
         */
        public static final TransformFilter sPhoneNumberTransformFilter = new TransformFilter() {
            public final String transformUrl(final Matcher match, String url) {
                return Regex.digitsAndPlusOnly(match);
            }
        };
    
        /**
         *  MatchFilter enables client code to have more control over
         *  what is allowed to match and become a link, and what is not.
         *
         *  For example:  when matching web urls you would like things like
         *  http://www.example.com to match, as well as just example.com itelf.
         *  However, you would not want to match against the domain in
         *  support@example.com.  So, when matching against a web url pattern you
         *  might also include a MatchFilter that disallows the match if it is
         *  immediately preceded by an at-sign (@).
         */
        public interface MatchFilter {
            /**
             *  Examines the character span matched by the pattern and determines
             *  if the match should be turned into an actionable link.
             *
             *  @param s        The body of text against which the pattern
             *                  was matched
             *  @param start    The index of the first character in s that was
             *                  matched by the pattern - inclusive
             *  @param end      The index of the last character in s that was
             *                  matched - exclusive
             *
             *  @return         Whether this match should be turned into a link
             */
            boolean acceptMatch(CharSequence s, int start, int end);
        }
    
        /**
         *  TransformFilter enables client code to have more control over
         *  how matched patterns are represented as URLs.
         *
         *  For example:  when converting a phone number such as (919)  555-1212
         *  into a tel: URL the parentheses, white space, and hyphen need to be
         *  removed to produce tel:9195551212.
         */
        public interface TransformFilter {
            /**
             *  Examines the matched text and either passes it through or uses the
             *  data in the Matcher state to produce a replacement.
             *
             *  @param match    The regex matcher state that found this URL text
             *  @param url      The text that was matched
             *
             *  @return         The transformed form of the URL
             */
            String transformUrl(final Matcher match, String url);
        }
    
        /**
         *  Scans the text of the provided Spannable and turns all occurrences
         *  of the link types indicated in the mask into clickable links.
         *  If the mask is nonzero, it also removes any existing URLSpans
         *  attached to the Spannable, to avoid problems if you call it
         *  repeatedly on the same text.
         */
        public static final boolean addLinks(Spannable text, int mask) {
            if (mask == 0) {
                return false;
            }
    
            URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
    
            for (int i = old.length - 1; i >= 0; i--) {
                text.removeSpan(old[i]);
            }
    
            ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
    
            if ((mask & WEB_URLS) != 0) {
                gatherLinks(links, text, Regex.WEB_URL_PATTERN,
                    new String[] { "http://", "https://" },
                    sUrlMatchFilter, null);
            }
    
            if ((mask & EMAIL_ADDRESSES) != 0) {
                gatherLinks(links, text, Regex.EMAIL_ADDRESS_PATTERN,
                    new String[] { "mailto:" },
                    null, null);
            }
    
            if ((mask & PHONE_NUMBERS) != 0) {
                gatherLinks(links, text, Regex.PHONE_PATTERN,
                    new String[] { "tel:" },
                    sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
            }
    
            if((mask & AT_MENTIONS) != 0){
                gatherLinks(links, text, Pattern.compile("@([A-Za-z0-9_-]+)"),
                        new String[] { "http://www.twitter.com/" },
                        null, null);
            }
    
            if((mask & HASH_TAGS) != 0){
                TransformFilter hashTagFilter = new TransformFilter() {
                    public final String transformUrl(final Matcher match, String url) {
                        return match.group(0).replaceAll("#", "%23");
                    }
                };
    
                gatherLinks(links, text, Pattern.compile("#([A-Za-z0-9_-]+)"),
                    new String[] { "http://twitter.com/#!/search/" },
                    null,hashTagFilter);
            }
    
            if ((mask & MAP_ADDRESSES) != 0) {
                gatherMapLinks(links, text);
            }
    
            pruneOverlaps(links);
    
            if (links.size() == 0) {
                return false;
            }
    
            for (LinkSpec link: links) {
                applyLink(link.url, link.start, link.end, text);
            }
    
            return true;
        }
    
        /**
         *  Scans the text of the provided TextView and turns all occurrences of
         *  the link types indicated in the mask into clickable links.  If matches
         *  are found the movement method for the TextView is set to
         *  LinkMovementMethod.
         */
        public static final boolean addLinks(TextView text, int mask) {
            if (mask == 0) {
                return false;
            }
    
            CharSequence t = text.getText();
    
            if (t instanceof Spannable) {
                if (addLinks((Spannable) t, mask)) {
                    addLinkMovementMethod(text);
                    return true;
                }
    
                return false;
            } else {
                SpannableString s = SpannableString.valueOf(t);
    
                if (addLinks(s, mask)) {
                    addLinkMovementMethod(text);
                    text.setText(s);
    
                    return true;
                }
    
                return false;
            }
        }
    
        private static final void addLinkMovementMethod(TextView t) {
            MovementMethod m = t.getMovementMethod();
    
            if ((m == null) || !(m instanceof LinkMovementMethod)) {
                if (t.getLinksClickable()) {
                    t.setMovementMethod(LinkMovementMethod.getInstance());
                }
            }
        }
    
        /**
         *  Applies a regex to the text of a TextView turning the matches into
         *  links.  If links are found then UrlSpans are applied to the link
         *  text match areas, and the movement method for the text is changed
         *  to LinkMovementMethod.
         *
         *  @param text         TextView whose text is to be marked-up with links
         *  @param pattern      Regex pattern to be used for finding links
         *  @param scheme       Url scheme string (eg <code>http://</code> to be
         *                      prepended to the url of links that do not have
         *                      a scheme specified in the link text
         */
        public static final void addLinks(TextView text, Pattern pattern, String scheme) {
            addLinks(text, pattern, scheme, null, null);
        }
    
        /**
         *  Applies a regex to the text of a TextView turning the matches into
         *  links.  If links are found then UrlSpans are applied to the link
         *  text match areas, and the movement method for the text is changed
         *  to LinkMovementMethod.
         *
         *  @param text         TextView whose text is to be marked-up with links
         *  @param p            Regex pattern to be used for finding links
         *  @param scheme       Url scheme string (eg <code>http://</code> to be
         *                      prepended to the url of links that do not have
         *                      a scheme specified in the link text
         *  @param matchFilter  The filter that is used to allow the client code
         *                      additional control over which pattern matches are
         *                      to be converted into links.
         */
        public static final void addLinks(TextView text, Pattern p, String scheme,
                MatchFilter matchFilter, TransformFilter transformFilter) {
            SpannableString s = SpannableString.valueOf(text.getText());
    
            if (addLinks(s, p, scheme, matchFilter, transformFilter)) {
                text.setText(s);
                addLinkMovementMethod(text);
            }
        }
    
        /**
         *  Applies a regex to a Spannable turning the matches into
         *  links.
         *
         *  @param text         Spannable whose text is to be marked-up with
         *                      links
         *  @param pattern      Regex pattern to be used for finding links
         *  @param scheme       Url scheme string (eg <code>http://</code> to be
         *                      prepended to the url of links that do not have
         *                      a scheme specified in the link text
         */
        public static final boolean addLinks(Spannable text, Pattern pattern, String scheme) {
            return addLinks(text, pattern, scheme, null, null);
        }
    
        /**
         *  Applies a regex to a Spannable turning the matches into
         *  links.
         *
         *  @param s            Spannable whose text is to be marked-up with
         *                      links
         *  @param p            Regex pattern to be used for finding links
         *  @param scheme       Url scheme string (eg <code>http://</code> to be
         *                      prepended to the url of links that do not have
         *                      a scheme specified in the link text
         *  @param matchFilter  The filter that is used to allow the client code
         *                      additional control over which pattern matches are
         *                      to be converted into links.
         */
        public static final boolean addLinks(Spannable s, Pattern p,
                String scheme, MatchFilter matchFilter,
                TransformFilter transformFilter) {
            boolean hasMatches = false;
            String prefix = (scheme == null) ? "" : scheme.toLowerCase();
            Matcher m = p.matcher(s);
    
            while (m.find()) {
                int start = m.start();
                int end = m.end();
                boolean allowed = true;
    
                if (matchFilter != null) {
                    allowed = matchFilter.acceptMatch(s, start, end);
                }
    
                if (allowed) {
                    String url = makeUrl(m.group(0), new String[] { prefix },
                                         m, transformFilter);
    
                    applyLink(url, start, end, s);
                    hasMatches = true;
                }
            }
    
            return hasMatches;
        }
    
        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);
        }
    
        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;
        }
    
        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);
                }
            }
        }
    
        private static final void gatherMapLinks(ArrayList<LinkSpec> links, Spannable s) {
            String string = s.toString();
            String address;
            int base = 0;
    
            while ((address = WebView.findAddress(string)) != null) {
                int start = string.indexOf(address);
    
                if (start < 0) {
                    break;
                }
    
                LinkSpec spec = new LinkSpec();
                int length = address.length();
                int end = start + length;
    
                spec.start = base + start;
                spec.end = base + end;
                string = string.substring(end);
                base += end;
    
                String encodedAddress = null;
    
                try {
                    encodedAddress = URLEncoder.encode(address,"UTF-8");
                } catch (UnsupportedEncodingException e) {
                    continue;
                }
    
                spec.url = "geo:0,0?q=" + encodedAddress;
                links.add(spec);
            }
        }
    
        private static final void pruneOverlaps(ArrayList<LinkSpec> links) {
            Comparator<LinkSpec>  c = new Comparator<LinkSpec>() {
                public final int compare(LinkSpec a, LinkSpec b) {
                    if (a.start < b.start) {
                        return -1;
                    }
    
                    if (a.start > b.start) {
                        return 1;
                    }
    
                    if (a.end < b.end) {
                        return 1;
                    }
    
                    if (a.end > b.end) {
                        return -1;
                    }
    
                    return 0;
                }
    
                public final boolean equals(Object o) {
                    return false;
                }
            };
    
            Collections.sort(links, c);
    
            int len = links.size();
            int i = 0;
    
            while (i < len - 1) {
                LinkSpec a = links.get(i);
                LinkSpec b = links.get(i + 1);
                int remove = -1;
    
                if ((a.start <= b.start) && (a.end > b.start)) {
                    if (b.end <= a.end) {
                        remove = i + 1;
                    } else if ((a.end - a.start) > (b.end - b.start)) {
                        remove = i + 1;
                    } else if ((a.end - a.start) < (b.end - b.start)) {
                        remove = i;
                    }
    
                    if (remove != -1) {
                        links.remove(remove);
                        len--;
                        continue;
                    }
    
                }
    
                i++;
            }
        }
    }
    
    class LinkSpec {
        String url;
        int start;
        int end;
    }
    

    【讨论】:

    【解决方案4】:

    我想出了一种方法来完成这项工作。这有点hacky,但它完成了工作。如果有人知道更合适的方法,请告诉我。

    为了使其正常工作,我为自己制作了 Linkify 类的副本,并编辑了处理电话号码链接的部分以改为执行 @提及链接。

    Here is where I found my copy of the linkify class

    我改变了这个:

    if ((mask & PHONE_NUMBERS) != 0) {
                gatherLinks(links, text, Regex.PHONE_PATTERN,
                    new String[] { "tel:" },
                    sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
            }
    

    进入这个:

    if ((mask & PHONE_NUMBERS) != 0) {
        gatherLinks(links, text, Pattern.compile("@([A-Za-z0-9_-]+)"),
            new String[] { "http://www.twitter.com/" },
            null, null);
    }
    

    我将这个类称为 MyLinkify 并在我的 Activity 中使用此代码来应用链接。

    MyLinkify.addLinks(tweetTxt, Linkify.ALL);
    

    要构建 MyLinkify 类,我还必须将 Regex 类的副本添加到我的项目中,here is where I found Regex.java

    我将此作为答案,以防其他任何寻找此效果的人找到此线程。我意识到这可能不是让它发挥作用的最佳方法。如果有人知道让它工作的更好方法,请在此处添加它,我会选择它是这个问题的实际答案。

    【讨论】:

      【解决方案5】:

      使用下面的代码:

       TextView textView =  (TextView) findViewById(R.id.hello);
              String str = "@aman_vivek how are u  #aman_vivek <http://www.google.com> <http://www.tekritisoftware.com>";
              textView.setText(str);
              Pattern wikiWordMatcher = Pattern.compile("(@[a-zA-Z0-9_]+)");
              String wikiViewURL =    "http://www.twitter.com/";
              Linkify.addLinks(textView, wikiWordMatcher, wikiViewURL);
      
      
              Pattern wikiWordMatcher1 = Pattern.compile("\\b(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
              Linkify.addLinks(textView, wikiWordMatcher1, null);
      

      【讨论】:

        【解决方案6】:

        tweetTxt 是您要链接的输入字符串还是 TextView 对象的句柄? 如果它是输入字符串,那么您只是用对 addLinks 的最终调用的输出覆盖 TextView。根据你的观察,我猜是这样的。

        在这两种情况下,您都需要将 TextView 传递给 addLinks。在内部,Linkify 将从 TextView 中提取 Spanned 缓冲区并使用它,这样对 addLinks 的多次调用是累积的。 WikiNotes 示例传递了 TextView,您也应该这样做:http://developer.android.com/resources/articles/wikinotes-linkify.html

        【讨论】:

        • tweetTxt 是 TextView。如果您尝试传入字符串,Linkify.addLinks 将不起作用。
        猜你喜欢
        • 1970-01-01
        • 2011-06-29
        • 2022-11-13
        • 1970-01-01
        • 1970-01-01
        • 2016-12-13
        • 2012-12-26
        • 2015-08-30
        • 2012-03-06
        相关资源
        最近更新 更多