Textview加入Intent、表情,点击跳转Activity

 做过web开发的人应该都知道,在HTML里支持<a>标签在文本里插入一个链接,点击后跳转;并且有<img>标签可 以插入图片。Android开发是否也支持呢?带着这个疑问,我们去APIDemos探索一下。OK,在 com.example.android.apis.text.link这个类里,官方演示了TextView支持的一些链接,上个图:


      看来TextView是支持链接跳转的,不过做Android开发的应该都知道,android的View载体是Activity,能不能支持activity跳转呢,很遗憾,不支持。

      不过无所谓,Android很有爱,开源的,理解了原理后我们自己去做,这也是我写本篇文章的主要目的,"授之以鱼,不如授之以渔",希望大家在遇到相 似问题时能像我这样去分析源码,然后找出解决办法(或者大家可以提出更好的方法),另外,文中如有不妥的地方,也欢迎大家批评指正。先上效果图:点击左边 的链接后跳转到右边。



  

    现在我们开始开发吧!第一步,研究相关的源代码吧。通过跟踪TextView的源码,我们发现TextView支持的链接是由android.text.style.URLSpan这个类实现的,它重写了一个onClick方法:

Java代码  收藏代码
  1. public void onClick(View widget) {  
  2.         Uri uri = Uri.parse(getURL());  
  3.         Context context = widget.getContext();  
  4.         Intent intent = new Intent(Intent.ACTION_VIEW, uri);  
  5.         intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());  
  6.         context.startActivity(intent);  
  7.     }  

      大家看到了吧startActivity,多么熟悉的方法。既然它能实现,为什么我们不能呢,答案是可以的。我们接着跟踪代码,可以看到URLSpan其实继承的是android.text.style.ClickableSpan,我们来看一下他的源码:

Java代码  收藏代码
  1. public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {  
  2.   
  3.     /** 
  4.      * Performs the click action associated with this span. 
  5.      */  
  6.     public abstract void onClick(View widget);  
  7.      
  8.     /** 
  9.      * Makes the text underlined and in the link color. 
  10.      */  
  11.     @Override  
  12.     public void updateDrawState(TextPaint ds) {  
  13.         ds.setColor(ds.linkColor);  
  14.         ds.setUnderlineText(true);  
  15.     }  
  16. }  

 是不是有点眉目了,我们直接继承这个类,重写他的方法不就可以了吗?大胆假设,小心求证,我们新建一个类:

Java代码  收藏代码
  1. import android.content.Context;  
  2. import android.content.Intent;  
  3. import android.text.TextPaint;  
  4. import android.text.style.ClickableSpan;  
  5. import android.view.View;  
  6.   
  7. /** 
  8.  * If an object of this type is attached to the text of a TextView with a 
  9.  * movement method of LinkMovementMethod, the affected spans of text can be 
  10.  * selected. If clicked, the {@link #onClick} method will be called. 
  11.  *  
  12.  * @author 张宁 
  13.  */  
  14. public class MyClickableSpan extends ClickableSpan {  
  15.   
  16.     int color = -1;  
  17.     private Context context;  
  18.     private Intent intent;  
  19.   
  20.     public MyClickableSpan(Context context, Intent intent) {  
  21.         this(-1, context, intent);  
  22.     }  
  23.   
  24.     /** 
  25.      * constructor 
  26.      * @param color the link color 
  27.      * @param context 
  28.      * @param intent 
  29.      */  
  30.     public MyClickableSpan(int color, Context context, Intent intent) {  
  31.         if (color!=-1) {  
  32.             this.color = color;  
  33.         }  
  34.         this.context = context;  
  35.         this.intent = intent;  
  36.     }  
  37.   
  38.     /** 
  39.      * Performs the click action associated with this span. 
  40.      */  
  41.     public void onClick(View widget){  
  42.         context.startActivity(intent);  
  43.     };  
  44.   
  45.     /** 
  46.      * Makes the text without underline. 
  47.      */  
  48.     @Override  
  49.     public void updateDrawState(TextPaint ds) {  
  50.         if (color == -1) {  
  51.             ds.setColor(ds.linkColor);  
  52.         } else {  
  53.             ds.setColor(color);  
  54.         }  
  55.         ds.setUnderlineText(false);  
  56.     }  
  57. }  

      在这个类里,我们重写了onClick事件,实现了Activity的跳转,并且去掉了下划线。Ok,第一个目的就达到了,下面我们来看一下如何在TextView里加入表情。

      这个就比较复杂了,因为TextView只能在其上下左右方向加入图片,是由Drawables这个类实现的,而我们想要的效果是在中间也可以插入,看 来这次TextView插入图片源码帮不了我们了。不过我们可以去android.text这个包里去找别的类,大家可以看到在这个包里有一个Html 类,做过web开发的应该可以想到什么吧?在文章开头已经提到了Html的<img>标签可以插入图片,那这个类是否提供这个功能呢?带着这 个疑问我们可以进去看看,其中有个接口:

Java代码  收藏代码
  1. /** 
  2.     * Retrieves images for HTML &lt;img&gt; tags. 
  3.     */  
  4.    public static interface ImageGetter {  
  5.        /** 
  6.         * This methos is called when the HTML parser encounters an 
  7.         * &lt;img&gt; tag.  The <code>source</code> argument is the 
  8.         * string from the "src" attribute; the return value should be 
  9.         * a Drawable representation of the image or <code>null</code> 
  10.         * for a generic replacement image.  Make sure you call 
  11.         * setBounds() on your Drawable if it doesn't already have 
  12.         * its bounds set. 
  13.         */  
  14.        public Drawable getDrawable(String source);  
  15.    }  

     看到<code>source</code>这个没,熟悉吧,结合URLSpan的用法,我们是否可以配合Spanned实现一个

ImageSpan呢?OK,上代码:

Java代码  收藏代码
  1. import java.util.Map;  
  2. import java.util.Set;  
  3.   
  4. import android.content.Context;  
  5. import android.graphics.drawable.Drawable;  
  6. import android.text.Html;  
  7. import android.text.Spanned;  
  8. import android.text.Html.ImageGetter;  
  9.   
  10. /** 
  11.  * this is a class which defining a spanned with image 
  12.  * @author 张宁 
  13.  * 
  14.  */  
  15. public class ImageSpan {  
  16.       
  17.     /** 
  18.      * the map of face. 
  19.      */  
  20.     private Map<String, String> faceMap;  
  21.     private Context context;  
  22.       
  23.     public ImageSpan(Context context, Map<String, String> faceMap){  
  24.         this.context = context;  
  25.         this.faceMap = faceMap;  
  26.     }   
  27.   
  28.     /** 
  29.      * get the image by the given key 
  30.      */  
  31.     private ImageGetter imageGetter = new Html.ImageGetter() {  
  32.         @Override  
  33.         public Drawable getDrawable(String source) {  
  34.             Drawable drawable = null;  
  35.             String sourceName = context.getPackageName() + ":drawable/"  
  36.                     + source;  
  37.             int id = context.getResources().getIdentifier(sourceName, nullnull);  
  38.             if (id != 0) {  
  39.                 drawable = context.getResources().getDrawable(id);  
  40.                 if (drawable != null) {  
  41.                     drawable.setBounds(00, drawable.getIntrinsicWidth(),  
  42.                             drawable.getIntrinsicHeight());  
  43.                 }  
  44.             }  
  45.             return drawable;  
  46.         }  
  47.     };  
  48.       
  49.     /** 
  50.      * return a {@link Spanned} with image 
  51.      * @param text 
  52.      * @return 
  53.      */  
  54.     public Spanned getImageSpan(CharSequence text){  
  55.         String cs = text.toString();  
  56.         if (faceMap != null) {  
  57.             Set<String> keys = faceMap.keySet();  
  58.             for (String key : keys) {  
  59.                 if (cs.contains(key)) {  
  60.                     cs = cs.replace(key, "<img src='" + faceMap.get(key) + "'>");  
  61.                 }  
  62.             }  
  63.         }  
  64.         return Html.fromHtml(cs, imageGetter, null);  
  65.     }  
  66.   
  67. }  

      到目前为止可以说关键代码都已经实现了,但是会有人问,我该如何使用这两个类呢?下面,我们在实现一个工具类来封装这两个类的方法,以方便调用:

Java代码  收藏代码
  1. import java.util.HashMap;  
  2. import java.util.List;  
  3. import java.util.Map;  
  4.   
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.text.SpannableStringBuilder;  
  8. import android.text.Spanned;  
  9. import android.text.TextUtils;  
  10. import android.text.method.LinkMovementMethod;  
  11. import android.widget.EditText;  
  12. import android.widget.TextView;  
  13.   
  14. /** 
  15.  * TextView with intent that can redirect to a new activity 
  16.  *  
  17.  * @author 张宁 
  18.  *  
  19.  */  
  20. public class CustomTextView {  
  21.   
  22.     private static Map<String, String> faceMap;  
  23.   
  24.     static {  
  25.         faceMap = new HashMap<String, String>();  
  26.         faceMap.put("[哭]""face_1");  
  27.         faceMap.put("[怒]""face_2");  
  28.     }  
  29.   
  30.     /** 
  31.      * make textview a clickable textview<br> 
  32.      * Note: make true the order of textList and intentList are mapped 
  33.      *  
  34.      * @param context 
  35.      * @param textView 
  36.      * @param textList 
  37.      *            the text should be set to this textview,not null 
  38.      * @param intentList 
  39.      *            the intent map to the text, if the text have no intent mapped 
  40.      *            to, please set a null value.Or it will happen some unknown 
  41.      *            error.<br> 
  42.      *            not null 
  43.      */  
  44.     public static void setClickableTextView(Context context, TextView textView,  
  45.             List<String> textList, List<Intent> intentList) {  
  46.         if (textList == null || intentList == null) {  
  47.             return;  
  48.         }  
  49.         SpannableStringBuilder builder = new SpannableStringBuilder();  
  50.         int end = -1, length = -1;  
  51.         int size = textList.size();  
  52.         Intent intent;  
  53.         for (int i = 0; i < size; i++) {  
  54.             String text = textList.get(i);  
  55.             if (TextUtils.isEmpty(text)) {  
  56.                 continue;  
  57.             }  
  58.             builder.append(textList.get(i));  
  59.             if ((intent = intentList.get(i)) != null) {  
  60.                 end = builder.length();  
  61.                 length = textList.get(i).length();  
  62.                 builder.setSpan(getClickableSpan(context, intent),  
  63.                         end - length, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  64.             }  
  65.             builder.append(" ");  
  66.         }  
  67.         textView.setText(builder);  
  68.         textView.setFocusable(true);  
  69.         textView.setMovementMethod(LinkMovementMethod.getInstance());  
  70.     }  
  71.       
  72.     /** 
  73.      *  make textview a clickable textview<br> 
  74.      *  Note: make true the order of textList and intentList are mapped 
  75.      * @param context 
  76.      * @param textView 
  77.      * @param text 
  78.      * @param intent 
  79.      */  
  80.     public static void setClickableTextView(Context context, TextView textView,  
  81.             String text, Intent intent) {  
  82.         SpannableStringBuilder builder = new SpannableStringBuilder(text);  
  83.         builder.setSpan(getClickableSpan(context, intent), 0, text.length(),   
  84.                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  85.         textView.setText(builder);  
  86.         textView.setMovementMethod(LinkMovementMethod.getInstance());  
  87.     }  
  88.   
  89.     /** 
  90.      * make TextView a View with image at any index   
  91.      * @param context 
  92.      * @param textView 
  93.      * @param textList 
  94.      */  
  95.     public static void setImgTextView(Context context, TextView textView,  
  96.             List<String> textList) {  
  97.         StringBuilder builder = new StringBuilder();  
  98.         for (int i = 0; i < textList.size(); i++) {  
  99.             builder.append(textList.get(i)).append(" ");  
  100.         }  
  101.         setImgTextView(context, textView, builder.toString());  
  102.   
  103.     }  
  104.   
  105.     /** 
  106.      * make TextView a View with image at any index   
  107.      * @param context 
  108.      * @param textView 
  109.      * @param text 
  110.      */  
  111.     public static void setImgTextView(Context context, TextView textView,  
  112.             String text) {  
  113.         ImageSpan imageSpan = new ImageSpan(context, faceMap);  
  114.         Spanned spanned = imageSpan.getImageSpan(text);  
  115.         textView.setText(spanned);  
  116.     }  
  117.       
  118.     /** 
  119.      * make EditText a View with image at any index   
  120.      * @param context 
  121.      * @param EditText 
  122.      * @param text 
  123.      */  
  124.     public static void setImgTextView(Context context, EditText editText,  
  125.             String text) {  
  126.         ImageSpan imageSpan = new ImageSpan(context, faceMap);  
  127.         Spanned spanned = imageSpan.getImageSpan(text);  
  128.         editText.setText(spanned);  
  129.     }  
  130.   
  131.     /** 
  132.      * return a custom ClickableSpan 
  133.      *  
  134.      * @param context 
  135.      * @param intent 
  136.      * @return 
  137.      */  
  138.     public static MyClickableSpan getClickableSpan(Context context,  
  139.             Intent intent) {  
  140.         return new MyClickableSpan(context, intent);  
  141.     }  
  142.   
  143.     /** 
  144.      * make textview a clickable textview with image<br> 
  145.      * Note: make true the order of textList and intentList are mapped 
  146.      *  
  147.      * @param context 
  148.      *            not null 
  149.      * @param haveImg 
  150.      *            whether this is image in the text,not null 
  151.      * @param textView 
  152.      *            not null 
  153.      * @param textList 
  154.      *            the text should be set to this textview,not null 
  155.      * @param intentList 
  156.      *            the intent map to the text, if the text have no intent mapped 
  157.      *            to, please set a null value.Or it will happen some unknown 
  158.      *            error.<br> 
  159.      *            allow null 
  160.      */  
  161.     public static void setCustomText(Context context, Boolean haveImg,  
  162.             TextView textView, List<String> textList, List<Intent> intentList) {  
  163.         SpannableStringBuilder builder = new SpannableStringBuilder();  
  164.         int end = -1, length = -1;  
  165.         if (intentList != null) {  
  166.             int size = textList.size();  
  167.             Intent intent;  
  168.             for (int i = 0; i < size; i++) {  
  169.                 String text = textList.get(i);  
  170.                 if (TextUtils.isEmpty(text)) {  
  171.                     continue;  
  172.                 }  
  173.                 builder.append(textList.get(i));  
  174.                 if ((intent = intentList.get(i)) != null) {  
  175.                     end = builder.length();  
  176.                     length = textList.get(i).length();  
  177.                     builder.setSpan(getClickableSpan(context, intent), end  
  178.                             - length, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  179.                 }  
  180.                 builder.append(" ");  
  181.             }  
  182.         } else {  
  183.             for (String text : textList) {  
  184.                 builder.append(text).append(" ");  
  185.             }  
  186.         }  
  187.         if (haveImg) {  
  188.             ImageSpan imageSpan = new ImageSpan(context, faceMap);  
  189.             Spanned spanned = imageSpan.getImageSpan(builder);  
  190.             textView.setText(spanned);  
  191.         } else {  
  192.             textView.setText(builder);  
  193.         }  
  194.         textView.setMovementMethod(LinkMovementMethod.getInstance());  
  195.   
  196.     }  
  197.   
  198. }  

    有了这个类,我们就可以方便的实现在TextView中插入Intent和表情了,甚至不用管底层是怎样实现的,也降低了代码的耦合度。

原文地址:https://www.cnblogs.com/xingmeng/p/2629959.html