テキストの中に ImageSpan
があります。私が気づいたのは、周囲のテキストが常にテキスト行の下部に描画されることです-より正確には、テキスト行のサイズは画像とともに大きくなりますが、テキストのベースラインは上に移動しません。画像がテキストサイズよりも著しく大きい場合、効果はかなり見苦しくなります。
以下にサンプルを示します。アウトラインはTextView
の境界を示しています。
表示されている画像に対して、周囲のテキストを垂直方向に中央揃えしようとしています。同じサンプルに、目的の場所を示す青色のテキストを示します。
私が拘束している制約は次のとおりです。
TextViewでAndroid:gravity="center_vertical"
属性を使用してみましたが、これは効果がありません。これは、テキスト行をちょうど垂直方向に中央揃えしていると思いますが、テキスト行内ではテキストはまだ下部に描画されます。
私の現在の考え方は、行の高さと現在のテキストサイズに基づいてテキストのベースラインをシフトするカスタムスパンを作成することです。このスパンはテキスト全体を網羅し、ImageSpan
sとの交差を計算する必要があるため、画像のシフトも回避できます。これはかなり気が遠くなるようで、誰かが別のアプローチを提案できることを期待しています。
すべての助けがありがたいです!
少し遅いかもしれませんが、画像サイズに関係なく、それを行う方法を見つけました。 ImageSpanを拡張するクラスを作成し、メソッドgetSize()
およびgetCachedDrawable()
をオーバーライドする必要があります(最後のメソッドを変更する必要はありませんが、DynamicDrawableSpan
のこのメソッドはプライベートであり、子クラスから別の方法でアクセスすることはできません)。 getSize(...)
では、DynamicDrawableSpan
が行のアセント/トップ/ディセント/ボトムを設定する方法を再定義して、やりたいことを実現できます。
クラスの例を次に示します。
import Android.graphics.Canvas;
import Android.graphics.Paint;
import Android.graphics.Rect;
import Android.graphics.drawable.Drawable;
import Android.text.style.DynamicDrawableSpan;
import Android.text.style.ImageSpan;
import Java.lang.ref.WeakReference;
public class CenteredImageSpan extends ImageSpan {
// Extra variables used to redefine the Font Metrics when an ImageSpan is added
private int initialDescent = 0;
private int extraSpace = 0;
public CenteredImageSpan(final Drawable drawable) {
this(drawable, DynamicDrawableSpan.ALIGN_BOTTOM);
}
public CenteredImageSpan(final Drawable drawable, final int verticalAlignment) {
super(drawable, verticalAlignment);
}
@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
getDrawable().draw(canvas);
}
// Method used to redefined the Font Metrics when an ImageSpan is added
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
if (fm != null) {
// Centers the text with the ImageSpan
if (rect.bottom - (fm.descent - fm.ascent) >= 0) {
// Stores the initial descent and computes the margin available
initialDescent = fm.descent;
extraSpace = rect.bottom - (fm.descent - fm.ascent);
}
fm.descent = extraSpace / 2 + initialDescent;
fm.bottom = fm.descent;
fm.ascent = -rect.bottom + fm.descent;
fm.top = fm.ascent;
}
return rect.right;
}
// Redefined locally because it is a private member from DynamicDrawableSpan
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;
}
そのクラスに問題がある場合はお知らせください!
私の答えは最初の答えを微調整します。実際、私は上記の2つの方法の両方を試してみましたが、それらは真ん中にあるとは思いません。 ascent
とdescent
ではなく、top
とbottom
の間に配置すると、ドロアブルがより中央に配置されます。 2番目の答えについては、ドロウアブルの中心をテキストの中心ではなく、テキストのベースラインに揃えます。私のソリューションは次のとおりです。
public class CenteredImageSpan extends ImageSpan {
private WeakReference<Drawable> mDrawableRef;
public CenteredImageSpan(Context context, final int drawableRes) {
super(context, drawableRes);
}
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
if (fm != null) {
Paint.FontMetricsInt pfm = Paint.getFontMetricsInt();
// keep it the same as Paint's fm
fm.ascent = pfm.ascent;
fm.descent = pfm.descent;
fm.top = pfm.top;
fm.bottom = pfm.bottom;
}
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 b = getCachedDrawable();
canvas.save();
int drawableHeight = b.getIntrinsicHeight();
int fontAscent = Paint.getFontMetricsInt().ascent;
int fontDescent = Paint.getFontMetricsInt().descent;
int transY = bottom - b.getBounds().bottom + // align bottom to bottom
(drawableHeight - fontDescent + fontAscent) / 2; // align center to center
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
// Redefined locally because it is a private member from DynamicDrawableSpan
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;
}
}
また、DrawableのFontMetricsを他のテキストと同じに保つためにgetSize
を書き換えます。そうしないと、親ビューはコンテンツを正しくラップしません。
ImageSpan imageSpan = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM) {
public void draw(Canvas canvas, CharSequence text, int start,
int end, float x, int top, int y, int bottom,
Paint paint) {
Drawable b = getDrawable();
canvas.save();
int transY = bottom - b.getBounds().bottom;
// this is the key
transY -= Paint.getFontMetricsInt().descent / 2;
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
};
TextViewのソースコードを読んだ後、各テキスト行の「y」であるbaseLineを使用できると思います。また、lineSpaceExtraを設定しても機能します。
public class VerticalImageSpan extends ImageSpan {
public VerticalImageSpan(Drawable drawable) {
super(drawable);
}
/**
* update the text line height
*/
@Override
public int getSize(Paint paint, CharSequence text, int start, int end,
Paint.FontMetricsInt fontMetricsInt) {
Drawable drawable = getDrawable();
Rect rect = drawable.getBounds();
if (fontMetricsInt != null) {
Paint.FontMetricsInt fmPaint = Paint.getFontMetricsInt();
int fontHeight = fmPaint.descent - fmPaint.ascent;
int drHeight = rect.bottom - rect.top;
int centerY = fmPaint.ascent + fontHeight / 2;
fontMetricsInt.ascent = centerY - drHeight / 2;
fontMetricsInt.top = fontMetricsInt.ascent;
fontMetricsInt.bottom = centerY + drHeight / 2;
fontMetricsInt.descent = fontMetricsInt.bottom;
}
return rect.right;
}
/**
* see detail message in Android.text.TextLine
*
* @param canvas the canvas, can be null if not rendering
* @param text the text to be draw
* @param start the text start position
* @param end the text end position
* @param x the Edge of the replacement closest to the leading margin
* @param top the top of the line
* @param y the baseline
* @param bottom the bottom of the line
* @param Paint the work Paint
*/
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, Paint paint) {
Drawable drawable = getDrawable();
canvas.save();
Paint.FontMetricsInt fmPaint = Paint.getFontMetricsInt();
int fontHeight = fmPaint.descent - fmPaint.ascent;
int centerY = y + fmPaint.descent - fontHeight / 2;
int transY = centerY - (drawable.getBounds().bottom - drawable.getBounds().top) / 2;
canvas.translate(x, transY);
drawable.draw(canvas);
canvas.restore();
}
}
ImageSpan を継承するクラスを作成することにより、実用的なソリューションを得ました。
次に、DynamicDrawableSpanから描画実装を変更しました。少なくともこの実装は、画像の高さがフォントの高さより低い場合に機能します。あなたのような大きな画像でこれがどのように機能するかはわかりません。
@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 bCenter = b.getIntrinsicHeight() / 2;
int fontTop = Paint.getFontMetricsInt().top;
int fontBottom = Paint.getFontMetricsInt().bottom;
int transY = (bottom - b.getBounds().bottom) -
(((fontBottom - fontTop) / 2) - bCenter);
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
また、DynamicDrawableSpanの実装はプライベートであるため、再利用する必要がありました。
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;
}
private WeakReference<Drawable> mDrawableRef;
これは、テキストの前に画像を挿入する静的メソッドとして使用する方法です。
public static CharSequence formatTextWithIcon(Context context, String text,
int iconResourceId) {
SpannableStringBuilder sb = new SpannableStringBuilder("X");
try {
Drawable d = context.getResources().getDrawable(iconResourceId);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
CenteredImageSpan span = new CenteredImageSpan(d);
sb.setSpan(span, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(" " + text);
} catch (Exception e) {
e.printStackTrace();
sb.append(text);
}
return sb;
ローカライズを考慮すると、おそらく良いプラクティスではありませんが、私には役立ちます。テキストの中央に画像を設定するには、当然テキスト内のトークンをスパンに置き換える必要があります。
私の答えはmisaka-10032の答えを微調整します。完璧に動作します!
public static class CenteredImageSpan extends ImageSpan {
private WeakReference<Drawable> mDrawableRef;
CenteredImageSpan(Context context, final int drawableRes) {
super(context, drawableRes);
}
public CenteredImageSpan(@NonNull Drawable d) {
super(d);
}
@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 b = getCachedDrawable();
canvas.save();
int transY = top + (bottom - top - b.getBounds().bottom)/2;
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
// Redefined locally because it is a private member from DynamicDrawableSpan
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;
}
}