package org.aisen.weibo.sina.ui.widget; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ImageSpan; import android.text.style.URLSpan; import android.text.util.Linkify; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; import org.aisen.android.common.context.GlobalContext; import org.aisen.android.common.utils.BitmapUtil; import org.aisen.android.common.utils.KeyGenerator; import org.aisen.android.common.utils.Logger; import org.aisen.android.component.bitmaploader.BitmapLoader; import org.aisen.android.component.bitmaploader.core.LruMemoryCache; import org.aisen.android.component.bitmaploader.core.MyBitmap; import org.aisen.android.network.task.TaskException; import org.aisen.android.network.task.WorkTask; import org.aisen.android.support.textspan.ClickableTextViewMentionLinkOnTouchListener; import org.aisen.android.support.textspan.MyURLSpan; import org.aisen.android.ui.activity.basic.BaseActivity; import org.aisen.weibo.sina.R; import org.aisen.weibo.sina.base.AppSettings; import org.aisen.weibo.sina.support.sqlit.EmotionsDB; import java.lang.ref.WeakReference; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 加载表情,添加链接两个功能<br/> * * @author wangdan * */ public class AisenTextView extends TextView { static final String TAG = "AisenTextView"; private static final int CORE_POOL_SIZE = 5; /** * 默认执行最大线程是128个 */ private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { int count = mCount.getAndIncrement(); Logger.v(TAG, "new Thread " + "AisenTextView #" + count); return new Thread(r, "AisenTextView #" + count); } }; /** * 执行队列,默认是10个,超过10个后会开启新的线程,如果已运行线程大于 {@link #MAXIMUM_POOL_SIZE},执行异常策略 */ private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(10); private static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); public static LruMemoryCache<String, SpannableString> stringMemoryCache; private EmotionTask emotionTask; private String content; private boolean innerWeb = AppSettings.isInnerBrower(); public AisenTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public AisenTextView(Context context, AttributeSet attrs) { super(context, attrs); } public AisenTextView(Context context) { super(context); } public void setContent(String text) { if (stringMemoryCache == null) { stringMemoryCache = new LruMemoryCache<String, SpannableString>(200) { }; } boolean replace = false; if (!replace) replace = innerWeb != AppSettings.isInnerBrower(); innerWeb = AppSettings.isInnerBrower(); if (!replace && TextUtils.isEmpty(text)) { super.setText(text); return; } if (!replace && !TextUtils.isEmpty(content) && content.equals(text)) return; content = text; if (emotionTask != null) emotionTask.cancel(true); String key = KeyGenerator.generateMD5(text); SpannableString spannableString = stringMemoryCache.get(key); if (spannableString != null) { Logger.v(TAG, "从内存中加载spannable数据"); super.setText(spannableString); } else { Logger.v(TAG, "开启线程,开始加载spannable数据"); super.setText(text); emotionTask = new EmotionTask(this); emotionTask.executeOnExecutor(THREAD_POOL_EXECUTOR); } setClickable(false); setOnTouchListener(onTouchListener); } static class EmotionTask extends WorkTask<Void, SpannableString, Boolean> { WeakReference<TextView> textViewRef; EmotionTask(TextView textView) { textViewRef = new WeakReference<TextView>(textView); } @Override public Boolean workInBackground(Void... params) throws TaskException { TextView textView = textViewRef.get(); if (textView == null) return false; if (TextUtils.isEmpty(textView.getText())) return false; // android.view.ViewRootImpl$CalledFromWrongThreadException Only the original thread that created a view hierarchy can touch its views. // 把getText + 一个空字符试试,可能是直接取值会刷UI String text = textView.getText() + ""; SpannableString spannableString = SpannableString.valueOf(text); Matcher localMatcher = Pattern.compile("\\[(\\S+?)\\]").matcher(spannableString); while (localMatcher.find()) { if (isCancelled()) break; String key = localMatcher.group(0); int k = localMatcher.start(); int m = localMatcher.end(); byte[] data = EmotionsDB.getEmotion(key); if (data == null) continue; MyBitmap mb = BitmapLoader.getInstance().getImageCache().getBitmapFromMemCache(key, null); Bitmap b = null; if (mb != null) { b = mb.getBitmap(); } else { b = BitmapFactory.decodeByteArray(data, 0, data.length); int size = BaseActivity.getRunningActivity().getResources().getDimensionPixelSize(R.dimen.emotion_size); b = BitmapUtil.zoomBitmap(b, size); // 添加到内存中 BitmapLoader.getInstance().getImageCache().addBitmapToMemCache(key, null, new MyBitmap(b, key)); } ImageSpan l = new ImageSpan(GlobalContext.getInstance(), b, ImageSpan.ALIGN_BASELINE); spannableString.setSpan(l, k, m, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } // 用户名称 // Pattern pattern = Pattern.compile("@([a-zA-Z0-9_\\-\\u4e00-\\u9fa5]+)"); Pattern pattern = Pattern.compile("@[\\w\\p{InCJKUnifiedIdeographs}-]{1,26}"); String scheme = "org.aisen.weibo.sina.userinfo://"; Linkify.addLinks(spannableString, pattern, scheme); // 网页链接 scheme = "http://"; // 启用内置浏览器 if (AppSettings.isInnerBrower()) scheme = "aisen://"; Linkify.addLinks(spannableString, Pattern.compile("http://[a-zA-Z0-9+&@#/%?=~_\\-|!:,\\.;]*[a-zA-Z0-9+&@#/%=~_|]"), scheme); // 话题 Pattern dd = Pattern.compile("#[\\p{Print}\\p{InCJKUnifiedIdeographs}&&[^#]]+#"); //Pattern dd = Pattern.compile("#([a-zA-Z0-9_\\-\\u4e00-\\u9fa5]+)#"); scheme = "org.aisen.weibo.sina.topics://"; Linkify.addLinks(spannableString, dd, scheme); URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length(), URLSpan.class); MyURLSpan weiboSpan = null; for (URLSpan urlSpan : urlSpans) { weiboSpan = new MyURLSpan(urlSpan.getURL()); // if (AppSettings.isHightlight()) // weiboSpan.setColor(Color.parseColor(color)); // else // weiboSpan.setColor(0); int start = spannableString.getSpanStart(urlSpan); int end = spannableString.getSpanEnd(urlSpan); try { spannableString.removeSpan(urlSpan); } catch (Exception e) { } spannableString.setSpan(weiboSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } publishProgress(spannableString); String key = KeyGenerator.generateMD5(spannableString.toString()); stringMemoryCache.put(key, spannableString); Logger.v(TAG, String.format("添加spannable到内存中,现在共有%d个spannable", stringMemoryCache.size())); return null; } @Override protected void onProgressUpdate(SpannableString... values) { super.onProgressUpdate(values); TextView textView = textViewRef.get(); if (textView == null) return; try { if (values != null && values.length > 0) textView.setText(values[0]); } catch (Exception e) { e.printStackTrace(); } } } private OnTouchListener onTouchListener = new OnTouchListener() { ClickableTextViewMentionLinkOnTouchListener listener = new ClickableTextViewMentionLinkOnTouchListener(); @Override public boolean onTouch(View v, MotionEvent event) { return listener.onTouch(v, event); } }; }