擷取網絡圖檔的ImageSpan
- 擷取網絡圖檔的ImageSpan
- 效果
- 代碼
- 實作方式
- 原理
TextView做圖文混排時可能用到Html下的ImageGetter工具或者ImageSpan。
ImageGetter擷取網絡圖檔的例子很多, 但是如果文本中不包含圖檔的寬高資訊,那麼考慮預留多少空間是挺麻煩的事。
本例使用ImageSpan+Glide擷取網絡圖檔, 适用于文本内容較短的場景(因為擷取到圖檔後會重新整理控件,内容多會卡頓)。圖檔加載庫換成類似的也行, 原理都一樣
效果
代碼
/**
* 擷取網絡圖檔的ImageSpan
* Created by Yomii on 2016/10/13.
*/
public class UrlImageSpan extends ImageSpan {
private String url;
private TextView tv;
private boolean picShowed;
public UrlImageSpan(Context context, String url, TextView tv) {
super(context, R.mipmap.pic_holder);
this.url = url;
this.tv = tv;
}
@Override
public Drawable getDrawable() {
if (!picShowed) {
Glide.with(tv.getContext()).load(url).asBitmap().into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
Resources resources = tv.getContext().getResources();
int targetWidth = (int) (resources.getDisplayMetrics().widthPixels * );
Bitmap zoom = zoom(resource, targetWidth);
BitmapDrawable b = new BitmapDrawable(resources, zoom);
b.setBounds(, , b.getIntrinsicWidth(), b.getIntrinsicHeight());
Field mDrawable;
Field mDrawableRef;
try {
mDrawable = ImageSpan.class.getDeclaredField("mDrawable");
mDrawable.setAccessible(true);
mDrawable.set(UrlImageSpan.this, b);
mDrawableRef = DynamicDrawableSpan.class.getDeclaredField("mDrawableRef");
mDrawableRef.setAccessible(true);
mDrawableRef.set(UrlImageSpan.this, null);
picShowed = true;
tv.setText(tv.getText());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
});
}
return super.getDrawable();
}
/**
* 按寬度縮放圖檔
*
* @param bmp 需要縮放的圖檔源
* @param newW 需要縮放成的圖檔寬度
*
* @return 縮放後的圖檔
*/
public static Bitmap zoom(@NonNull Bitmap bmp, int newW) {
// 獲得圖檔的寬高
int width = bmp.getWidth();
int height = bmp.getHeight();
// 計算縮放比例
float scale = ((float) newW) / width;
// 取得想要縮放的matrix參數
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
// 得到新的圖檔
Bitmap newbm = Bitmap.createBitmap(bmp, , , width, height, matrix, true);
return newbm;
}
}
實作方式
先通過Uri或者ResId的構造傳入一個占位圖。
在getDrawable這個方法中, 如果圖檔未加載, 那麼異步擷取圖檔, 否則直接傳回父類。
成功擷取到圖檔的回調中, 通過反射把占位圖替換為擷取到的圖檔, 然後重新整理文本控件就可以。縮放部分不是核心代碼,擷取到圖檔怎麼改都行。
原理
首先看下父類ImageSpan, 隻有一個核心方法getDrawable, 其他都是通過不同方式擷取圖檔的構造。
@Override
public Drawable getDrawable() {
Drawable drawable = null;
if (mDrawable != null) {
drawable = mDrawable;
} else if (mContentUri != null) {
Bitmap bitmap = null;
try {
InputStream is = mContext.getContentResolver().openInputStream(
mContentUri);
bitmap = BitmapFactory.decodeStream(is);
drawable = new BitmapDrawable(mContext.getResources(), bitmap);
drawable.setBounds(, , drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
is.close();
} catch (Exception e) {
Log.e("sms", "Failed to loaded content " + mContentUri, e);
}
} else {
try {
drawable = mContext.getDrawable(mResourceId);
drawable.setBounds(, , drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
} catch (Exception e) {
Log.e("sms", "Unable to find resource: " + mResourceId);
}
}
return drawable;
}
這個方法中對應不同的構造擷取一個Drawable并傳回, 并且首先判斷mDrawable屬性, 也就是說我們把擷取到的圖檔改成它就行了。
但是光改它還不行, 依然沒有到核心方法draw(), 看一下它在父類中是如何被調用的。
在DynamicDrawableSpan的draw方法中,繪制的bitmap是從getCachedDrawable()方法中擷取的。
可以看到在getCachedDrawable()裡并不是優先調用getDrawable而是調用一個緩存弱引用mDrawableRef。
由于擷取圖檔是異步的,是以這個mDrawableRef必定存在,它就是我們的占位圖。于是隻要在回調時把它置空并重新整理控件,就能成功調用getDrawable,傳回之前替換進去的目标圖檔。
@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();
int transY = bottom - b.getBounds().bottom;
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
}
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
private Drawable getCachedDrawable() {
WeakReference<Drawable> wr = mDrawableRef;
Drawable d = null;
if (wr != null)
d = wr.get();
if (d == null) {
d = getDrawable();
mDrawableRef = new WeakReference<Drawable>(d);
}
return d;
}