SpannableString相關工具類
阿新 • • 發佈:2019-01-30
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.MaskFilterSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.ReplacementSpan;
import android.text.style.ScaleXSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
public final class SpannableStringUtils {
private SpannableStringUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
public static final int ALIGN_BOTTOM = 0;
public static final int ALIGN_BASELINE = 1;
public static final int ALIGN_CENTER = 2;
public static final int ALIGN_TOP = 3;
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})
@Retention(RetentionPolicy.SOURCE)
public @interface Align {
}
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
public static class Builder {
private int defaultValue = 0x12000000;
private CharSequence text;
private int flag;
@ColorInt
private int foregroundColor;
@ColorInt
private int backgroundColor;
@ColorInt
private int quoteColor;
private int stripeWidth;
private int quoteGapWidth;
private boolean isLeadingMargin;
private int first;
private int rest;
private int margin;
private boolean isBullet;
private int bulletColor;
private int bulletRadius;
private int bulletGapWidth;
private int fontSize;
private boolean fontSizeIsDp;
private float proportion;
private float xProportion;
private boolean isStrikethrough;
private boolean isUnderline;
private boolean isSuperscript;
private boolean isSubscript;
private boolean isBold;
private boolean isItalic;
private boolean isBoldItalic;
private String fontFamily;
private Typeface typeface;
private Alignment alignment;
private boolean imageIsBitmap;
private Bitmap bitmap;
private boolean imageIsDrawable;
private Drawable drawable;
private boolean imageIsUri;
private Uri uri;
private boolean imageIsResourceId;
@DrawableRes
private int resourceId;
@Align
int align;
private ClickableSpan clickSpan;
private String url;
private boolean isBlur;
private float blurRadius;
private BlurMaskFilter.Blur style;
private SpannableStringBuilder mBuilder;
public Builder() {
flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
foregroundColor = defaultValue;
backgroundColor = defaultValue;
quoteColor = defaultValue;
margin = -1;
fontSize = -1;
proportion = -1;
xProportion = -1;
align = ALIGN_BOTTOM;
mBuilder = new SpannableStringBuilder();
}
/**
* 設定標識
*
* @param flag <ul>
* <li>{@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}</li>
* <li>{@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}</li>
* <li>{@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}</li>
* <li>{@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}</li>
* </ul>
* @return {@link Builder}
*/
public Builder setFlag(int flag) {
this.flag = flag;
return this;
}
/**
* 設定前景色
*
* @param color 前景色
* @return {@link Builder}
*/
public Builder setForegroundColor(@ColorInt int color) {
this.foregroundColor = color;
return this;
}
/**
* 設定背景色
*
* @param color 背景色
* @return {@link Builder}
*/
public Builder setBackgroundColor(@ColorInt int color) {
this.backgroundColor = color;
return this;
}
/**
* 設定引用線的顏色
*
* @param color 引用線的顏色
* @return {@link Builder}
*/
public Builder setQuoteColor(@ColorInt int color) {
this.quoteColor = color;
this.stripeWidth = 2;
this.quoteGapWidth = 2;
return this;
}
/**
* 設定引用線的顏色
*
* @param color 引用線的顏色
* @param stripeWidth 引用線線寬
* @param quoteGapWidth 引用線和文字間距
* @return {@link Builder}
*/
public Builder setQuoteColor(@ColorInt int color, int stripeWidth, int quoteGapWidth) {
this.quoteColor = color;
this.stripeWidth = stripeWidth;
this.quoteGapWidth = quoteGapWidth;
return this;
}
/**
* 設定縮排
*
* @param first 首行縮排
* @param rest 剩餘行縮排
* @return {@link Builder}
*/
public Builder setLeadingMargin(int first, int rest) {
this.first = first;
this.rest = rest;
isLeadingMargin = true;
return this;
}
/**
* 設定間距
*
* @param margin 間距
* @return {@link Builder}
*/
public Builder setMargin(int margin) {
this.margin = margin;
this.text = " " + this.text;
return this;
}
/**
* 設定列表標記
*
* @param gapWidth 列表標記和文字間距離
* @return {@link Builder}
*/
public Builder setBullet(@ColorInt int gapWidth) {
this.bulletColor = 0;
this.bulletRadius = 3;
this.bulletGapWidth = gapWidth;
isBullet = true;
return this;
}
/**
* 設定列表標記
*
* @param color 列表標記的顏色
* @param radius 列表標記顏色
* @param gapWidth 列表標記和文字間距離
* @return {@link Builder}
*/
public Builder setBullet(@ColorInt int color, int radius, int gapWidth) {
this.bulletColor = color;
this.bulletRadius = radius;
this.bulletGapWidth = gapWidth;
isBullet = true;
return this;
}
/**
* 設定字型尺寸
*
* @param size 尺寸
* @return {@link Builder}
*/
public Builder setFontSize(int size) {
this.fontSize = size;
this.fontSizeIsDp = false;
return this;
}
/**
* 設定字型尺寸
*
* @param size 尺寸
* @param isDp 是否使用dip
* @return {@link Builder}
*/
public Builder setFontSize(int size, boolean isDp) {
this.fontSize = size;
this.fontSizeIsDp = isDp;
return this;
}
/**
* 設定字型比例
*
* @param proportion 比例
* @return {@link Builder}
*/
public Builder setProportion(float proportion) {
this.proportion = proportion;
return this;
}
/**
* 設定字型橫向比例
*
* @param proportion 比例
* @return {@link Builder}
*/
public Builder setXProportion(float proportion) {
this.xProportion = proportion;
return this;
}
/**
* 設定刪除線
*
* @return {@link Builder}
*/
public Builder setStrikethrough() {
this.isStrikethrough = true;
return this;
}
/**
* 設定下劃線
*
* @return {@link Builder}
*/
public Builder setUnderline() {
this.isUnderline = true;
return this;
}
/**
* 設定上標
*
* @return {@link Builder}
*/
public Builder setSuperscript() {
this.isSuperscript = true;
return this;
}
/**
* 設定下標
*
* @return {@link Builder}
*/
public Builder setSubscript() {
this.isSubscript = true;
return this;
}
/**
* 設定粗體
*
* @return {@link Builder}
*/
public Builder setBold() {
isBold = true;
return this;
}
/**
* 設定斜體
*
* @return {@link Builder}
*/
public Builder setItalic() {
isItalic = true;
return this;
}
/**
* 設定粗斜體
*
* @return {@link Builder}
*/
public Builder setBoldItalic() {
isBoldItalic = true;
return this;
}
/**
* 設定字體系列
*
* @param fontFamily 字體系列
* <ul>
* <li>monospace</li>
* <li>serif</li>
* <li>sans-serif</li>
* </ul>
* @return {@link Builder}
*/
public Builder setFontFamily(@NonNull String fontFamily) {
this.fontFamily = fontFamily;
return this;
}
/**
* 設定字型
*
* @param typeface 字型
* @return {@link Builder}
*/
public Builder setTypeface(@NonNull Typeface typeface) {
this.typeface = typeface;
return this;
}
/**
* 設定對齊
*
* @param alignment 對其方式
* <ul>
* <li>{@link Alignment#ALIGN_NORMAL}正常</li>
* <li>{@link Alignment#ALIGN_OPPOSITE}相反</li>
* <li>{@link Alignment#ALIGN_CENTER}居中</li>
* </ul>
* @return {@link Builder}
*/
public Builder setAlign(@NonNull Alignment alignment) {
this.alignment = alignment;
return this;
}
/**
* 設定圖片
*
* @param bitmap 圖片點陣圖
* @return {@link Builder}
*/
public Builder setBitmap(@NonNull Bitmap bitmap) {
return setBitmap(bitmap, align);
}
/**
* 設定圖片
*
* @param bitmap 圖片點陣圖
* @param align 對齊
* <ul>
* <li>{@link Align#ALIGN_TOP}頂部對齊</li>
* <li>{@link Align#ALIGN_CENTER}居中對齊</li>
* <li>{@link Align#ALIGN_BASELINE}基線對齊</li>
* <li>{@link Align#ALIGN_BOTTOM}底部對齊</li>
* </ul>
* @return {@link Builder}
*/
public Builder setBitmap(@NonNull Bitmap bitmap, @Align int align) {
this.bitmap = bitmap;
this.align = align;
this.text = " " + this.text;
imageIsBitmap = true;
return this;
}
/**
* 設定圖片
*
* @param drawable 圖片資源
* @return {@link Builder}
*/
public Builder setDrawable(@NonNull Drawable drawable) {
return setDrawable(drawable, align);
}
/**
* 設定圖片
*
* @param drawable 圖片資源
* @param align 對齊
* <ul>
* <li>{@link Align#ALIGN_TOP}頂部對齊</li>
* <li>{@link Align#ALIGN_CENTER}居中對齊</li>
* <li>{@link Align#ALIGN_BASELINE}基線對齊</li>
* <li>{@link Align#ALIGN_BOTTOM}底部對齊</li>
* </ul>
* @return {@link Builder}
*/
public Builder setDrawable(@NonNull Drawable drawable, @Align int align) {
this.drawable = drawable;
this.align = align;
this.text = " " + this.text;
imageIsDrawable = true;
return this;
}
/**
* 設定圖片
*
* @param uri 圖片uri
* @return {@link Builder}
*/
public Builder setUri(@NonNull Uri uri) {
setUri(uri, ALIGN_BOTTOM);
return this;
}
/**
* 設定圖片
*
* @param uri 圖片uri
* @param align 對齊
* <ul>
* <li>{@link Align#ALIGN_TOP}頂部對齊</li>
* <li>{@link Align#ALIGN_CENTER}居中對齊</li>
* <li>{@link Align#ALIGN_BASELINE}基線對齊</li>
* <li>{@link Align#ALIGN_BOTTOM}底部對齊</li>
* </ul>
* @return {@link Builder}
*/
public Builder setUri(@NonNull Uri uri, @Align int align) {
this.uri = uri;
this.align = align;
this.text = " " + this.text;
imageIsUri = true;
return this;
}
/**
* 設定圖片
*
* @param resourceId 圖片資源id
* @return {@link Builder}
*/
public Builder setResourceId(@DrawableRes int resourceId) {
return setResourceId(resourceId, align);
}
/**
* 設定圖片
*
* @param resourceId 圖片資源id
* @param align 對齊
* @return {@link Builder}
*/
public Builder setResourceId(@DrawableRes int resourceId, @Align int align) {
this.resourceId = resourceId;
this.align = align;
this.text = " " + this.text;
imageIsResourceId = true;
return this;
}
/**
* 設定點選事件
* <p>需新增view.setMovementMethod(LinkMovementMethod.getInstance())</p>
*
* @param clickSpan 點選事件
* @return {@link Builder}
*/
public Builder setClickSpan(@NonNull ClickableSpan clickSpan) {
this.clickSpan = clickSpan;
return this;
}
/**
* 設定超連結
* <p>需新增view.setMovementMethod(LinkMovementMethod.getInstance())</p>
*
* @param url 超連結
* @return {@link Builder}
*/
public Builder setUrl(@NonNull String url) {
this.url = url;
return this;
}
/**
* 設定模糊
* <p>尚存bug,其他地方存在相同的字型的話,相同字型出現在之前的話那麼就不會模糊,出現在之後的話那會一起模糊</p>
* <p>推薦還是把所有字型都模糊這樣使用</p>
*
* @param radius 模糊半徑(需大於0)
* @param style 模糊樣式<ul>
* <li>{@link BlurMaskFilter.Blur#NORMAL}</li>
* <li>{@link BlurMaskFilter.Blur#SOLID}</li>
* <li>{@link BlurMaskFilter.Blur#OUTER}</li>
* <li>{@link BlurMaskFilter.Blur#INNER}</li>
* </ul>
* @return {@link Builder}
*/
public Builder setBlur(float radius, BlurMaskFilter.Blur style) {
this.blurRadius = radius;
this.style = style;
this.isBlur = true;
return this;
}
/**
* 追加樣式一行字串
*
* @param text 樣式字串文字
* @return {@link Builder}
*/
public Builder appendLine(@NonNull CharSequence text) {
return append(text + LINE_SEPARATOR);
}
/**
* 追加樣式字串
*
* @param text 樣式字串文字
* @return {@link Builder}
*/
public Builder append(@NonNull CharSequence text) {
setSpan();
this.text = text;
return this;
}
/**
* 建立樣式字串
*
* @return 樣式字串
*/
public SpannableStringBuilder create() {
setSpan();
return mBuilder;
}
/**
* 設定樣式
*/
private void setSpan() {
if (text == null || text.length() == 0) return;
int start = mBuilder.length();
mBuilder.append(this.text);
int end = mBuilder.length();
if (backgroundColor != defaultValue) {
mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
backgroundColor = defaultValue;
}
if (foregroundColor != defaultValue) {
mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
foregroundColor = defaultValue;
}
if (isLeadingMargin) {
mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
isLeadingMargin = false;
}
if (margin != -1) {
mBuilder.setSpan(new MarginSpan(margin), start, end, flag);
margin = -1;
}
if (quoteColor != defaultValue) {
mBuilder.setSpan(new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth), start, end, flag);
quoteColor = defaultValue;
}
if (isBullet) {
mBuilder.setSpan(new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth), start, end, flag);
isBullet = false;
}
if (fontSize != -1) {
mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag);
fontSize = -1;
fontSizeIsDp = false;
}
if (proportion != -1) {
mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
proportion = -1;
}
if (xProportion != -1) {
mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
xProportion = -1;
}
if (isStrikethrough) {
mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
isStrikethrough = false;
}
if (isUnderline) {
mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
isUnderline = false;
}
if (isSuperscript) {
mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
isSuperscript = false;
}
if (isSubscript) {
mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
isSubscript = false;
}
if (isBold) {
mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
isBold = false;
}
if (isItalic) {
mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
isItalic = false;
}
if (isBoldItalic) {
mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
isBoldItalic = false;
}
if (fontFamily != null) {
mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
fontFamily = null;
}
if (typeface != null) {
mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);
typeface = null;
}
if (alignment != null) {
mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);
alignment = null;
}
if (imageIsBitmap || imageIsDrawable || imageIsUri || imageIsResourceId) {
if (imageIsBitmap) {
mBuilder.setSpan(new CustomImageSpan(Utils.getContext(), bitmap, align), start, end, flag);
bitmap = null;
imageIsBitmap = false;
} else if (imageIsDrawable) {
mBuilder.setSpan(new CustomImageSpan(drawable, align), start, end, flag);
drawable = null;
imageIsDrawable = false;
} else if (imageIsUri) {
mBuilder.setSpan(new CustomImageSpan(Utils.getContext(), uri, align), start, end, flag);
uri = null;
imageIsUri = false;
} else {
mBuilder.setSpan(new CustomImageSpan(Utils.getContext(), resourceId, align), start, end, flag);
resourceId = 0;
imageIsResourceId = false;
}
}
if (clickSpan != null) {
mBuilder.setSpan(clickSpan, start, end, flag);
clickSpan = null;
}
if (url != null) {
mBuilder.setSpan(new URLSpan(url), start, end, flag);
url = null;
}
if (isBlur) {
mBuilder.setSpan(new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)), start, end, flag);
isBlur = false;
}
flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
}
}
static class MarginSpan extends ReplacementSpan {
private final int margin;
private MarginSpan(int margin) {
super();
this.margin = margin;
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) {
text = " ";
return margin;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
}
}
static class CustomQuoteSpan implements LeadingMarginSpan {
private final int color;
private final int stripeWidth;
private final int gapWidth;
private CustomQuoteSpan(@ColorInt int color, int stripeWidth, int gapWidth) {
super();
this.color = color;
this.stripeWidth = stripeWidth;
this.gapWidth = gapWidth;
}
public int getLeadingMargin(boolean first) {
return stripeWidth + gapWidth;
}
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom,
CharSequence text, int start, int end,
boolean first, Layout layout) {
Paint.Style style = p.getStyle();
int color = p.getColor();
p.setStyle(Paint.Style.FILL);
p.setColor(this.color);
c.drawRect(x, top, x + dir * stripeWidth, bottom, p);
p.setStyle(style);
p.setColor(color);
}
}
static class CustomBulletSpan implements LeadingMarginSpan {
private final int color;
private final int radius;
private final int gapWidth;
private static Path sBulletPath = null;
private CustomBulletSpan(int color, int radius, int gapWidth) {
this.color = color;
this.radius = radius;
this.gapWidth = gapWidth;
}
public int getLeadingMargin(boolean first) {
return 2 * radius + gapWidth;
}
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom,
CharSequence text, int start, int end,
boolean first, Layout l) {
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style style = p.getStyle();
int oldColor = 0;
oldColor = p.getColor();
p.setColor(color);
p.setStyle(Paint.Style.FILL);
if (c.isHardwareAccelerated()) {
if (sBulletPath == null) {
sBulletPath = new Path();
// Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);
}
c.save();
c.translate(x + dir * radius, (top + bottom) / 2.0f);
c.drawPath(sBulletPath, p);
c.restore();
} else {
c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);
}
p.setColor(oldColor);
p.setStyle(style);
}
}
}
@SuppressLint("ParcelCreator")
static class CustomTypefaceSpan extends TypefaceSpan {
private final Typeface newType;
private CustomTypefaceSpan(Typeface type) {
super("");
newType = type;
}
@Override
public void updateDrawState(TextPaint textPaint) {
apply(textPaint, newType);
}
@Override
public void updateMeasureState(TextPaint paint) {
apply(paint, newType);
}
private static void apply(Paint paint, Typeface tf) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
}
static class CustomImageSpan extends CustomDynamicDrawableSpan {
private Drawable mDrawable;
private Uri mContentUri;
private int mResourceId;
private Context mContext;
private String mSource;
CustomImageSpan(Context context, Bitmap b, int verticalAlignment) {
super(verticalAlignment);
mContext = context;
mDrawable = context != null
? new BitmapDrawable(context.getResources(), b)
: new BitmapDrawable(b);
int width = mDrawable.getIntrinsicWidth();
int height = mDrawable.getIntrinsicHeight();
mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0);
}
CustomImageSpan(Drawable d, int verticalAlignment) {
super(verticalAlignment);
mDrawable = d;
}
CustomImageSpan(Drawable d, String source, int verticalAlignment) {
super(verticalAlignment);
mDrawable = d;
mSource = source;
}
CustomImageSpan(Context context, Uri uri, int verticalAlignment) {
super(verticalAlignment);
mContext = context;
mContentUri = uri;
mSource = uri.toString();
}
CustomImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) {
super(verticalAlignment);
mContext = context;
mResourceId = resourceId;
}
@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(0, 0, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
if (is != null) {
is.close();
}
} catch (Exception e) {
Log.e("sms", "Failed to loaded content " + mContentUri, e);
}
} else {
try {
drawable = ContextCompat.getDrawable(mContext, mResourceId);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
} catch (Exception e) {
Log.e("sms", "Unable to find resource: " + mResourceId);
}
}
return drawable;
}
}
static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {
static final int ALIGN_BOTTOM = 0;
static final int ALIGN_BASELINE = 1;
static final int ALIGN_CENTER = 2;
static final int ALIGN_TOP = 3;
final int mVerticalAlignment;
CustomDynamicDrawableSpan() {
mVerticalAlignment = ALIGN_BOTTOM;
}
CustomDynamicDrawableSpan(int verticalAlignment) {
mVerticalAlignment = verticalAlignment;
}
public abstract Drawable getDrawable();
@Override
public int getSize(@NonNull Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
final int fontHeight = (int) (paint.getFontMetrics().descent - paint.getFontMetrics().ascent);
if (fm != null) { // this is the fucking code which I waste 3 days
if (rect.height() > fontHeight) {
if (mVerticalAlignment == ALIGN_TOP) {
fm.descent += rect.height() - fontHeight;
} else if (mVerticalAlignment == ALIGN_CENTER) {
fm.ascent -= (rect.height() - fontHeight) / 2;
fm.descent += (rect.height() - fontHeight) / 2;
} else if (mVerticalAlignment == ALIGN_BASELINE) {
fm.ascent -= rect.height() - fontHeight + fm.descent;
} else {
fm.ascent -= rect.height() - fontHeight;
}
}
}
return rect.right;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
canvas.save();
final float fontHeight = paint.getFontMetrics().descent - paint.getFontMetrics().ascent;
int transY = bottom - rect.bottom;
if (rect.height() < fontHeight) { // this is the fucking code which I waste 3 days
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
} else if (mVerticalAlignment == ALIGN_CENTER) {
transY -= (fontHeight - rect.height()) / 2;
} else if (mVerticalAlignment == ALIGN_TOP) {
transY -= fontHeight - rect.height();
}
} else {
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
}
}
canvas.translate(x, transY);
d.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<>(d);
}
return d;
}
private WeakReference<Drawable> mDrawableRef;
}
}
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.MaskFilterSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.ReplacementSpan;
import android.text.style.ScaleXSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
public final class SpannableStringUtils {
private SpannableStringUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
public static final int ALIGN_BOTTOM = 0;
public static final int ALIGN_BASELINE = 1;
public static final int ALIGN_CENTER = 2;
public static final int ALIGN_TOP = 3;
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})
@Retention(RetentionPolicy.SOURCE)
public @interface Align {
}
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
public static class Builder {
private int defaultValue = 0x12000000;
private CharSequence text;
private int flag;
@ColorInt
private int foregroundColor;
@ColorInt
private int backgroundColor;
@ColorInt
private int quoteColor;
private int stripeWidth;
private int quoteGapWidth;
private boolean isLeadingMargin;
private int first;
private int rest;
private int margin;
private boolean isBullet;
private int bulletColor;
private int bulletRadius;
private int bulletGapWidth;
private int fontSize;
private boolean fontSizeIsDp;
private float proportion;
private float xProportion;
private boolean isStrikethrough;
private boolean isUnderline;
private boolean isSuperscript;
private boolean isSubscript;
private boolean isBold;
private boolean isItalic;
private boolean isBoldItalic;
private String fontFamily;
private Typeface typeface;
private Alignment alignment;
private boolean imageIsBitmap;
private Bitmap bitmap;
private boolean imageIsDrawable;
private Drawable drawable;
private boolean imageIsUri;
private Uri uri;
private boolean imageIsResourceId;
@DrawableRes
private int resourceId;
@Align
int align;
private ClickableSpan clickSpan;
private String url;
private boolean isBlur;
private float blurRadius;
private BlurMaskFilter.Blur style;
private SpannableStringBuilder mBuilder;
public Builder() {
flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
foregroundColor = defaultValue;
backgroundColor = defaultValue;
quoteColor = defaultValue;
margin = -1;
fontSize = -1;
proportion = -1;
xProportion = -1;
align = ALIGN_BOTTOM;
mBuilder = new SpannableStringBuilder();
}
/**
* 設定標識
*
* @param flag <ul>
* <li>{@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}</li>
* <li>{@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}</li>
* <li>{@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}</li>
* <li>{@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}</li>
* </ul>
* @return {@link Builder}
*/
public Builder setFlag(int flag) {
this.flag = flag;
return this;
}
/**
* 設定前景色
*
* @param color 前景色
* @return {@link Builder}
*/
public Builder setForegroundColor(@ColorInt int color) {
this.foregroundColor = color;
return this;
}
/**
* 設定背景色
*
* @param color 背景色
* @return {@link Builder}
*/
public Builder setBackgroundColor(@ColorInt int color) {
this.backgroundColor = color;
return this;
}
/**
* 設定引用線的顏色
*
* @param color 引用線的顏色
* @return {@link Builder}
*/
public Builder setQuoteColor(@ColorInt int color) {
this.quoteColor = color;
this.stripeWidth = 2;
this.quoteGapWidth = 2;
return this;
}
/**
* 設定引用線的顏色
*
* @param color 引用線的顏色
* @param stripeWidth 引用線線寬
* @param quoteGapWidth 引用線和文字間距
* @return {@link Builder}
*/
public Builder setQuoteColor(@ColorInt int color, int stripeWidth, int quoteGapWidth) {
this.quoteColor = color;
this.stripeWidth = stripeWidth;
this.quoteGapWidth = quoteGapWidth;
return this;
}
/**
* 設定縮排
*
* @param first 首行縮排
* @param rest 剩餘行縮排
* @return {@link Builder}
*/
public Builder setLeadingMargin(int first, int rest) {
this.first = first;
this.rest = rest;
isLeadingMargin = true;
return this;
}
/**
* 設定間距
*
* @param margin 間距
* @return {@link Builder}
*/
public Builder setMargin(int margin) {
this.margin = margin;
this.text = " " + this.text;
return this;
}
/**
* 設定列表標記
*
* @param gapWidth 列表標記和文字間距離
* @return {@link Builder}
*/
public Builder setBullet(@ColorInt int gapWidth) {
this.bulletColor = 0;
this.bulletRadius = 3;
this.bulletGapWidth = gapWidth;
isBullet = true;
return this;
}
/**
* 設定列表標記
*
* @param color 列表標記的顏色
* @param radius 列表標記顏色
* @param gapWidth 列表標記和文字間距離
* @return {@link Builder}
*/
public Builder setBullet(@ColorInt int color, int radius, int gapWidth) {
this.bulletColor = color;
this.bulletRadius = radius;
this.bulletGapWidth = gapWidth;
isBullet = true;
return this;
}
/**
* 設定字型尺寸
*
* @param size 尺寸
* @return {@link Builder}
*/
public Builder setFontSize(int size) {
this.fontSize = size;
this.fontSizeIsDp = false;
return this;
}
/**
* 設定字型尺寸
*
* @param size 尺寸
* @param isDp 是否使用dip
* @return {@link Builder}
*/
public Builder setFontSize(int size, boolean isDp) {
this.fontSize = size;
this.fontSizeIsDp = isDp;
return this;
}
/**
* 設定字型比例
*
* @param proportion 比例
* @return {@link Builder}
*/
public Builder setProportion(float proportion) {
this.proportion = proportion;
return this;
}
/**
* 設定字型橫向比例
*
* @param proportion 比例
* @return {@link Builder}
*/
public Builder setXProportion(float proportion) {
this.xProportion = proportion;
return this;
}
/**
* 設定刪除線
*
* @return {@link Builder}
*/
public Builder setStrikethrough() {
this.isStrikethrough = true;
return this;
}
/**
* 設定下劃線
*
* @return {@link Builder}
*/
public Builder setUnderline() {
this.isUnderline = true;
return this;
}
/**
* 設定上標
*
* @return {@link Builder}
*/
public Builder setSuperscript() {
this.isSuperscript = true;
return this;
}
/**
* 設定下標
*
* @return {@link Builder}
*/
public Builder setSubscript() {
this.isSubscript = true;
return this;
}
/**
* 設定粗體
*
* @return {@link Builder}
*/
public Builder setBold() {
isBold = true;
return this;
}
/**
* 設定斜體
*
* @return {@link Builder}
*/
public Builder setItalic() {
isItalic = true;
return this;
}
/**
* 設定粗斜體
*
* @return {@link Builder}
*/
public Builder setBoldItalic() {
isBoldItalic = true;
return this;
}
/**
* 設定字體系列
*
* @param fontFamily 字體系列
* <ul>
* <li>monospace</li>
* <li>serif</li>
* <li>sans-serif</li>
* </ul>
* @return {@link Builder}
*/
public Builder setFontFamily(@NonNull String fontFamily) {
this.fontFamily = fontFamily;
return this;
}
/**
* 設定字型
*
* @param typeface 字型
* @return {@link Builder}
*/
public Builder setTypeface(@NonNull Typeface typeface) {
this.typeface = typeface;
return this;
}
/**
* 設定對齊
*
* @param alignment 對其方式
* <ul>
* <li>{@link Alignment#ALIGN_NORMAL}正常</li>
* <li>{@link Alignment#ALIGN_OPPOSITE}相反</li>
* <li>{@link Alignment#ALIGN_CENTER}居中</li>
* </ul>
* @return {@link Builder}
*/
public Builder setAlign(@NonNull Alignment alignment) {
this.alignment = alignment;
return this;
}
/**
* 設定圖片
*
* @param bitmap 圖片點陣圖
* @return {@link Builder}
*/
public Builder setBitmap(@NonNull Bitmap bitmap) {
return setBitmap(bitmap, align);
}
/**
* 設定圖片
*
* @param bitmap 圖片點陣圖
* @param align 對齊
* <ul>
* <li>{@link Align#ALIGN_TOP}頂部對齊</li>
* <li>{@link Align#ALIGN_CENTER}居中對齊</li>
* <li>{@link Align#ALIGN_BASELINE}基線對齊</li>
* <li>{@link Align#ALIGN_BOTTOM}底部對齊</li>
* </ul>
* @return {@link Builder}
*/
public Builder setBitmap(@NonNull Bitmap bitmap, @Align int align) {
this.bitmap = bitmap;
this.align = align;
this.text = " " + this.text;
imageIsBitmap = true;
return this;
}
/**
* 設定圖片
*
* @param drawable 圖片資源
* @return {@link Builder}
*/
public Builder setDrawable(@NonNull Drawable drawable) {
return setDrawable(drawable, align);
}
/**
* 設定圖片
*
* @param drawable 圖片資源
* @param align 對齊
* <ul>
* <li>{@link Align#ALIGN_TOP}頂部對齊</li>
* <li>{@link Align#ALIGN_CENTER}居中對齊</li>
* <li>{@link Align#ALIGN_BASELINE}基線對齊</li>
* <li>{@link Align#ALIGN_BOTTOM}底部對齊</li>
* </ul>
* @return {@link Builder}
*/
public Builder setDrawable(@NonNull Drawable drawable, @Align int align) {
this.drawable = drawable;
this.align = align;
this.text = " " + this.text;
imageIsDrawable = true;
return this;
}
/**
* 設定圖片
*
* @param uri 圖片uri
* @return {@link Builder}
*/
public Builder setUri(@NonNull Uri uri) {
setUri(uri, ALIGN_BOTTOM);
return this;
}
/**
* 設定圖片
*
* @param uri 圖片uri
* @param align 對齊
* <ul>
* <li>{@link Align#ALIGN_TOP}頂部對齊</li>
* <li>{@link Align#ALIGN_CENTER}居中對齊</li>
* <li>{@link Align#ALIGN_BASELINE}基線對齊</li>
* <li>{@link Align#ALIGN_BOTTOM}底部對齊</li>
* </ul>
* @return {@link Builder}
*/
public Builder setUri(@NonNull Uri uri, @Align int align) {
this.uri = uri;
this.align = align;
this.text = " " + this.text;
imageIsUri = true;
return this;
}
/**
* 設定圖片
*
* @param resourceId 圖片資源id
* @return {@link Builder}
*/
public Builder setResourceId(@DrawableRes int resourceId) {
return setResourceId(resourceId, align);
}
/**
* 設定圖片
*
* @param resourceId 圖片資源id
* @param align 對齊
* @return {@link Builder}
*/
public Builder setResourceId(@DrawableRes int resourceId, @Align int align) {
this.resourceId = resourceId;
this.align = align;
this.text = " " + this.text;
imageIsResourceId = true;
return this;
}
/**
* 設定點選事件
* <p>需新增view.setMovementMethod(LinkMovementMethod.getInstance())</p>
*
* @param clickSpan 點選事件
* @return {@link Builder}
*/
public Builder setClickSpan(@NonNull ClickableSpan clickSpan) {
this.clickSpan = clickSpan;
return this;
}
/**
* 設定超連結
* <p>需新增view.setMovementMethod(LinkMovementMethod.getInstance())</p>
*
* @param url 超連結
* @return {@link Builder}
*/
public Builder setUrl(@NonNull String url) {
this.url = url;
return this;
}
/**
* 設定模糊
* <p>尚存bug,其他地方存在相同的字型的話,相同字型出現在之前的話那麼就不會模糊,出現在之後的話那會一起模糊</p>
* <p>推薦還是把所有字型都模糊這樣使用</p>
*
* @param radius 模糊半徑(需大於0)
* @param style 模糊樣式<ul>
* <li>{@link BlurMaskFilter.Blur#NORMAL}</li>
* <li>{@link BlurMaskFilter.Blur#SOLID}</li>
* <li>{@link BlurMaskFilter.Blur#OUTER}</li>
* <li>{@link BlurMaskFilter.Blur#INNER}</li>
* </ul>
* @return {@link Builder}
*/
public Builder setBlur(float radius, BlurMaskFilter.Blur style) {
this.blurRadius = radius;
this.style = style;
this.isBlur = true;
return this;
}
/**
* 追加樣式一行字串
*
* @param text 樣式字串文字
* @return {@link Builder}
*/
public Builder appendLine(@NonNull CharSequence text) {
return append(text + LINE_SEPARATOR);
}
/**
* 追加樣式字串
*
* @param text 樣式字串文字
* @return {@link Builder}
*/
public Builder append(@NonNull CharSequence text) {
setSpan();
this.text = text;
return this;
}
/**
* 建立樣式字串
*
* @return 樣式字串
*/
public SpannableStringBuilder create() {
setSpan();
return mBuilder;
}
/**
* 設定樣式
*/
private void setSpan() {
if (text == null || text.length() == 0) return;
int start = mBuilder.length();
mBuilder.append(this.text);
int end = mBuilder.length();
if (backgroundColor != defaultValue) {
mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
backgroundColor = defaultValue;
}
if (foregroundColor != defaultValue) {
mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
foregroundColor = defaultValue;
}
if (isLeadingMargin) {
mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
isLeadingMargin = false;
}
if (margin != -1) {
mBuilder.setSpan(new MarginSpan(margin), start, end, flag);
margin = -1;
}
if (quoteColor != defaultValue) {
mBuilder.setSpan(new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth), start, end, flag);
quoteColor = defaultValue;
}
if (isBullet) {
mBuilder.setSpan(new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth), start, end, flag);
isBullet = false;
}
if (fontSize != -1) {
mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag);
fontSize = -1;
fontSizeIsDp = false;
}
if (proportion != -1) {
mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
proportion = -1;
}
if (xProportion != -1) {
mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
xProportion = -1;
}
if (isStrikethrough) {
mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
isStrikethrough = false;
}
if (isUnderline) {
mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
isUnderline = false;
}
if (isSuperscript) {
mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
isSuperscript = false;
}
if (isSubscript) {
mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
isSubscript = false;
}
if (isBold) {
mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
isBold = false;
}
if (isItalic) {
mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
isItalic = false;
}
if (isBoldItalic) {
mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
isBoldItalic = false;
}
if (fontFamily != null) {
mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
fontFamily = null;
}
if (typeface != null) {
mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);
typeface = null;
}
if (alignment != null) {
mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);
alignment = null;
}
if (imageIsBitmap || imageIsDrawable || imageIsUri || imageIsResourceId) {
if (imageIsBitmap) {
mBuilder.setSpan(new CustomImageSpan(Utils.getContext(), bitmap, align), start, end, flag);
bitmap = null;
imageIsBitmap = false;
} else if (imageIsDrawable) {
mBuilder.setSpan(new CustomImageSpan(drawable, align), start, end, flag);
drawable = null;
imageIsDrawable = false;
} else if (imageIsUri) {
mBuilder.setSpan(new CustomImageSpan(Utils.getContext(), uri, align), start, end, flag);
uri = null;
imageIsUri = false;
} else {
mBuilder.setSpan(new CustomImageSpan(Utils.getContext(), resourceId, align), start, end, flag);
resourceId = 0;
imageIsResourceId = false;
}
}
if (clickSpan != null) {
mBuilder.setSpan(clickSpan, start, end, flag);
clickSpan = null;
}
if (url != null) {
mBuilder.setSpan(new URLSpan(url), start, end, flag);
url = null;
}
if (isBlur) {
mBuilder.setSpan(new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)), start, end, flag);
isBlur = false;
}
flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
}
}
static class MarginSpan extends ReplacementSpan {
private final int margin;
private MarginSpan(int margin) {
super();
this.margin = margin;
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) {
text = " ";
return margin;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
}
}
static class CustomQuoteSpan implements LeadingMarginSpan {
private final int color;
private final int stripeWidth;
private final int gapWidth;
private CustomQuoteSpan(@ColorInt int color, int stripeWidth, int gapWidth) {
super();
this.color = color;
this.stripeWidth = stripeWidth;
this.gapWidth = gapWidth;
}
public int getLeadingMargin(boolean first) {
return stripeWidth + gapWidth;
}
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom,
CharSequence text, int start, int end,
boolean first, Layout layout) {
Paint.Style style = p.getStyle();
int color = p.getColor();
p.setStyle(Paint.Style.FILL);
p.setColor(this.color);
c.drawRect(x, top, x + dir * stripeWidth, bottom, p);
p.setStyle(style);
p.setColor(color);
}
}
static class CustomBulletSpan implements LeadingMarginSpan {
private final int color;
private final int radius;
private final int gapWidth;
private static Path sBulletPath = null;
private CustomBulletSpan(int color, int radius, int gapWidth) {
this.color = color;
this.radius = radius;
this.gapWidth = gapWidth;
}
public int getLeadingMargin(boolean first) {
return 2 * radius + gapWidth;
}
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom,
CharSequence text, int start, int end,
boolean first, Layout l) {
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style style = p.getStyle();
int oldColor = 0;
oldColor = p.getColor();
p.setColor(color);
p.setStyle(Paint.Style.FILL);
if (c.isHardwareAccelerated()) {
if (sBulletPath == null) {
sBulletPath = new Path();
// Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);
}
c.save();
c.translate(x + dir * radius, (top + bottom) / 2.0f);
c.drawPath(sBulletPath, p);
c.restore();
} else {
c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);
}
p.setColor(oldColor);
p.setStyle(style);
}
}
}
@SuppressLint("ParcelCreator")
static class CustomTypefaceSpan extends TypefaceSpan {
private final Typeface newType;
private CustomTypefaceSpan(Typeface type) {
super("");
newType = type;
}
@Override
public void updateDrawState(TextPaint textPaint) {
apply(textPaint, newType);
}
@Override
public void updateMeasureState(TextPaint paint) {
apply(paint, newType);
}
private static void apply(Paint paint, Typeface tf) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
}
static class CustomImageSpan extends CustomDynamicDrawableSpan {
private Drawable mDrawable;
private Uri mContentUri;
private int mResourceId;
private Context mContext;
private String mSource;
CustomImageSpan(Context context, Bitmap b, int verticalAlignment) {
super(verticalAlignment);
mContext = context;
mDrawable = context != null
? new BitmapDrawable(context.getResources(), b)
: new BitmapDrawable(b);
int width = mDrawable.getIntrinsicWidth();
int height = mDrawable.getIntrinsicHeight();
mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0);
}
CustomImageSpan(Drawable d, int verticalAlignment) {
super(verticalAlignment);
mDrawable = d;
}
CustomImageSpan(Drawable d, String source, int verticalAlignment) {
super(verticalAlignment);
mDrawable = d;
mSource = source;
}
CustomImageSpan(Context context, Uri uri, int verticalAlignment) {
super(verticalAlignment);
mContext = context;
mContentUri = uri;
mSource = uri.toString();
}
CustomImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) {
super(verticalAlignment);
mContext = context;
mResourceId = resourceId;
}
@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(0, 0, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
if (is != null) {
is.close();
}
} catch (Exception e) {
Log.e("sms", "Failed to loaded content " + mContentUri, e);
}
} else {
try {
drawable = ContextCompat.getDrawable(mContext, mResourceId);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
} catch (Exception e) {
Log.e("sms", "Unable to find resource: " + mResourceId);
}
}
return drawable;
}
}
static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {
static final int ALIGN_BOTTOM = 0;
static final int ALIGN_BASELINE = 1;
static final int ALIGN_CENTER = 2;
static final int ALIGN_TOP = 3;
final int mVerticalAlignment;
CustomDynamicDrawableSpan() {
mVerticalAlignment = ALIGN_BOTTOM;
}
CustomDynamicDrawableSpan(int verticalAlignment) {
mVerticalAlignment = verticalAlignment;
}
public abstract Drawable getDrawable();
@Override
public int getSize(@NonNull Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
final int fontHeight = (int) (paint.getFontMetrics().descent - paint.getFontMetrics().ascent);
if (fm != null) { // this is the fucking code which I waste 3 days
if (rect.height() > fontHeight) {
if (mVerticalAlignment == ALIGN_TOP) {
fm.descent += rect.height() - fontHeight;
} else if (mVerticalAlignment == ALIGN_CENTER) {
fm.ascent -= (rect.height() - fontHeight) / 2;
fm.descent += (rect.height() - fontHeight) / 2;
} else if (mVerticalAlignment == ALIGN_BASELINE) {
fm.ascent -= rect.height() - fontHeight + fm.descent;
} else {
fm.ascent -= rect.height() - fontHeight;
}
}
}
return rect.right;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
canvas.save();
final float fontHeight = paint.getFontMetrics().descent - paint.getFontMetrics().ascent;
int transY = bottom - rect.bottom;
if (rect.height() < fontHeight) { // this is the fucking code which I waste 3 days
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
} else if (mVerticalAlignment == ALIGN_CENTER) {
transY -= (fontHeight - rect.height()) / 2;
} else if (mVerticalAlignment == ALIGN_TOP) {
transY -= fontHeight - rect.height();
}
} else {
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
}
}
canvas.translate(x, transY);
d.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<>(d);
}
return d;
}
private WeakReference<Drawable> mDrawableRef;
}
}