TextView图文混排(加载网络图片+本地图片、表情)

具体Demo见GitHub:RichTextDemo

无图无真相,下图中的所有内容全在一个TextView中展示。

TextView实现图文混排

主要由SpannableString.setSpan()ImageSapn实现TextView显示图片功能,核心代码如下:

String text = "图文混排内容";
SpannableString spannableString = new SpannableString(text);
Drawable drawable = 来自本地或者网络的图片;
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
spannableString.setSpan(imageSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);  

其中spannableString.setSpan(imageSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)text中图片对应的文本,替换成相应的文图片,实现图文混排。ImageSpan中需传入的Drawable 可以读取本地图片,或者从网络获取。

详细代码如下,具体说明看代码注释:

其中简书中正则表达式显示有问题,正确的为


正则表达式.png
private void setRichText(Context context) {
    float imageSize = textView.getTextSize();
    //图文混排的text,这里用"[]"标示图片
    String text = "这是图文混排的例子:\n网络图片:"
            + "[http://hao.qudao.com/upload/article/20160120/82935299371453253610.jpg][http://b.hiphotos.baidu.com/zhidao/pic/item/d6ca7bcb0a46f21f27c5c194f7246b600d33ae00.jpg]"
            + "\n本地图片:" + "[哈哈][泪][多肉][多肉2]";
    SpannableString spannableString = new SpannableString(text);
    //匹配"[(除了']'任意内容)]"的正则表达式,获取网络图片和本地图片替换位置
    //简书中正则表达式显示有问题,正确如上图
    Pattern pattern = Pattern.compile("\\[[^\\]]+\\]");
    Matcher matcher = pattern.matcher(text);
    while (matcher.find()) {
        ImageSpan imageSpan;
        //匹配的内容,例如[http://hao.qudao.com/upload/article/20160120/82935299371453253610.jpg]或[哈哈]
        String group = matcher.group();
        if (group.contains("http")) {
            //网络图片
            //获取图片url(去掉'['和']')
            String url = group.substring(1, group.length() - 1);
            //异步获取网络图片
            Drawable drawableFromNet = new URLImageParser(textView, context, (int) imageSize).getDrawable(url);
            imageSpan = new ImageSpan(drawableFromNet, ImageSpan.ALIGN_BASELINE);
            //设置网络图片
            spannableString.setSpan(imageSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        } else {
            //本地图片
            if (localIconMap.get(group) != null) {
                //获取本地图片Drawable
                Drawable drawableFromLocal = context.getResources().getDrawable(localIconMap.get(group));
                //获取图片宽高比
                float ratio = drawableFromLocal.getIntrinsicWidth() * 1.0f / drawableFromLocal.getIntrinsicHeight();
                //设置图片宽高
                drawableFromLocal.setBounds(0, 0, (int) (imageSize * ratio), (int)(imageSize));
                imageSpan = new ImageSpan(drawableFromLocal, ImageSpan.ALIGN_BASELINE);
                //设置本地图片
                spannableString.setSpan(imageSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    textView.setText(spannableString);
}

private void initData(Context context) {
    ResourceUtils utils = ResourceUtils.getInstance(context);
    localIconMap = new HashMap<>();
    //获取本地图片标示对应的图片ID(例如[哈哈]对应的R.drawable.haha)
    localIconMap.put(utils.getString("haha"), utils.getDrawableId("haha"));
    localIconMap.put(utils.getString("lei"), utils.getDrawableId("lei"));
    localIconMap.put(utils.getString("duorou"), utils.getDrawableId("duorou"));
    localIconMap.put(utils.getString("duorou2"), utils.getDrawableId("duorou2"));
}  

其中URLImageParser代码如下:

public class URLImageParser {
    private Context mContext;
    private TextView mTextView;
    private int mImageSize;

    /**
     *
     * @param textView 图文混排TextView
     * @param context
     * @param imageSize 图片显示高度
     */
    public URLImageParser(TextView textView, Context context, int imageSize) {
        mTextView = textView;
        mContext = context;
        mImageSize = imageSize;
    }

    public Drawable getDrawable(String url) {
        URLDrawable urlDrawable = new URLDrawable();
        new ImageGetterAsyncTask(mContext, url, urlDrawable).execute(mTextView);
        return urlDrawable;
    }

    public class ImageGetterAsyncTask extends AsyncTask<TextView, Void, Bitmap> {

        private URLDrawable urlDrawable;
        private Context context;
        private String source;
        private TextView textView;

        public ImageGetterAsyncTask(Context context, String source, URLDrawable urlDrawable) {
            this.context = context;
            this.source = source;
            this.urlDrawable = urlDrawable;
        }

        @Override
        protected Bitmap doInBackground(TextView... params) {
            textView = params[0];
            try {
                //下载网络图片,以下是使用Picasso和Glide获取网络图片例子,也可以其他方式下载网络图片

                // 使用Picasso获取网络图片Bitmap
                return Picasso.with(context).load(source).get();
                // 使用Glide获取网络图片Bitmap(使用Glide获取图片bitmap还有待研究)
//                return Glide.with(context).load(source).asBitmap().fitCenter().into(mImageSize * 3, mImageSize * 3).get();
            } catch (Exception e) {
                return null;
            }
        }

        @Override
        protected void onPostExecute(final Bitmap bitmap) {
            try {
                //获取图片宽高比
                float ratio = bitmap.getWidth() * 1.0f / bitmap.getHeight();
                Drawable bitmapDrawable = new BitmapDrawable(context.getResources(), bitmap);
                bitmapDrawable.setBounds(0, 0, (int) (mImageSize * ratio), mImageSize);
                //设置图片宽、高(这里传入的mImageSize为字体大小,所以,设置的高为字体大小,宽为按宽高比缩放)
                urlDrawable.setBounds(0, 0, (int) (mImageSize * ratio), mImageSize);
                urlDrawable.drawable = bitmapDrawable;
                //两次调用invalidate才会在异步加载完图片后,刷新图文混排TextView,显示出图片
                urlDrawable.invalidateSelf();
                textView.invalidate();
            } catch (Exception e) {
                /* Like a null bitmap, etc. */
            }
        }
    }
}    

URLDrawable代码如下:

public class URLDrawable extends BitmapDrawable {
    // the drawable that you need to set, you could set the initial drawing
    // with the loading image if you need to
    protected Drawable drawable;

    @Override
    public void draw(Canvas canvas) {
        // override the draw to facilitate refresh function later
        if(drawable != null) {
            drawable.draw(canvas);
        }
    }
}  

ResourceUtils代码如下:

public class ResourceUtils {
    private static ResourceUtils resourceUtils;
    private static Context context;
    private Resources resources;
    private String packageName;

    public static ResourceUtils getInstance(Context context) {
        if (resourceUtils == null) {
            synchronized (ResourceUtils.class) {
                resourceUtils = new ResourceUtils(context);
            }
        }

        return resourceUtils;
    }

    public ResourceUtils(Context context) {
        this.context = context;
        resources = context.getResources();
        packageName = context.getPackageName();
    }

    public int getStringId(String name) {
        try {
            //对应values:strings.xml文件
            return this.resources.getIdentifier(name, "string", packageName);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    public String getString(String name) {
        try {
            return this.resources.getString(getStringId(name));
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    public int getDrawableId(String name) {
        try {
            //对应drawable-***文件夹中的图片
            return this.resources.getIdentifier(name, "drawable", packageName);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

}  

具体Demo见GitHub:RichTextDemo

参考:

Android之Glide获取图片Path、Bitmap用法
Display images on Android using TextView and Html.ImageGetter asynchronously?
Android HTML ImageGetter as AsyncTask
Android ImageGetter images overlapping text

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容