2014-09-02 3 views
26

У меня есть ImageSpan внутри куска текста. Я заметил, что окружающий текст всегда рисуется в нижней части текстовой строки - точнее, размер текстовой строки растет с изображением, но исходный текст не смещается вверх. Когда изображение заметно больше размера текста, эффект довольно неприглядный.Выровнять текст вокруг ImageSpan center vertical

Вот пример, схема показывает границы в TextView: enter image description here

Я пытаюсь иметь окружающий текст центрируется по вертикали относительно выводимого изображения. Вот тот же пример с синим текстом, показывающий желаемое место:

enter image description here

Вот ограничения, которые я связанными:

  • Я не могу использовать сложные вводимого коэффициента. Изображения должны быть показаны между словами.
  • Текст может быть многострочным в зависимости от содержимого. Я не контролирую это.
  • Мои изображения больше, чем в окружающем тексте, и я не могу уменьшить их размер. В то время как изображение образца выше, чем фактические изображения (чтобы продемонстрировать текущее поведение), фактические изображения все еще достаточно велики, чтобы эта проблема была заметна.

Я попытался использовать атрибут android:gravity="center_vertical" в TextView, но это не имеет никакого эффекта. Я считаю, что это только вертикально центрирует текст строк, но в текстовой строке текст по-прежнему рисуется внизу.

Мой текущий ход мысли заключается в создании настраиваемого диапазона, который сдвигает базовую линию текста в зависимости от высоты строки и текущего размера текста. Этот диапазон охватывал бы весь текст, и мне пришлось бы вычислить пересечение с ImageSpan s, чтобы я мог избежать смещения изображений. Это звучит довольно сложно, и я надеюсь, что кто-то может предложить другой подход.

Любая помощь приветствуется!

+1

Вы выяснили, как это сделать? – WindsurferOak

+1

Я ищу для достижения того же материала, однако ответ ptilli заставляет его выглядеть так, как будто он работает, но на самом деле он не делает то, что нужно сделать, и это центрирует текст курсором. Я дам вам знать, смогу ли я что-то выяснить. – jujux789

+1

Ответы ниже работают только с меньшим размером изображения, чем текст. Автору и мне нужен противоположный трюк – ElSajko

ответ

23

Возможно, это немного поздно, но я нашел способ сделать это, независимо от размера изображения. Вам нужно создать класс, расширяющий ImageSpan и переопределить методы getSize() и getCachedDrawable() (нам не нужно менять последнее, но этот метод от DynamicDrawableSpan является закрытым и к нему нельзя получить доступ другим способом из дочернего класса). В getSize(...) вы можете переопределить способ DynamicDrawableSpan установить восхождение/верхний/спуск/дно линии и добиться того, что вы хотите сделать.

Вот мой класс пример:

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, entry, DynamicDrawableSpan.ALIGN_BOTTOM); 
    } 

    public CenteredImageSpan(final Drawable drawable, final int verticalAlignment) { 
     super(drawable, verticalAlignment); 
    } 

    @Override 
    public Rect getBounds() { 
     return getDrawable().getBounds(); 
    } 

    @Override 
    public void draw(final Canvas canvas) { 
     getDrawable().draw(canvas); 
    } 


    // 
    // Following methods are overriden from DynamicDrawableSpan. 
    // 

    // 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; 
} 

Позвольте мне знать, если у вас есть какие-либо проблемы с этим классом!

+0

Как использовать? Создание такого же класса, я думаю, недостаточно. – ElSajko

+1

Вам просто нужно заменить ImageSpan, который вы использовали с CenteredImageSpan, и он автоматически центрирует ваше изображение с текстом – jujux789

+0

, но что, если я использую класс ImageSpan не напрямую, а с помощью Html.fromHtml (String)? – ElSajko

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; 

Возможно, нет хорошей практики там, учитывая локализацию, но работает для меня. Чтобы установить изображения в середине текста, вам, естественно, нужно будет заменить токены в тексте пробелами.

+1

Что происходит, когда вы используете изображение большего размера? – Karakuri

45

Мой ответ изменяет первый ответ. На самом деле я попробовал оба метода выше, и я не думаю, что они действительно вертикальные по центру. Это сделало бы более привлекательным, если бы оно было размещено между ascent и descent, а не top и bottom. Что касается второго ответа, он выравнивает центр доступного к базовой линии текста, а не центр этого текста. Вот мое решение:

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 из вытяжке же, как и другой текст, в противном случае родительский вид будет не обернуть содержимое правильно.

+0

Это лучшее решение, которое также отвечает на мой вопрос. Это мой вопрос ... http://stackoverflow.com/questions/31249475/cannot-set-image-and-text-in-center-vertical-android-pagertabstrip-android – user3057361

+0

Работает отлично. Убедитесь, что вы используете контекст и имя ресурса при использовании класса. Пример: 'ImageSpan imageSpan = new CenteredImageSpan (getApplicationContext(), R.drawable.ic_youricon)' (в отличие от предыдущего примера) – logic

+1

did'nt работает на 4-дюймовом устройстве 480x800 Значки hdpi обрезаются внизу, текст отображается правильно, но работает xxhdpi устройства – chin87

16
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(); 
       } 
      }; 
+0

работает хорошо и просто. Могли бы объяснить значения «нижний верхний», «x y»? Я не понимаю и не объясняю никаких документов. Спасибо –

+0

заменить ничью с @ misaka-10032 с этой ничьей и код работает на все разрешения – chin87

-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; 
    } 
3

После прочтения исходного кода TextView, я думаю, мы можем использовать baseLine текстовой строки eache, которая является «y». И он будет работать, даже если вы установите 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(); 
    } 

} 
+0

Это должно быть принято ans :) Спасибо –