Android 将文本围绕图像中心垂直对齐
我有一段文字的内部。我注意到,周围的文本总是在文本行的底部绘制——更准确地说,文本行的大小随着图像的增长而增大,但文本的基线不会向上移动。当图像明显大于文本大小时,效果相当难看 下面是一个示例,大纲显示了Android 将文本围绕图像中心垂直对齐,android,text,spannable,imagespan,Android,Text,Spannable,Imagespan,我有一段文字的内部。我注意到,周围的文本总是在文本行的底部绘制——更准确地说,文本行的大小随着图像的增长而增大,但文本的基线不会向上移动。当图像明显大于文本大小时,效果相当难看 下面是一个示例,大纲显示了TextView的边界: 我试图让周围的文字相对于显示的图像垂直居中。下面是相同的示例,蓝色文本显示了所需的位置: 以下是约束条件: 我不能使用复合抽绳。图像必须能够在文字之间显示 文本可能是多行的,具体取决于内容。我无法控制这一切 我的图像比周围的文本大,我无法缩小它们的大小。虽然上面的
TextView
的边界:
我试图让周围的文字相对于显示的图像垂直居中。下面是相同的示例,蓝色文本显示了所需的位置:
以下是约束条件:
- 我不能使用复合抽绳。图像必须能够在文字之间显示
- 文本可能是多行的,具体取决于内容。我无法控制这一切
- 我的图像比周围的文本大,我无法缩小它们的大小。虽然上面的示例图像比实际图像大(以演示当前行为),但实际图像仍然足够大,因此此问题很明显
ImageSpan
s的交点,这样我也可以避免移动图像。这听起来很吓人,我希望有人能提出另一种方法
感谢您的任何帮助 通过创建继承自的类,我得到了一个可行的解决方案 然后修改了DynamicDrawableSpan中的draw实现。至少当我的图像高度小于字体高度时,此实现可以工作。不知道这对像你这样的大图像是如何起作用的
@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;
考虑到本地化,这可能不是一个好的做法,但对我来说很有效。为了在文本的中间设置图像,你自然需要用文本来替换文本中的标记。
可能有点晚,但是我已经找到了一种方法,不管图像大小。您需要创建一个扩展ImageSpan的类,并重写方法
getSize()
和getCachedRavable()
(我们不需要更改最后一个方法,但是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;
}
导入android.graphics.Canvas;
导入android.graphics.Paint;
导入android.graphics.Rect;
导入android.graphics.drawable.drawable;
导入android.text.style.DynamicDrawableSpan;
导入android.text.style.ImageSpan;
导入java.lang.ref.WeakReference;
公共类中心的ImageSpan扩展了ImageSpan{
//添加ImageSpan时用于重新定义字体度量的额外变量
私人整数初始下降=0;
私有int外部空间=0;
公共中心图像跨度(最终可绘制){
这(可拉拔、动态拉拔span.与底部对齐);
}
公共中心图像跨度(最终可绘制、最终内部垂直对齐){
超级(可拉伸、垂直对齐);
}
@凌驾
公共空白绘制(画布、字符序列文本、,
整数开始,整数结束,浮点x,
内部顶部、内部y、内部底部、油漆){
getDrawable().draw(画布);
}
//方法,用于在添加ImageSpan时重新定义字体度量
@凌驾
公共int getSize(绘制、字符序列文本、,
int开始,int结束,
Paint.FontMetricsInt格式){
Drawable d=getCachedDrawable();
Rect Rect=d.getBounds();
如果(fm!=null){
//将文本与ImageSpan居中
如果(垂直底部-(调频下降-调频上升)>=0){
//存储初始下降并计算可用的余量
初始下降=调频下降;
外部空间=垂直底部-(调频下降-调频上升);
}
fm.descent=外部空间/2+初始下降;
fm.bottom=fm.down;
fm.ascent=-rect.bottom+fm.DESCEND;
fm.top=fm.ascend;
}
返回rect.right;
}
//在本地重新定义,因为它是DynamicDrawableSpan中的私有成员
私有可绘制getCachedDrawable(){
WeakReference wr=mDrawableRef;
可提取d=null;
如果(wr!=null)
d=wr.get();
如果(d==null){
d=getDrawable();
mDrawableRef=新的WeakReference(d);
}
返回d;
}
私有WeakReference mDrawableRef;
}
如果你对那门课有任何问题,请告诉我 我的答案调整了第一个答案。事实上,我已经试过上面两种方法,我不认为它们真的是中心垂直的。如果它位于
上升
和下降
之间,而不是顶部
和底部
之间,则会使可绘制的图形更加居中。至于第二个答案,它将可绘制图形的中心与文本的基线对齐,而不是与文本的中心对齐。以下是我的解决方案:
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;
}
}
public class CenteredImageSpan扩展了ImageSpan{
私有WeakReference mDrawableRef;
public CenteredImageSpan(上下文,最终int-drawables){
super(上下文、drawableRes);
}
@凌驾
公众的
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;
}
}
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();
}
};
@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;
}
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();
}
}
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;
}
}
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;
}
}
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
}
}
val mySpannable = SpannableString(" $YourText")
mySpannable.setSpan(ImageSpan(yourDrawable, DynamicDrawableSpan.ALIGN_CENTER), 0, 1, 0)