博客分类: Android
androidgoogle 
      (第二届 Google 暑期大学生博客分享大赛 - 2011 Android 成长篇)
      做过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代码  
public void onClick(View widget) {  
        Uri uri = Uri.parse(getURL());  
        Context context = widget.getContext();  
        Intent intent = new Intent(Intent.ACTION_VIEW, uri);  
        intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());  
        context.startActivity(intent);  
    }  
      大家看到了吧startActivity,多么熟悉的方法。既然它能实现,为什么我们不能呢,答案是可以的。我们接着跟踪代码,可以看到URLSpan其实继承的是android.text.style.ClickableSpan,我们来看一下他的源码:

Java代码  
public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {  
  
    /** 
     * Performs the click action associated with this span. 
     */  
    public abstract void onClick(View widget);  
     
    /** 
     * Makes the text underlined and in the link color. 
     */  
    @Override  
    public void updateDrawState(TextPaint ds) {  
        ds.setColor(ds.linkColor);  
        ds.setUnderlineText(true);  
    }  
}  
是不是有点眉目了,我们直接继承这个类,重写他的方法不就可以了吗?大胆假设,小心求证,我们新建一个类:

Java代码  
import android.content.Context;  
import android.content.Intent;  
import android.text.TextPaint;  
import android.text.style.ClickableSpan;  
import android.view.View;  
  
/** 
* If an object of this type is attached to the text of a TextView with a 
* movement method of LinkMovementMethod, the affected spans of text can be 
* selected. If clicked, the {@link #onClick} method will be called. 
*  
* @author 张宁 
*/  
public class MyClickableSpan extends ClickableSpan {  
  
    int color = -1;  
    private Context context;  
    private Intent intent;  
  
    public MyClickableSpan(Context context, Intent intent) {  
        this(-1, context, intent);  
    }  
  
    /** 
     * constructor 
     * @param color the link color 
     * @param context 
     * @param intent 
     */  
    public MyClickableSpan(int color, Context context, Intent intent) {  
        if (color!=-1) {  
            this.color = color;  
        }  
        this.context = context;  
        this.intent = intent;  
    }  
  
    /** 
     * Performs the click action associated with this span. 
     */  
    public void onClick(View widget){  
        context.startActivity(intent);  
    };  
  
    /** 
     * Makes the text without underline. 
     */  
    @Override  
    public void updateDrawState(TextPaint ds) {  
        if (color == -1) {  
            ds.setColor(ds.linkColor);  
        } else {  
            ds.setColor(color);  
        }  
        ds.setUnderlineText(false);  
    }  
}  

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

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

     看到<code>source</code>这个没,熟悉吧,结合URLSpan的用法,我们是否可以配合Spanned实现一个
ImageSpan呢?OK,上代码:

Java代码  
import java.util.Map;  
import java.util.Set;  
  
import android.content.Context;  
import android.graphics.drawable.Drawable;  
import android.text.Html;  
import android.text.Spanned;  
import android.text.Html.ImageGetter;  
  
/** 
* this is a class which defining a spanned with image 
* @author 张宁 

*/  
public class ImageSpan {  
      
    /** 
     * the map of face. 
     */  
    private Map<String, String> faceMap;  
    private Context context;  
      
    public ImageSpan(Context context, Map<String, String> faceMap){  
        this.context = context;  
        this.faceMap = faceMap;  
    }   
  
    /** 
     * get the image by the given key 
     */  
    private ImageGetter imageGetter = new Html.ImageGetter() {  
        @Override  
        public Drawable getDrawable(String source) {  
            Drawable drawable = null;  
            String sourceName = context.getPackageName() + ":drawable/"  
                    + source;  
            int id = context.getResources().getIdentifier(sourceName, null, null);  
            if (id != 0) {  
                drawable = context.getResources().getDrawable(id);  
                if (drawable != null) {  
                    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),  
                            drawable.getIntrinsicHeight());  
                }  
            }  
            return drawable;  
        }  
    };  
      
    /** 
     * return a {@link Spanned} with image 
     * @param text 
     * @return 
     */  
    public Spanned getImageSpan(CharSequence text){  
        String cs = text.toString();  
        if (faceMap != null) {  
            Set<String> keys = faceMap.keySet();  
            for (String key : keys) {  
                if (cs.contains(key)) {  
                    cs = cs.replace(key, "<img src='" + faceMap.get(key) + "'>");  
                }  
            }  
        }  
        return Html.fromHtml(cs, imageGetter, null);  
    }  
  
}  

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

Java代码  
import java.util.HashMap;  
import java.util.List;  
import java.util.Map;  
  
import android.content.Context;  
import android.content.Intent;  
import android.text.SpannableStringBuilder;  
import android.text.Spanned;  
import android.text.TextUtils;  
import android.text.method.LinkMovementMethod;  
import android.widget.EditText;  
import android.widget.TextView;  
  
/** 
* TextView with intent that can redirect to a new activity 
*  
* @author 张宁 
*  
*/  
public class CustomTextView {  
  
    private static Map<String, String> faceMap;  
  
    static {  
        faceMap = new HashMap<String, String>();  
        faceMap.put("[哭]", "face_1");  
        faceMap.put("[怒]", "face_2");  
    }  
  
    /** 
     * make textview a clickable textview<br> 
     * Note: make true the order of textList and intentList are mapped 
     *  
     * @param context 
     * @param textView 
     * @param textList 
     *            the text should be set to this textview,not null 
     * @param intentList 
     *            the intent map to the text, if the text have no intent mapped 
     *            to, please set a null value.Or it will happen some unknown 
     *            error.<br> 
     *            not null 
     */  
    public static void setClickableTextView(Context context, TextView textView,  
            List<String> textList, List<Intent> intentList) {  
        if (textList == null || intentList == null) {  
            return;  
        }  
        SpannableStringBuilder builder = new SpannableStringBuilder();  
        int end = -1, length = -1;  
        int size = textList.size();  
        Intent intent;  
        for (int i = 0; i < size; i++) {  
            String text = textList.get(i);  
            if (TextUtils.isEmpty(text)) {  
                continue;  
            }  
            builder.append(textList.get(i));  
            if ((intent = intentList.get(i)) != null) {  
                end = builder.length();  
                length = textList.get(i).length();  
                builder.setSpan(getClickableSpan(context, intent),  
                        end - length, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
            }  
            builder.append(" ");  
        }  
        textView.setText(builder);  
        textView.setFocusable(true);  
        textView.setMovementMethod(LinkMovementMethod.getInstance());  
    }  
      
    /** 
     *  make textview a clickable textview<br> 
     *  Note: make true the order of textList and intentList are mapped 
     * @param context 
     * @param textView 
     * @param text 
     * @param intent 
     */  
    public static void setClickableTextView(Context context, TextView textView,  
            String text, Intent intent) {  
        SpannableStringBuilder builder = new SpannableStringBuilder(text);  
        builder.setSpan(getClickableSpan(context, intent), 0, text.length(),   
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
        textView.setText(builder);  
        textView.setMovementMethod(LinkMovementMethod.getInstance());  
    }  
  
    /** 
     * make TextView a View with image at any index   
     * @param context 
     * @param textView 
     * @param textList 
     */  
    public static void setImgTextView(Context context, TextView textView,  
            List<String> textList) {  
        StringBuilder builder = new StringBuilder();  
        for (int i = 0; i < textList.size(); i++) {  
            builder.append(textList.get(i)).append(" ");  
        }  
        setImgTextView(context, textView, builder.toString());  
  
    }  
  
    /** 
     * make TextView a View with image at any index   
     * @param context 
     * @param textView 
     * @param text 
     */  
    public static void setImgTextView(Context context, TextView textView,  
            String text) {  
        ImageSpan imageSpan = new ImageSpan(context, faceMap);  
        Spanned spanned = imageSpan.getImageSpan(text);  
        textView.setText(spanned);  
    }  
      
    /** 
     * make EditText a View with image at any index   
     * @param context 
     * @param EditText 
     * @param text 
     */  
    public static void setImgTextView(Context context, EditText editText,  
            String text) {  
        ImageSpan imageSpan = new ImageSpan(context, faceMap);  
        Spanned spanned = imageSpan.getImageSpan(text);  
        editText.setText(spanned);  
    }  
  
    /** 
     * return a custom ClickableSpan 
     *  
     * @param context 
     * @param intent 
     * @return 
     */  
    public static MyClickableSpan getClickableSpan(Context context,  
            Intent intent) {  
        return new MyClickableSpan(context, intent);  
    }  
  
    /** 
     * make textview a clickable textview with image<br> 
     * Note: make true the order of textList and intentList are mapped 
     *  
     * @param context 
     *            not null 
     * @param haveImg 
     *            whether this is image in the text,not null 
     * @param textView 
     *            not null 
     * @param textList 
     *            the text should be set to this textview,not null 
     * @param intentList 
     *            the intent map to the text, if the text have no intent mapped 
     *            to, please set a null value.Or it will happen some unknown 
     *            error.<br> 
     *            allow null 
     */  
    public static void setCustomText(Context context, Boolean haveImg,  
            TextView textView, List<String> textList, List<Intent> intentList) {  
        SpannableStringBuilder builder = new SpannableStringBuilder();  
        int end = -1, length = -1;  
        if (intentList != null) {  
            int size = textList.size();  
            Intent intent;  
            for (int i = 0; i < size; i++) {  
                String text = textList.get(i);  
                if (TextUtils.isEmpty(text)) {  
                    continue;  
                }  
                builder.append(textList.get(i));  
                if ((intent = intentList.get(i)) != null) {  
                    end = builder.length();  
                    length = textList.get(i).length();  
                    builder.setSpan(getClickableSpan(context, intent), end  
                            - length, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
                }  
                builder.append(" ");  
            }  
        } else {  
            for (String text : textList) {  
                builder.append(text).append(" ");  
            }  
        }  
        if (haveImg) {  
            ImageSpan imageSpan = new ImageSpan(context, faceMap);  
            Spanned spanned = imageSpan.getImageSpan(builder);  
            textView.setText(spanned);  
        } else {  
            textView.setText(builder);  
        }  
        textView.setMovementMethod(LinkMovementMethod.getInstance());  
  
    }  
  
}  

    有了这个类,我们就可以方便的实现在TextView中插入Intent和表情了,甚至不用管底层是怎样实现的,也降低了代码的耦合度。但是又回到我写这篇文章的目的:希望大家能得到“渔”而不仅仅是“鱼”。
    Ok,任务完成。源码奉上。
注:原创作品,转载请标明出处:

转载:
http://zhangning290.iteye.com/blog/1134286

posted on 2012-12-21 19:04  zhengbeibei  阅读(1115)  评论(0编辑  收藏  举报