Loading core/java/android/text/style/DrawableMarginSpan.java +62 −22 Original line number Diff line number Diff line Loading @@ -16,32 +16,72 @@ package android.text.style; import android.annotation.NonNull; import android.annotation.Px; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.Spanned; public class DrawableMarginSpan implements LeadingMarginSpan, LineHeightSpan { public DrawableMarginSpan(Drawable b) { mDrawable = b; /** * A span which adds a drawable and a padding to the paragraph it's attached to. * <p> * If the height of the drawable is bigger than the height of the line it's attached to then the * line height is increased to fit the drawable. <code>DrawableMarginSpan</code> allows setting a * padding between the drawable and the text. The default value is 0. The span must be set from the * beginning of the text, otherwise either the span won't be rendered or it will be rendered * incorrectly. * <p> * For example, a drawable and a padding of 20px can be added like this: * <pre>{@code SpannableString string = new SpannableString("Text with a drawable."); * string.setSpan(new DrawableMarginSpan(drawable, 20), 0, string.length(), * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> * <img src="{@docRoot}reference/android/images/text/style/drawablemarginspan.png" /> * <figcaption>Text with a drawable and a padding.</figcaption> * <p> * * @see IconMarginSpan for working with a {@link android.graphics.Bitmap} instead of * a {@link Drawable}. */ public class DrawableMarginSpan implements LeadingMarginSpan, LineHeightSpan { private static final int STANDARD_PAD_WIDTH = 0; @NonNull private final Drawable mDrawable; @Px private final int mPad; /** * Creates a {@link DrawableMarginSpan} from a {@link Drawable}. The pad width will be 0. * * @param drawable the drawable to be added */ public DrawableMarginSpan(@NonNull Drawable drawable) { this(drawable, STANDARD_PAD_WIDTH); } public DrawableMarginSpan(Drawable b, int pad) { mDrawable = b; /** * Creates a {@link DrawableMarginSpan} from a {@link Drawable} and a padding, in pixels. * * @param drawable the drawable to be added * @param pad the distance between the drawable and the text */ public DrawableMarginSpan(@NonNull Drawable drawable, int pad) { mDrawable = drawable; mPad = pad; } @Override public int getLeadingMargin(boolean first) { return mDrawable.getIntrinsicWidth() + mPad; } public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, @Override public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { @NonNull CharSequence text, int start, int end, boolean first, @NonNull Layout layout) { int st = ((Spanned) text).getSpanStart(this); int ix = (int) x; int itop = (int) layout.getLineTop(layout.getLineForOffset(st)); Loading @@ -54,22 +94,22 @@ implements LeadingMarginSpan, LineHeightSpan mDrawable.draw(c); } public void chooseHeight(CharSequence text, int start, int end, @Override public void chooseHeight(@NonNull CharSequence text, int start, int end, int istartv, int v, Paint.FontMetricsInt fm) { @NonNull Paint.FontMetricsInt fm) { if (end == ((Spanned) text).getSpanEnd(this)) { int ht = mDrawable.getIntrinsicHeight(); int need = ht - (v + fm.descent - fm.ascent - istartv); if (need > 0) if (need > 0) { fm.descent += need; } need = ht - (v + fm.bottom - fm.top - istartv); if (need > 0) if (need > 0) { fm.bottom += need; } } private Drawable mDrawable; private int mPad; } } core/java/android/text/style/DynamicDrawableSpan.java +60 −19 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.text.style; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; Loading @@ -24,10 +27,41 @@ import android.graphics.drawable.Drawable; import java.lang.ref.WeakReference; /** * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with * the bottom or with the baseline of the surrounding text. * <p> * For an implementation that constructs the drawable from various sources (<code>Bitmap</code>, * <code>Drawable</code>, resource id or <code>Uri</code>) use {@link ImageSpan}. * <p> * A simple implementation of <code>DynamicDrawableSpan</code> that uses drawables from resources * looks like this: * <pre> * class MyDynamicDrawableSpan extends DynamicDrawableSpan { * * private final Context mContext; * private final int mResourceId; * * public MyDynamicDrawableSpan(Context context, @DrawableRes int resourceId) { * mContext = context; * mResourceId = resourceId; * } * * {@literal @}Override * public Drawable getDrawable() { * Drawable drawable = mContext.getDrawable(mResourceId); * drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); * return drawable; * } * }</pre> * The class can be used like this: * <pre> * SpannableString string = new SpannableString("Text with a drawable span"); * string.setSpan(new MyDynamicDrawableSpan(context, R.mipmap.ic_launcher), 12, 20, Spanned * .SPAN_EXCLUSIVE_EXCLUSIVE);</pre> * <img src="{@docRoot}reference/android/images/text/style/dynamicdrawablespan.png" /> * <figcaption>Replacing text with a drawable.</figcaption> */ public abstract class DynamicDrawableSpan extends ReplacementSpan { private static final String TAG = "DynamicDrawableSpan"; /** * A constant indicating that the bottom of this span should be aligned Loading @@ -44,12 +78,20 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { protected final int mVerticalAlignment; private WeakReference<Drawable> mDrawableRef; /** * Creates a {@link DynamicDrawableSpan}. The default vertical alignment is * {@link #ALIGN_BOTTOM} */ public DynamicDrawableSpan() { mVerticalAlignment = ALIGN_BOTTOM; } /** * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE}. * Creates a {@link DynamicDrawableSpan} based on a vertical alignment.\ * * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE} */ protected DynamicDrawableSpan(int verticalAlignment) { mVerticalAlignment = verticalAlignment; Loading @@ -71,9 +113,9 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { public abstract Drawable getDrawable(); @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { public int getSize(@NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) { Drawable d = getCachedDrawable(); Rect rect = d.getBounds(); Loading @@ -89,9 +131,9 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 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) { Drawable b = getCachedDrawable(); canvas.save(); Loading @@ -109,8 +151,9 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { WeakReference<Drawable> wr = mDrawableRef; Drawable d = null; if (wr != null) if (wr != null) { d = wr.get(); } if (d == null) { d = getDrawable(); Loading @@ -119,7 +162,5 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { return d; } private WeakReference<Drawable> mDrawableRef; } core/java/android/text/style/IconMarginSpan.java +58 −17 Original line number Diff line number Diff line Loading @@ -16,28 +16,67 @@ package android.text.style; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Px; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.text.Layout; import android.text.Spanned; public class IconMarginSpan implements LeadingMarginSpan, LineHeightSpan { public IconMarginSpan(Bitmap b) { mBitmap = b; /** * Paragraph affecting span, that draws a bitmap at the beginning of a text. The span also allows * setting a padding between the bitmap and the text. The default value of the padding is 0px. The * span should be attached from the first character of the text. * <p> * For example, an <code>IconMarginSpan</code> with a bitmap and a padding of 30px can be set * like this: * <pre> * SpannableString string = new SpannableString("Text with icon and padding"); * string.setSpan(new IconMarginSpan(bitmap, 30), 0, string.length(), * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); * </pre> * <img src="{@docRoot}reference/android/images/text/style/iconmarginspan.png" /> * <figcaption>Text with <code>IconMarginSpan</code></figcaption> * <p> * * @see DrawableMarginSpan for working with a {@link android.graphics.drawable.Drawable} instead of * a {@link Bitmap}. */ public class IconMarginSpan implements LeadingMarginSpan, LineHeightSpan { @NonNull private final Bitmap mBitmap; @Px private final int mPad; /** * Creates an {@link IconMarginSpan} from a {@link Bitmap}. * * @param bitmap bitmap to be rendered at the beginning of the text */ public IconMarginSpan(@NonNull Bitmap bitmap) { this(bitmap, 0); } public IconMarginSpan(Bitmap b, int pad) { mBitmap = b; /** * Creates an {@link IconMarginSpan} from a {@link Bitmap}. * * @param bitmap bitmap to be rendered at the beginning of the text * @param pad padding width, in pixels, between the bitmap and the text */ public IconMarginSpan(@NonNull Bitmap bitmap, @IntRange(from = 0) int pad) { mBitmap = bitmap; mPad = pad; } @Override public int getLeadingMargin(boolean first) { return mBitmap.getWidth() + mPad; } @Override public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, Loading @@ -45,12 +84,14 @@ implements LeadingMarginSpan, LineHeightSpan int st = ((Spanned) text).getSpanStart(this); int itop = layout.getLineTop(layout.getLineForOffset(st)); if (dir < 0) if (dir < 0) { x -= mBitmap.getWidth(); } c.drawBitmap(mBitmap, x, itop, p); } @Override public void chooseHeight(CharSequence text, int start, int end, int istartv, int v, Paint.FontMetricsInt fm) { Loading @@ -58,15 +99,15 @@ implements LeadingMarginSpan, LineHeightSpan int ht = mBitmap.getHeight(); int need = ht - (v + fm.descent - fm.ascent - istartv); if (need > 0) if (need > 0) { fm.descent += need; } need = ht - (v + fm.bottom - fm.top - istartv); if (need > 0) if (need > 0) { fm.bottom += need; } } } private Bitmap mBitmap; private int mPad; } core/java/android/text/style/ImageSpan.java +130 −31 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package android.text.style; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; Loading @@ -27,18 +29,49 @@ import android.util.Log; import java.io.InputStream; /** * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with * the bottom or with the baseline of the surrounding text. The drawable can be constructed from * varied sources: * <ul> * <li>{@link Bitmap} - see {@link #ImageSpan(Context, Bitmap)} and * {@link #ImageSpan(Context, Bitmap, int)} * </li> * <li>{@link Drawable} - see {@link #ImageSpan(Drawable, int)}</li> * <li>resource id - see {@link #ImageSpan(Context, int, int)}</li> * <li>{@link Uri} - see {@link #ImageSpan(Context, Uri, int)}</li> * </ul> * The default value for the vertical alignment is {@link DynamicDrawableSpan#ALIGN_BOTTOM} * <p> * For example, an <code>ImagedSpan</code> can be used like this: * <pre> * SpannableString string = SpannableString("Bottom: span.\nBaseline: span."); * // using the default alignment: ALIGN_BOTTOM * string.setSpan(ImageSpan(this, R.mipmap.ic_launcher), 7, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); * string.setSpan(ImageSpan(this, R.mipmap.ic_launcher, DynamicDrawableSpan.ALIGN_BASELINE), * 22, 23, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); * </pre> * <img src="{@docRoot}reference/android/images/text/style/imagespan.png" /> * <figcaption>Text with <code>ImageSpan</code>s aligned bottom and baseline.</figcaption> */ public class ImageSpan extends DynamicDrawableSpan { @Nullable private Drawable mDrawable; @Nullable private Uri mContentUri; @DrawableRes private int mResourceId; @Nullable private Context mContext; @Nullable private String mSource; /** * @deprecated Use {@link #ImageSpan(Context, Bitmap)} instead. */ @Deprecated public ImageSpan(Bitmap b) { public ImageSpan(@NonNull Bitmap b) { this(null, b, ALIGN_BOTTOM); } Loading @@ -46,80 +79,143 @@ public class ImageSpan extends DynamicDrawableSpan { * @deprecated Use {@link #ImageSpan(Context, Bitmap, int)} instead. */ @Deprecated public ImageSpan(Bitmap b, int verticalAlignment) { public ImageSpan(@NonNull Bitmap b, int verticalAlignment) { this(null, b, verticalAlignment); } public ImageSpan(Context context, Bitmap b) { this(context, b, ALIGN_BOTTOM); /** * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Bitmap} with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} * * @param context context used to create a drawable from {@param bitmap} based on the display * metrics of the resources * @param bitmap bitmap to be rendered */ public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap) { this(context, bitmap, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Bitmap} and a vertical * alignment. * * @param context context used to create a drawable from {@param bitmap} based on * the display metrics of the resources * @param bitmap bitmap to be rendered * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Context context, Bitmap b, int verticalAlignment) { public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap, int verticalAlignment) { super(verticalAlignment); mContext = context; mDrawable = context != null ? new BitmapDrawable(context.getResources(), b) : new BitmapDrawable(b); ? new BitmapDrawable(context.getResources(), bitmap) : new BitmapDrawable(bitmap); int width = mDrawable.getIntrinsicWidth(); int height = mDrawable.getIntrinsicHeight(); mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0); } public ImageSpan(Drawable d) { this(d, ALIGN_BOTTOM); /** * Constructs an {@link ImageSpan} from a drawable with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. * * @param drawable drawable to be rendered */ public ImageSpan(@NonNull Drawable drawable) { this(drawable, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a drawable and a vertical alignment. * * @param drawable drawable to be rendered * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Drawable d, int verticalAlignment) { public ImageSpan(@NonNull Drawable drawable, int verticalAlignment) { super(verticalAlignment); mDrawable = d; mDrawable = drawable; } public ImageSpan(Drawable d, String source) { this(d, source, ALIGN_BOTTOM); /** * Constructs an {@link ImageSpan} from a drawable and a source with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} * * @param drawable drawable to be rendered * @param source drawable's Uri source */ public ImageSpan(@NonNull Drawable drawable, @NonNull String source) { this(drawable, source, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a drawable, a source and a vertical alignment. * * @param drawable drawable to be rendered * @param source drawable's uri source * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Drawable d, String source, int verticalAlignment) { public ImageSpan(@NonNull Drawable drawable, @NonNull String source, int verticalAlignment) { super(verticalAlignment); mDrawable = d; mDrawable = drawable; mSource = source; } public ImageSpan(Context context, Uri uri) { /** * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Uri} with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. The Uri source can be retrieved via * {@link #getSource()} * * @param context context used to create a drawable from {@param bitmap} based on the display * metrics of the resources * @param uri {@link Uri} used to construct the drawable that will be rendered */ public ImageSpan(@NonNull Context context, @NonNull Uri uri) { this(context, uri, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Uri} and a vertical * alignment. The Uri source can be retrieved via {@link #getSource()} * * @param context context used to create a drawable from {@param bitmap} based on * the display * metrics of the resources * @param uri {@link Uri} used to construct the drawable that will be rendered. * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Context context, Uri uri, int verticalAlignment) { public ImageSpan(@NonNull Context context, @NonNull Uri uri, int verticalAlignment) { super(verticalAlignment); mContext = context; mContentUri = uri; mSource = uri.toString(); } public ImageSpan(Context context, @DrawableRes int resourceId) { /** * Constructs an {@link ImageSpan} from a {@link Context} and a resource id with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} * * @param context context used to retrieve the drawable from resources * @param resourceId drawable resource id based on which the drawable is retrieved */ public ImageSpan(@NonNull Context context, @DrawableRes int resourceId) { this(context, resourceId, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a {@link Context}, a resource id and a vertical * alignment. * * @param context context used to retrieve the drawable from resources * @param resourceId drawable resource id based on which the drawable is retrieved. * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) { public ImageSpan(@NonNull Context context, @DrawableRes int resourceId, int verticalAlignment) { super(verticalAlignment); mContext = context; mResourceId = resourceId; Loading @@ -142,7 +238,7 @@ public class ImageSpan extends DynamicDrawableSpan { drawable.getIntrinsicHeight()); is.close(); } catch (Exception e) { Log.e("sms", "Failed to loaded content " + mContentUri, e); Log.e("ImageSpan", "Failed to loaded content " + mContentUri, e); } } else { try { Loading @@ -150,7 +246,7 @@ public class ImageSpan extends DynamicDrawableSpan { drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } catch (Exception e) { Log.e("sms", "Unable to find resource: " + mResourceId); Log.e("ImageSpan", "Unable to find resource: " + mResourceId); } } Loading @@ -159,9 +255,12 @@ public class ImageSpan extends DynamicDrawableSpan { /** * Returns the source string that was saved during construction. * * @return the source string that was saved during construction * @see #ImageSpan(Drawable, String) and this{@link #ImageSpan(Context, Uri)} */ @Nullable public String getSource() { return mSource; } } core/java/android/text/style/LineHeightSpan.java +33 −7 Original line number Diff line number Diff line Loading @@ -19,16 +19,42 @@ package android.text.style; import android.graphics.Paint; import android.text.TextPaint; public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { /** * The classes that affect the height of the line should implement this interface. */ public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { /** * Classes that implement this should define how the height is being calculated. * * @param text the text * @param start the start of the line * @param end the end of the line * @param spanstartv the start of the span * @param lineHeight the line height * @param fm font metrics of the paint, in integers */ public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, int spanstartv, int lineHeight, Paint.FontMetricsInt fm); /** * The classes that affect the height of the line with respect to density, should implement this * interface. */ public interface WithDensity extends LineHeightSpan { /** * Classes that implement this should define how the height is being calculated. * * @param text the text * @param start the start of the line * @param end the end of the line * @param spanstartv the start of the span * @param lineHeight the line height * @param paint the paint */ public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, int spanstartv, int lineHeight, Paint.FontMetricsInt fm, TextPaint paint); } } Loading
core/java/android/text/style/DrawableMarginSpan.java +62 −22 Original line number Diff line number Diff line Loading @@ -16,32 +16,72 @@ package android.text.style; import android.annotation.NonNull; import android.annotation.Px; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.Spanned; public class DrawableMarginSpan implements LeadingMarginSpan, LineHeightSpan { public DrawableMarginSpan(Drawable b) { mDrawable = b; /** * A span which adds a drawable and a padding to the paragraph it's attached to. * <p> * If the height of the drawable is bigger than the height of the line it's attached to then the * line height is increased to fit the drawable. <code>DrawableMarginSpan</code> allows setting a * padding between the drawable and the text. The default value is 0. The span must be set from the * beginning of the text, otherwise either the span won't be rendered or it will be rendered * incorrectly. * <p> * For example, a drawable and a padding of 20px can be added like this: * <pre>{@code SpannableString string = new SpannableString("Text with a drawable."); * string.setSpan(new DrawableMarginSpan(drawable, 20), 0, string.length(), * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> * <img src="{@docRoot}reference/android/images/text/style/drawablemarginspan.png" /> * <figcaption>Text with a drawable and a padding.</figcaption> * <p> * * @see IconMarginSpan for working with a {@link android.graphics.Bitmap} instead of * a {@link Drawable}. */ public class DrawableMarginSpan implements LeadingMarginSpan, LineHeightSpan { private static final int STANDARD_PAD_WIDTH = 0; @NonNull private final Drawable mDrawable; @Px private final int mPad; /** * Creates a {@link DrawableMarginSpan} from a {@link Drawable}. The pad width will be 0. * * @param drawable the drawable to be added */ public DrawableMarginSpan(@NonNull Drawable drawable) { this(drawable, STANDARD_PAD_WIDTH); } public DrawableMarginSpan(Drawable b, int pad) { mDrawable = b; /** * Creates a {@link DrawableMarginSpan} from a {@link Drawable} and a padding, in pixels. * * @param drawable the drawable to be added * @param pad the distance between the drawable and the text */ public DrawableMarginSpan(@NonNull Drawable drawable, int pad) { mDrawable = drawable; mPad = pad; } @Override public int getLeadingMargin(boolean first) { return mDrawable.getIntrinsicWidth() + mPad; } public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, @Override public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { @NonNull CharSequence text, int start, int end, boolean first, @NonNull Layout layout) { int st = ((Spanned) text).getSpanStart(this); int ix = (int) x; int itop = (int) layout.getLineTop(layout.getLineForOffset(st)); Loading @@ -54,22 +94,22 @@ implements LeadingMarginSpan, LineHeightSpan mDrawable.draw(c); } public void chooseHeight(CharSequence text, int start, int end, @Override public void chooseHeight(@NonNull CharSequence text, int start, int end, int istartv, int v, Paint.FontMetricsInt fm) { @NonNull Paint.FontMetricsInt fm) { if (end == ((Spanned) text).getSpanEnd(this)) { int ht = mDrawable.getIntrinsicHeight(); int need = ht - (v + fm.descent - fm.ascent - istartv); if (need > 0) if (need > 0) { fm.descent += need; } need = ht - (v + fm.bottom - fm.top - istartv); if (need > 0) if (need > 0) { fm.bottom += need; } } private Drawable mDrawable; private int mPad; } }
core/java/android/text/style/DynamicDrawableSpan.java +60 −19 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.text.style; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; Loading @@ -24,10 +27,41 @@ import android.graphics.drawable.Drawable; import java.lang.ref.WeakReference; /** * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with * the bottom or with the baseline of the surrounding text. * <p> * For an implementation that constructs the drawable from various sources (<code>Bitmap</code>, * <code>Drawable</code>, resource id or <code>Uri</code>) use {@link ImageSpan}. * <p> * A simple implementation of <code>DynamicDrawableSpan</code> that uses drawables from resources * looks like this: * <pre> * class MyDynamicDrawableSpan extends DynamicDrawableSpan { * * private final Context mContext; * private final int mResourceId; * * public MyDynamicDrawableSpan(Context context, @DrawableRes int resourceId) { * mContext = context; * mResourceId = resourceId; * } * * {@literal @}Override * public Drawable getDrawable() { * Drawable drawable = mContext.getDrawable(mResourceId); * drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); * return drawable; * } * }</pre> * The class can be used like this: * <pre> * SpannableString string = new SpannableString("Text with a drawable span"); * string.setSpan(new MyDynamicDrawableSpan(context, R.mipmap.ic_launcher), 12, 20, Spanned * .SPAN_EXCLUSIVE_EXCLUSIVE);</pre> * <img src="{@docRoot}reference/android/images/text/style/dynamicdrawablespan.png" /> * <figcaption>Replacing text with a drawable.</figcaption> */ public abstract class DynamicDrawableSpan extends ReplacementSpan { private static final String TAG = "DynamicDrawableSpan"; /** * A constant indicating that the bottom of this span should be aligned Loading @@ -44,12 +78,20 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { protected final int mVerticalAlignment; private WeakReference<Drawable> mDrawableRef; /** * Creates a {@link DynamicDrawableSpan}. The default vertical alignment is * {@link #ALIGN_BOTTOM} */ public DynamicDrawableSpan() { mVerticalAlignment = ALIGN_BOTTOM; } /** * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE}. * Creates a {@link DynamicDrawableSpan} based on a vertical alignment.\ * * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE} */ protected DynamicDrawableSpan(int verticalAlignment) { mVerticalAlignment = verticalAlignment; Loading @@ -71,9 +113,9 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { public abstract Drawable getDrawable(); @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { public int getSize(@NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) { Drawable d = getCachedDrawable(); Rect rect = d.getBounds(); Loading @@ -89,9 +131,9 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 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) { Drawable b = getCachedDrawable(); canvas.save(); Loading @@ -109,8 +151,9 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { WeakReference<Drawable> wr = mDrawableRef; Drawable d = null; if (wr != null) if (wr != null) { d = wr.get(); } if (d == null) { d = getDrawable(); Loading @@ -119,7 +162,5 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { return d; } private WeakReference<Drawable> mDrawableRef; }
core/java/android/text/style/IconMarginSpan.java +58 −17 Original line number Diff line number Diff line Loading @@ -16,28 +16,67 @@ package android.text.style; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Px; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.text.Layout; import android.text.Spanned; public class IconMarginSpan implements LeadingMarginSpan, LineHeightSpan { public IconMarginSpan(Bitmap b) { mBitmap = b; /** * Paragraph affecting span, that draws a bitmap at the beginning of a text. The span also allows * setting a padding between the bitmap and the text. The default value of the padding is 0px. The * span should be attached from the first character of the text. * <p> * For example, an <code>IconMarginSpan</code> with a bitmap and a padding of 30px can be set * like this: * <pre> * SpannableString string = new SpannableString("Text with icon and padding"); * string.setSpan(new IconMarginSpan(bitmap, 30), 0, string.length(), * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); * </pre> * <img src="{@docRoot}reference/android/images/text/style/iconmarginspan.png" /> * <figcaption>Text with <code>IconMarginSpan</code></figcaption> * <p> * * @see DrawableMarginSpan for working with a {@link android.graphics.drawable.Drawable} instead of * a {@link Bitmap}. */ public class IconMarginSpan implements LeadingMarginSpan, LineHeightSpan { @NonNull private final Bitmap mBitmap; @Px private final int mPad; /** * Creates an {@link IconMarginSpan} from a {@link Bitmap}. * * @param bitmap bitmap to be rendered at the beginning of the text */ public IconMarginSpan(@NonNull Bitmap bitmap) { this(bitmap, 0); } public IconMarginSpan(Bitmap b, int pad) { mBitmap = b; /** * Creates an {@link IconMarginSpan} from a {@link Bitmap}. * * @param bitmap bitmap to be rendered at the beginning of the text * @param pad padding width, in pixels, between the bitmap and the text */ public IconMarginSpan(@NonNull Bitmap bitmap, @IntRange(from = 0) int pad) { mBitmap = bitmap; mPad = pad; } @Override public int getLeadingMargin(boolean first) { return mBitmap.getWidth() + mPad; } @Override public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, Loading @@ -45,12 +84,14 @@ implements LeadingMarginSpan, LineHeightSpan int st = ((Spanned) text).getSpanStart(this); int itop = layout.getLineTop(layout.getLineForOffset(st)); if (dir < 0) if (dir < 0) { x -= mBitmap.getWidth(); } c.drawBitmap(mBitmap, x, itop, p); } @Override public void chooseHeight(CharSequence text, int start, int end, int istartv, int v, Paint.FontMetricsInt fm) { Loading @@ -58,15 +99,15 @@ implements LeadingMarginSpan, LineHeightSpan int ht = mBitmap.getHeight(); int need = ht - (v + fm.descent - fm.ascent - istartv); if (need > 0) if (need > 0) { fm.descent += need; } need = ht - (v + fm.bottom - fm.top - istartv); if (need > 0) if (need > 0) { fm.bottom += need; } } } private Bitmap mBitmap; private int mPad; }
core/java/android/text/style/ImageSpan.java +130 −31 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package android.text.style; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; Loading @@ -27,18 +29,49 @@ import android.util.Log; import java.io.InputStream; /** * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with * the bottom or with the baseline of the surrounding text. The drawable can be constructed from * varied sources: * <ul> * <li>{@link Bitmap} - see {@link #ImageSpan(Context, Bitmap)} and * {@link #ImageSpan(Context, Bitmap, int)} * </li> * <li>{@link Drawable} - see {@link #ImageSpan(Drawable, int)}</li> * <li>resource id - see {@link #ImageSpan(Context, int, int)}</li> * <li>{@link Uri} - see {@link #ImageSpan(Context, Uri, int)}</li> * </ul> * The default value for the vertical alignment is {@link DynamicDrawableSpan#ALIGN_BOTTOM} * <p> * For example, an <code>ImagedSpan</code> can be used like this: * <pre> * SpannableString string = SpannableString("Bottom: span.\nBaseline: span."); * // using the default alignment: ALIGN_BOTTOM * string.setSpan(ImageSpan(this, R.mipmap.ic_launcher), 7, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); * string.setSpan(ImageSpan(this, R.mipmap.ic_launcher, DynamicDrawableSpan.ALIGN_BASELINE), * 22, 23, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); * </pre> * <img src="{@docRoot}reference/android/images/text/style/imagespan.png" /> * <figcaption>Text with <code>ImageSpan</code>s aligned bottom and baseline.</figcaption> */ public class ImageSpan extends DynamicDrawableSpan { @Nullable private Drawable mDrawable; @Nullable private Uri mContentUri; @DrawableRes private int mResourceId; @Nullable private Context mContext; @Nullable private String mSource; /** * @deprecated Use {@link #ImageSpan(Context, Bitmap)} instead. */ @Deprecated public ImageSpan(Bitmap b) { public ImageSpan(@NonNull Bitmap b) { this(null, b, ALIGN_BOTTOM); } Loading @@ -46,80 +79,143 @@ public class ImageSpan extends DynamicDrawableSpan { * @deprecated Use {@link #ImageSpan(Context, Bitmap, int)} instead. */ @Deprecated public ImageSpan(Bitmap b, int verticalAlignment) { public ImageSpan(@NonNull Bitmap b, int verticalAlignment) { this(null, b, verticalAlignment); } public ImageSpan(Context context, Bitmap b) { this(context, b, ALIGN_BOTTOM); /** * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Bitmap} with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} * * @param context context used to create a drawable from {@param bitmap} based on the display * metrics of the resources * @param bitmap bitmap to be rendered */ public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap) { this(context, bitmap, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Bitmap} and a vertical * alignment. * * @param context context used to create a drawable from {@param bitmap} based on * the display metrics of the resources * @param bitmap bitmap to be rendered * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Context context, Bitmap b, int verticalAlignment) { public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap, int verticalAlignment) { super(verticalAlignment); mContext = context; mDrawable = context != null ? new BitmapDrawable(context.getResources(), b) : new BitmapDrawable(b); ? new BitmapDrawable(context.getResources(), bitmap) : new BitmapDrawable(bitmap); int width = mDrawable.getIntrinsicWidth(); int height = mDrawable.getIntrinsicHeight(); mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0); } public ImageSpan(Drawable d) { this(d, ALIGN_BOTTOM); /** * Constructs an {@link ImageSpan} from a drawable with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. * * @param drawable drawable to be rendered */ public ImageSpan(@NonNull Drawable drawable) { this(drawable, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a drawable and a vertical alignment. * * @param drawable drawable to be rendered * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Drawable d, int verticalAlignment) { public ImageSpan(@NonNull Drawable drawable, int verticalAlignment) { super(verticalAlignment); mDrawable = d; mDrawable = drawable; } public ImageSpan(Drawable d, String source) { this(d, source, ALIGN_BOTTOM); /** * Constructs an {@link ImageSpan} from a drawable and a source with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} * * @param drawable drawable to be rendered * @param source drawable's Uri source */ public ImageSpan(@NonNull Drawable drawable, @NonNull String source) { this(drawable, source, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a drawable, a source and a vertical alignment. * * @param drawable drawable to be rendered * @param source drawable's uri source * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Drawable d, String source, int verticalAlignment) { public ImageSpan(@NonNull Drawable drawable, @NonNull String source, int verticalAlignment) { super(verticalAlignment); mDrawable = d; mDrawable = drawable; mSource = source; } public ImageSpan(Context context, Uri uri) { /** * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Uri} with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. The Uri source can be retrieved via * {@link #getSource()} * * @param context context used to create a drawable from {@param bitmap} based on the display * metrics of the resources * @param uri {@link Uri} used to construct the drawable that will be rendered */ public ImageSpan(@NonNull Context context, @NonNull Uri uri) { this(context, uri, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Uri} and a vertical * alignment. The Uri source can be retrieved via {@link #getSource()} * * @param context context used to create a drawable from {@param bitmap} based on * the display * metrics of the resources * @param uri {@link Uri} used to construct the drawable that will be rendered. * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Context context, Uri uri, int verticalAlignment) { public ImageSpan(@NonNull Context context, @NonNull Uri uri, int verticalAlignment) { super(verticalAlignment); mContext = context; mContentUri = uri; mSource = uri.toString(); } public ImageSpan(Context context, @DrawableRes int resourceId) { /** * Constructs an {@link ImageSpan} from a {@link Context} and a resource id with the default * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} * * @param context context used to retrieve the drawable from resources * @param resourceId drawable resource id based on which the drawable is retrieved */ public ImageSpan(@NonNull Context context, @DrawableRes int resourceId) { this(context, resourceId, ALIGN_BOTTOM); } /** * Constructs an {@link ImageSpan} from a {@link Context}, a resource id and a vertical * alignment. * * @param context context used to retrieve the drawable from resources * @param resourceId drawable resource id based on which the drawable is retrieved. * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or * {@link DynamicDrawableSpan#ALIGN_BASELINE}. * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ public ImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) { public ImageSpan(@NonNull Context context, @DrawableRes int resourceId, int verticalAlignment) { super(verticalAlignment); mContext = context; mResourceId = resourceId; Loading @@ -142,7 +238,7 @@ public class ImageSpan extends DynamicDrawableSpan { drawable.getIntrinsicHeight()); is.close(); } catch (Exception e) { Log.e("sms", "Failed to loaded content " + mContentUri, e); Log.e("ImageSpan", "Failed to loaded content " + mContentUri, e); } } else { try { Loading @@ -150,7 +246,7 @@ public class ImageSpan extends DynamicDrawableSpan { drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } catch (Exception e) { Log.e("sms", "Unable to find resource: " + mResourceId); Log.e("ImageSpan", "Unable to find resource: " + mResourceId); } } Loading @@ -159,9 +255,12 @@ public class ImageSpan extends DynamicDrawableSpan { /** * Returns the source string that was saved during construction. * * @return the source string that was saved during construction * @see #ImageSpan(Drawable, String) and this{@link #ImageSpan(Context, Uri)} */ @Nullable public String getSource() { return mSource; } }
core/java/android/text/style/LineHeightSpan.java +33 −7 Original line number Diff line number Diff line Loading @@ -19,16 +19,42 @@ package android.text.style; import android.graphics.Paint; import android.text.TextPaint; public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { /** * The classes that affect the height of the line should implement this interface. */ public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { /** * Classes that implement this should define how the height is being calculated. * * @param text the text * @param start the start of the line * @param end the end of the line * @param spanstartv the start of the span * @param lineHeight the line height * @param fm font metrics of the paint, in integers */ public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, int spanstartv, int lineHeight, Paint.FontMetricsInt fm); /** * The classes that affect the height of the line with respect to density, should implement this * interface. */ public interface WithDensity extends LineHeightSpan { /** * Classes that implement this should define how the height is being calculated. * * @param text the text * @param start the start of the line * @param end the end of the line * @param spanstartv the start of the span * @param lineHeight the line height * @param paint the paint */ public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, int spanstartv, int lineHeight, Paint.FontMetricsInt fm, TextPaint paint); } }