2014-10-31 3 views
3

Я пытаюсь отобразить в Android с TextView таким образом, что текст в окне будет сверху выровнено:Действительно верхнее выравнивание текста в Android TextView

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 

    // Create container layout 
    FrameLayout layout = new FrameLayout(this); 

    // Create text label 
    TextView label = new TextView(this); 
    label.setTextSize(TypedValue.COMPLEX_UNIT_PX, 25);  // 25 pixels tall 
    label.setGravity(Gravity.TOP + Gravity.CENTER);  // Align text top-center 
    label.setPadding(0, 0, 0, 0);       // No padding 
    Rect bounds = new Rect(); 
    label.getPaint().getTextBounds("gdyl!", 0, 5, bounds); // Measure height 
    label.setText("good day, world! "+bounds.top+" to "+bounds.bottom); 
    label.setTextColor  (0xFF000000);     // Black text 
    label.setBackgroundColor(0xFF00FFFF);     // Blue background 

    // Position text label 
    FrameLayout.LayoutParams layoutParams = 
      new FrameLayout.LayoutParams(300, 25, Gravity.LEFT + Gravity.TOP); 
                  // also 25 pixels tall 
    layoutParams.setMargins(50, 50, 0, 0); 
    label.setLayoutParams(layoutParams); 

    // Compose screen 
    layout.addView(label); 
    setContentView(layout); 
} 

Этот код выводит следующее изображение:

enter image description here

вещи, чтобы отметить:

  • Синее окно 25 пикселей та ll, точно так же, как запрошено
  • Текстовые границы также указываются как 25 пикселей в высоту по требованию (6 - (-19) = 25)
  • Текст не начинается в верхней части метки, но имеет надпись выше он, не обращая внимания setPadding()
  • Это приводит к тому, текст обрезаемый в нижней части, даже если коробка технически достаточно высок

Как сказать TextView начать текст на самом верху коробка?

У меня есть два ограничения на возможные ответы:

  • мне нужно сохранить текст верхним выровнен, хотя, так что если есть какой-то подвох с нижней выравниванью или центрирований его вертикально, а я могу» t использовать его, так как у меня есть сценарии, где TextView выше, чем нужно.
  • Я немного похож на совместимость, поэтому, если возможно, я хотел бы придерживаться вызовов, которые были доступны в ранних API Android (желательно 1, но определенно не выше 7).
+0

Предлагаю вам посмотреть на это; http://stackoverflow.com/questions/7549182/android-paint-measuretext-vs-gettextbounds Я считаю, что ваш textSize равен 25 px, но высота вашего textView больше 25 (я пробую 32 пикселя и полностью центрирую весь макет). –

ответ

8

TextViews использовать абстрактный класс android.text.Layout к draw the text на холсте:

canvas.drawText(buf, start, end, x, lbaseline, paint);

Вертикальное смещение lbaseline рассчитывается как в нижней части строки минус сошествия шрифта:

int lbottom = getLineTop(i+1);
int lbaseline = lbottom - getLineDescent(i);

Две вызываемые функции getLineTop и getLineDescent являются абстрактными, но простую реализацию можно найти в BoringLayout (go figure ... :), которая просто возвращает свои значения для и mDesc. Они рассчитываются в методе init следующим образом:

if (includepad) { 
    spacing = metrics.bottom - metrics.top; 
} else { 
    spacing = metrics.descent - metrics.ascent; 
} 

if (spacingmult != 1 || spacingadd != 0) { 
    spacing = (int)(spacing * spacingmult + spacingadd + 0.5f); 
} 

mBottom = spacing; 

if (includepad) { 
    mDesc = spacing + metrics.top; 
} else { 
    mDesc = spacing + metrics.ascent; 
} 

Здесь includepad это логическая переменная, которая определяет, будет ли текст включать в себя дополнительные отступы, чтобы для глифов, которые проходят мимо указанного подъема. Он может быть установлен (как указано @ggc) методом TextView setIncludeFontPadding.

Если в includeepad установлено значение true (значение по умолчанию), текст позиционируется с базовым значением, заданным top-field метрик шрифта. В противном случае базовая линия текста берется из descent-field.

Таким образом, технически, это должно означать, что все, что нам нужно сделать, это выключить IncludeFontPadding, но, к сожалению, это приводит к следующему результату:

enter image description here

Причина этого заключается в том, что отчеты о шрифте -23.2 как восхождение, в то время как ограничивающий блок сообщает о верхнем значении -19. Я не знаю, является ли это «ошибкой» в шрифте, или если это должно быть так. К сожалению, FontMetrics не предоставляет никакого значения, которое соответствует значению 19, указанному в ограничивающей рамке, даже если вы попытаетесь каким-то образом включить сообщение о разрешении экрана 240dpi в сравнении с определением точек шрифта при 72dpi, поэтому нет «официального» способа почини это.

Но, конечно, доступная информация может быть использована для взлома решения. Есть два способа сделать это:

  • с IncludeFontPadding оставили в покое, то есть установить в истинной:

    double top = label.getPaint().getFontMetrics().top; 
    label.setPadding(0, (int) (top - bounds.top - 0.5), 0, 0); 
    

    т.е. вертикальное заполнение устанавливается для компенсации разницы в у-значения из текстовых границ и верхнего значения font-metric. Результат:

    enter image description here

  • с IncludeFontPadding установлен ложной:

    double ascent = label.getPaint().getFontMetrics().ascent; 
    label.setPadding(0, (int) (ascent - bounds.top - 0.5), 0, 0); 
    label.setIncludeFontPadding(false); 
    

    т.е. вертикальное заполнение устанавливается для компенсации разницы в у-значения доносил из текстовых границ и тому font-metric's ascent-value.Результат:

    enter image description here

Обратите внимание, что нет ничего магического о настройке IncludeFontPadding в ложной. Оба версия следует работа. Причина, по которой они дают разные результаты, - это несколько разные ошибки округления, когда значения с плавающей запятой метрики шрифта преобразуются в целые числа. Так получилось, что в этом конкретном случае он выглядит лучше с IncludeFontPadding, установленным в false, но для разных шрифтов или размеров шрифта это может быть другим. Вероятно, довольно легко настроить вычисление верхнего заполнения, чтобы получить те же самые точные ошибки округления, что и расчет, используемый BoringLayout. Я еще не сделал этого, так как вместо этого я скорее использую шрифт без ошибок, но я могу добавить его позже, если найду какое-то время. Тогда должно быть действительно неуместно, установлено ли значение IncludeFontPadding равным false или true.

+0

Это интересно. Не могли бы вы указать, что должно быть сделано в случае, если я хочу перейти из TextView и добавить эту функцию внутри него? –

+1

@androiddeveloper Я думаю, вам нужно будет переопределить метод setTextSize и сделать его быстрым getTextBounds на некоторых типичных высоких буквах (TIHdi и др.) В новом размере, чтобы получить максимальные верхние границы, а затем просто использовать это для установки дополнения соответственно. И убедитесь, что вы вызываете этот метод setTextSize хотя бы один раз (возможно, в новом конструкторе). –

+1

@androiddeveloper Кстати: если вы это сделаете, появятся письма (возможно, китайские иероглифы и т. Д.), Которые больше не будут соответствовать вашему TextView и будут обрезаны сверху (и, возможно, внизу)! Поэтому убедитесь, что вы тестируете результат со всеми буквами, которые вы планируете использовать в своем приложении, что может быть намного больше, чем вы изначально думали, если бы вы предоставляли поля редактирования для пользователей. Некоторые люди, я уверен, используют Umlauts или подобное в своих любимых именах персонажей, например. –

1

Если ваш TextView находится внутри другого макета, убедитесь, что у вас достаточно свободного пространства. Вы можете добавить отступ в нижней части родительского вида и посмотреть, есть ли у вас полный текст. Это сработало для меня!

Пример: у вас есть textView внутри FrameLayout, но FrameLayout слишком мал и сокращает ваш textView. Добавьте дополнение к вашему FrameLayout, чтобы узнать, работает ли он.

Редактировать: Изменить эту строку

 FrameLayout.LayoutParams layoutParams = 
      new FrameLayout.LayoutParams(300, 25, Gravity.LEFT + Gravity.TOP); 

для этой линии

FrameLayout.LayoutParams layoutParams = 
      new FrameLayout.LayoutParams(300, 50, Gravity.LEFT + Gravity.TOP); 

Это сделает окно больше и, таким же образом, пусть достаточно места для вашего текста, чтобы показать ,

ИЛИ добавить эту строку

 label.setIncludeFontPadding(false); 

Это приведет к удалению окружающих отступы шрифта и пусть текст будет видно. Но единственное, что не работает в вашем случае, это то, что он не будет показывать полностью буквы типа «g», которые идут под строкой ... Возможно, вам придется немного изменить размер окна или текста (например, 2-3), если вы действительно хотите, чтобы он работал.

+0

? Не уверен, что я следую вашему ответу ... Ярлык не закрыт каким-либо другим элементом, поскольку вы можете увидеть его полную высоту в 25 пикселей, которые я установил ... Просто он размещает текст внутри него таким образом, что он не помещается внутри этикетки. (см. пример кода) –

+0

Но вы пробовали хотя бы мой ответ? Я сказал вам, что это сработало для меня, просто попробовав это, не повредит. – ggc

+0

Конечно, я могу просто сделать ярлык больше, но это решает только этот конкретный случай, и насколько больше мне нужно сделать, это будет зависеть от точного шрифта, размера и т. Д. Поэтому, пока ваш ответ определенно устранит пример, который я опубликовал, я надеялся, что он действительно подгонит текст к тому размеру, который ему понадобится, избавившись от прокладки над ним. Ваше решение сделает синюю коробку больше, например, которая не является чем-то, что я хотел бы в дизайне. –