我在一段文字中有一个ImageSpan
。我注意到的是,周围的文本总是在文本行的底部绘制 - 更准确地说,文本行的大小随着图像的增长而增加,但文本的基线不会向上移动。当图像明显大于文本大小时,效果相当难看。
以下是一个示例,大纲显示TextView
的边界:
我试图让周围的文字相对于正在显示的图像垂直居中。以下是蓝色文本显示所需位置的相同示例:
以下是我受约束的约束:
我尝试在TextView上使用android:gravity="center_vertical"
属性,但这没有任何效果。我相信这只是文本行的垂直居中,但在文本行中,文本仍然在底部绘制。
我目前的思路是创建一个自定义范围,根据线条的高度和当前文字大小来移动文本的基线。这个范围将涵盖整个文本,我将不得不计算与ImageSpan
的交集,所以我也可以避免移动图像。这听起来相当令人生畏,我希望有人能提出另一种方法。
感谢任何和所有帮助!
答案 0 :(得分:58)
我的回答调整了第一个答案。实际上我已经尝试了上述两种方法,我认为它们并不是真正的中心垂直方向。如果它位于ascent
和descent
之间,而不是top
和bottom
,它会使drawable更加居中。因此,对于第二个答案,它将drawable的中心与文本的基线对齐,而不是该文本的中心。这是我的解决方案:
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;
}
}
我还重写getSize
以使FontMetrics的drawable与其他文本保持一致,否则父视图将无法正确包装内容。
答案 1 :(得分:26)
可能有点晚了,但无论图像大小如何,我都找到了一种方法。您需要创建一个扩展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 :(得分:21)
while(true) {
auto file = CreateFileA(
"test.txt",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (file == INVALID_HANDLE_VALUE) {
std::cerr << "create failed: " << std::error_code(GetLastError(),
std::system_category()).message() << std::endl;
continue;
}
if (!CloseHandle(file))
std::cerr << "close failed: " << std::error_code(GetLastError(),
std::system_category()).message() << std::endl;
if (!DeleteFileA("test.txt"))
std::cerr << "delete failed: " << std::error_code(GetLastError(),
std::system_category()).message() << std::endl;
}
答案 3 :(得分:18)
在阅读了TextView的源代码之后,我想我们可以使用eache文本行的baseLine,它是&#34; y&#34;。 即使你设置了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();
}
}
答案 4 :(得分:4)
我通过创建一个继承自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;
考虑到本地化,也许不是一个好习惯,但对我有用。要在文本中间设置图像,您自然需要使用跨度替换文本中的标记。
答案 5 :(得分:1)
此解决方案根据实际字母大小提供垂直居中。它支持使用大写字母和小写字母居中。例如,查看字母附近的标记字符:X•。该解决方案可以达到类似的效果。
这是@WindRider答案的修改版本。另外,它在科特林。它支持可绘制尺寸的自定义。
创建此解决方案的原因是为了提供更好的视觉效果。许多其他解决方案都使用字体提升。但是在某些情况下,它似乎会引起视觉问题。例如,Android的默认Roboto字体比典型的大写字母顶部边框高。因此,需要进行一些手动调整才能正确居中图像。
class CenteredImageSpan(context: Context,
drawableRes: Int,
private val centerType: CenterType = CenterType.CAPITAL_LETTER,
private val customHeight: Int? = null,
private val customWidth: Int? = null) : ImageSpan(context, drawableRes) {
private var mDrawableRef: WeakReference<Drawable?>? = null
override fun getSize(paint: Paint, text: CharSequence,
start: Int, end: Int,
fontMetrics: FontMetricsInt?): Int {
if (fontMetrics != null) {
val currentFontMetrics = paint.fontMetricsInt
// keep it the same as paint's Font Metrics
fontMetrics.ascent = currentFontMetrics.ascent
fontMetrics.descent = currentFontMetrics.descent
fontMetrics.top = currentFontMetrics.top
fontMetrics.bottom = currentFontMetrics.bottom
}
val drawable = getCachedDrawable()
val rect = drawable.bounds
return rect.right
}
override fun draw(canvas: Canvas,
text: CharSequence,
start: Int,
end: Int,
x: Float,
lineTop: Int,
baselineY: Int,
lineBottom: Int,
paint: Paint) {
val cachedDrawable = getCachedDrawable()
val drawableHeight = cachedDrawable.bounds.height()
val relativeVerticalCenter = getLetterVerticalCenter(paint)
val drawableCenter = baselineY + relativeVerticalCenter
val drawableBottom = drawableCenter - drawableHeight / 2
canvas.save()
canvas.translate(x, drawableBottom.toFloat())
cachedDrawable.draw(canvas)
canvas.restore()
}
private fun getLetterVerticalCenter(paint: Paint): Int =
when (centerType) {
CenterType.CAPITAL_LETTER -> getCapitalVerticalCenter(paint)
CenterType.LOWER_CASE_LETTER -> getLowerCaseVerticalCenter(paint)
}
private fun getCapitalVerticalCenter(paint: Paint): Int {
val bounds = Rect()
paint.getTextBounds("X", 0, 1, bounds)
return (bounds.bottom + bounds.top) / 2
}
private fun getLowerCaseVerticalCenter(paint: Paint): Int {
val bounds = Rect()
paint.getTextBounds("x", 0, 1, bounds)
return (bounds.bottom + bounds.top) / 2
}
// Redefined here because it's private in DynamicDrawableSpan
private fun getCachedDrawable(): Drawable {
val drawableWeakReference = mDrawableRef
var drawable: Drawable? = null
if (drawableWeakReference != null) drawable = drawableWeakReference.get()
if (drawable == null) {
drawable = getDrawable()!!
val width = customWidth ?: drawable.intrinsicWidth
val height = customHeight ?: drawable.intrinsicHeight
drawable.setBounds(0, 0,
width, height)
mDrawableRef = WeakReference(drawable)
}
return drawable
}
enum class CenterType {
CAPITAL_LETTER, LOWER_CASE_LETTER
}
}
答案 6 :(得分:0)
我的答案调整了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;
}
}
答案 7 :(得分:0)
在创建图像跨度时,必须添加垂直对齐标记DynamicDrawableSpan.ALIGN_CENTER。那应该使图像的中心与文本对齐。
val mySpannable = SpannableString(" $YourText")
mySpannable.setSpan(ImageSpan(yourDrawable, DynamicDrawableSpan.ALIGN_CENTER), 0, 1, 0)
答案 8 :(得分:0)
您可以使用 ImageSpan.ALIGN_CENTER。我在各种模拟器上对其进行了测试,它似乎适用于 API <29
答案 9 :(得分:-1)
我的改进版本:可绘制字体指标相对于文本字体指标进行了缩放。这样行间距就可以正确计算。
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
float drawableHeight = Float.valueOf(rect.height());
if (fm != null) {
Paint.FontMetricsInt pfm = paint.getFontMetricsInt();
float fontHeight = pfm.descent - pfm.ascent;
float ratio = drawableHeight / fontHeight;
fm.ascent = Float.valueOf(pfm.ascent * ratio).intValue();
fm.descent = Float.valueOf(pfm.descent * ratio).intValue();
fm.top = fm.ascent;
fm.bottom = fm.descent;
}
答案 10 :(得分:-1)
此解决方案有效。我测试了它并且正在使用它一段时间。它不考虑上升和体面,但它在中心对齐drawable。
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.text.style.ImageSpan;
import java.lang.ref.WeakReference;
public class CustomImageSpan extends ImageSpan {
/**
* A constant indicating that the center of this span should be aligned
* with the center of the surrounding text
*/
public static final int ALIGN_CENTER = -12;
private WeakReference<Drawable> mDrawable;
private int mAlignment;
public CustomImageSpan(Context context, final int drawableRes, int alignment) {
super(context, drawableRes);
mAlignment = alignment;
}
@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();
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) {
if (mAlignment == ALIGN_CENTER) {
Drawable cachedDrawable = getCachedDrawable();
canvas.save();
//Get the center point and set the Y coordinate considering the drawable height for aligning the icon vertically
int transY = ((top + bottom) / 2) - cachedDrawable.getIntrinsicHeight() / 2;
canvas.translate(x, transY);
cachedDrawable.draw(canvas);
canvas.restore();
} else {
super.draw(canvas, text, start, end, x, top, y , bottom, paint);
}
}
// Redefined locally because it is a private member from DynamicDrawableSpan
private Drawable getCachedDrawable() {
WeakReference<Drawable> wr = mDrawable;
Drawable d = null;
if (wr != null) {
d = wr.get();
}
if (d == null) {
d = getDrawable();
mDrawable = new WeakReference<>(d);
}
return d;
}
}