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

来源:互联网 发布:javascript 表单 编辑:程序博客网 时间:2024/04/29 04:50
做过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代码
public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {   
  1.   
  2.     /**  
  3.      * Performs the click action associated with this span. 
  4.      */  
  5.     public abstract void onClick(View widget);   
  6.       
  7.     /**  
  8.      * Makes the text underlined and in the link color. 
  9.      */  
  10.     @Override  
  11.     public void updateDrawState(TextPaint ds) {   
  12.         ds.setColor(ds.linkColor);   
  13.         ds.setUnderlineText(true);   
  14.     }   
  15. }  

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

 

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

 

      在这个类里,我们重写了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和表情了,甚至不用管底层是怎样实现的,也降低了代码的耦合度。但是又回到我写这篇文章的目的:希望大家能得到“渔”而不仅仅是“鱼”。

    Ok,任务完成。源码奉上。

  • TextViewWithIntent.rar (170.3 KB)