?? ? ?( 第二屆 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方法:
?
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,我們來看一下他的源碼:
?
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); } }
?是不是有點眉目了,我們直接繼承這個類,重寫他的方法不就可以了嗎?大膽假設,小心求證,我們新建一個類:
?
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>標簽可以插入圖片,那這個類是否提供這個功能呢?帶著這個疑問我們可以進去看看,其中有個接口:
?
/** * Retrieves images for HTML <img> tags. */ public static interface ImageGetter { /** * This methos is called when the HTML parser encounters an * <img> 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,上代碼:
?
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); } }
?
?? ? ?到目前為止可以說關鍵代碼都已經實現了,但是會有人問,我該如何使用這兩個類呢?下面,我們在實現一個工具類來封裝這兩個類的方法,以方便調用:
?
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
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
