天天看點

擷取網絡圖檔的ImageSpan擷取網絡圖檔的ImageSpan

擷取網絡圖檔的ImageSpan

  • 擷取網絡圖檔的ImageSpan
    • 效果
    • 代碼
    • 實作方式
      • 原理

TextView做圖文混排時可能用到Html下的ImageGetter工具或者ImageSpan。

ImageGetter擷取網絡圖檔的例子很多, 但是如果文本中不包含圖檔的寬高資訊,那麼考慮預留多少空間是挺麻煩的事。

本例使用ImageSpan+Glide擷取網絡圖檔, 适用于文本内容較短的場景(因為擷取到圖檔後會重新整理控件,内容多會卡頓)。圖檔加載庫換成類似的也行, 原理都一樣

效果

擷取網絡圖檔的ImageSpan擷取網絡圖檔的ImageSpan

代碼

/**
 * 擷取網絡圖檔的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;
    }
           

繼續閱讀