Cómo ajustar el tamaño de fuente del texto para que se ajuste a la vista de texto

178

¿Hay alguna manera en Android para ajustar el tamaño del texto en una vista de texto para que se ajuste al espacio que ocupa?

Por ejemplo, estoy usando a TableLayouty agrego varios TextViews a cada fila. Como no quiero que los TextViews envuelvan el texto, prefiero ver que reduce el tamaño de fuente del contenido.

¿Algunas ideas?

Lo he intentado measureText, pero como no sé el tamaño de la columna, parece problemático de usar. Este es el código donde quiero cambiar el tamaño de fuente a algo que se ajuste

TableRow row = new TableRow(this);   
for (int i=0; i < ColumnNames.length; i++) {    
    TextView textColumn = new TextView(this);      
    textColumn.setText(ColumnNames[i]);
    textColumn.setPadding(0, 0, 1, 0);
    textColumn.setTextColor(getResources().getColor(R.drawable.text_default));          
    row.addView(textColumn, new TableRow.LayoutParams()); 
} 
table.addView(row, new TableLayout.LayoutParams());  
rudas
fuente
Verifique mi solución basada en el código de dunni aquí stackoverflow.com/questions/5033012/… Nota: No lo implementé con un bucle. PD: gracias
Dunni

Respuestas:

129

La solución a continuación incorpora todas las sugerencias aquí. Comienza con lo publicado originalmente por Dunni. Utiliza una búsqueda binaria como la de gjpc, pero es un poco más legible. También incluye las correcciones de errores de gregm y una corrección de errores propia.

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialise();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialise();
    }

    private void initialise() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) 
    { 
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        float hi = 100;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            mTestPaint.setTextSize(size);
            if(mTestPaint.measureText(text) >= targetWidth) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
        }
    }

    //Attributes
    private Paint mTestPaint;
}
Speedplane
fuente
9
Gracias por combinar todos los comentarios. Veo que la solución solo tiene en cuenta el ancho . Mi problema es que el aficionado excede la altura.
AlikElzin-kilaka
3
Oh, parece funcionar parcialmente en tiempo de ejecución. En un dispositivo ot emulador, el texto se corta a la mitad. En el editor de diseño de Eclipse se ve bien. ¿Algunas ideas?
AlikElzin-kilaka
1
parece que esta publicación hará el truco (agregando atributos xml personalizados para hi / lo): stackoverflow.com/a/8090772/156611
Ed Sinek
77
¡Esta fue una gran solución! En caso de que alguien más sea nuevo en el desarrollo de Android y no sepa cómo implementar una vista extendida en XML, se ve así: <com.example.zengame1.FontFitTextView android:paddingTop="5dip" android:id="@+id/childs_name" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:layout_gravity="center" android:textSize="@dimen/text_size"/>
Casey Murray
2
¡Buen trabajo! Lo tengo funcionando también con botones. Para cuidar la altura de la vista de texto que acaba de configurarfloat hi = this.getHeight() - this.getPaddingBottom() - this.getPaddingTop();
AlexGuti
28

He escrito una clase que extiende TextView y hace esto. Solo usa measureText como sugieres. Básicamente tiene un tamaño de texto máximo y un tamaño de texto mínimo (que se puede cambiar) y solo recorre los tamaños entre ellos en decrementos de 1 hasta que encuentra el más grande que se ajuste. No es particularmente elegante, pero no conozco otra forma.

Aquí está el código:

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialise();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialise();
    }

    private void initialise() {
        testPaint = new Paint();
        testPaint.set(this.getPaint());
        //max size defaults to the intially specified text size unless it is too small
        maxTextSize = this.getTextSize();
        if (maxTextSize < 11) {
            maxTextSize = 20;
        }
        minTextSize = 10;
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) { 
        if (textWidth > 0) {
            int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
            float trySize = maxTextSize;

            testPaint.setTextSize(trySize);
            while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) {
                trySize -= 1;
                if (trySize <= minTextSize) {
                    trySize = minTextSize;
                    break;
                }
                testPaint.setTextSize(trySize);
            }

            this.setTextSize(trySize);
        }
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
        }
    }

    //Getters and Setters
    public float getMinTextSize() {
        return minTextSize;
    }

    public void setMinTextSize(int minTextSize) {
        this.minTextSize = minTextSize;
    }

    public float getMaxTextSize() {
        return maxTextSize;
    }

    public void setMaxTextSize(int minTextSize) {
        this.maxTextSize = minTextSize;
    }

    //Attributes
    private Paint testPaint;
    private float minTextSize;
    private float maxTextSize;

}
Dunni
fuente
11
Una búsqueda binaria generalmente sería más rápida que una búsqueda lineal.
Ted Hopp
creo que sería más razonable hacer refitText en "onLayout" en lugar de "onSizeChanged"
Jacky
¿Puede echar un vistazo a mi problema stackoverflow.com/questions/36265448/…
Muhammad
17

Este es un plano de velocidadFontFitTextView , pero solo disminuye el tamaño de la fuente si es necesario para que el texto se ajuste, y de lo contrario mantiene su tamaño de fuente. No aumenta el tamaño de fuente para adaptarse a la altura.

public class FontFitTextView extends TextView {

    // Attributes
    private Paint mTestPaint;
    private float defaultTextSize;

    public FontFitTextView(Context context) {
        super(context);
        initialize();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    private void initialize() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        defaultTextSize = getTextSize();
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) {

        if (textWidth <= 0 || text.isEmpty())
            return;

        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();

        // this is most likely a non-relevant call
        if( targetWidth<=2 )
            return;

        // text already fits with the xml-defined font size?
        mTestPaint.set(this.getPaint());
        mTestPaint.setTextSize(defaultTextSize);
        if(mTestPaint.measureText(text) <= targetWidth) {
            this.setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
            return;
        }

        // adjust text size using binary search for efficiency
        float hi = defaultTextSize;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be
        while (hi - lo > threshold) {
            float size = (hi + lo) / 2;
            mTestPaint.setTextSize(size);
            if(mTestPaint.measureText(text) >= targetWidth ) 
                hi = size; // too big
            else 
                lo = size; // too small

        }

        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            refitText(this.getText().toString(), w);
        }
    }

}

Aquí hay un ejemplo de cómo podría usarse en xml:

<com.your.package.activity.widget.FontFitTextView
    android:id="@+id/my_id"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="My Text"
    android:textSize="60sp" />

Esto mantendría el tamaño de la fuente a 60sp siempre que el texto se ajuste al ancho. Si el texto es más largo, disminuirá el tamaño de la fuente. En este caso, la TextViewaltura de s también cambiará debido a height=wrap_content.

Si encuentra algún error, no dude en editar.

sulai
fuente
¿Cómo lo utilizas? He agregado un ejemplo de cómo lo uso a mi respuesta. Lo he probado con varias versiones de Android, emuladores y ADT Graphical Layout Editor.
sulai
Esencialmente, no estaba especificando una altura específica. Su onMeasure permite que la vista tome el control si lo desea. this.setMeasuredDimension(parentWidth, height);
Logré
1
Gran entrada, he corregido el código :) Ahora funciona con match_parenty wrap_content.
sulai
¿Por qué prueba la cadena vacía escribiendo "" .equals (s) en lugar de simplemente s.isEmpty () ?? O s.length () == 0? No entiendo por qué a veces puedo ver estas pruebas de igualdad.
Zordid
1
@PratikButani gracias por señalar esto. el equivalente a text.isEmpty()sería "".equals(text).
sulai
14

Aquí está mi solución que funciona en emulador y teléfonos, pero no muy bien en el editor de diseño de Eclipse. Está inspirado en el código de Kilaka, pero el tamaño del texto no se obtiene de Paint, sino de la medición de la llamada TextView measure(0, 0).

La clase de Java:

public class FontFitTextView extends TextView
{
    private static final float THRESHOLD = 0.5f;

    private enum Mode { Width, Height, Both, None }

    private int minTextSize = 1;
    private int maxTextSize = 1000;

    private Mode mode = Mode.None;
    private boolean inComputation;
    private int widthMeasureSpec;
    private int heightMeasureSpec;

    public FontFitTextView(Context context) {
            super(context);
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
    }

    public FontFitTextView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);

            TypedArray tAttrs = context.obtainStyledAttributes(attrs, R.styleable.FontFitTextView, defStyle, 0);
            maxTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_maxTextSize, maxTextSize);
            minTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_minTextSize, minTextSize);
            tAttrs.recycle();
    }

    private void resizeText() {
            if (getWidth() <= 0 || getHeight() <= 0)
                    return;
            if(mode == Mode.None)
                    return;

            final int targetWidth = getWidth();
            final int targetHeight = getHeight();

            inComputation = true;
            float higherSize = maxTextSize;
            float lowerSize = minTextSize;
            float textSize = getTextSize();
            while(higherSize - lowerSize > THRESHOLD) {
                    textSize = (higherSize + lowerSize) / 2;
                    if (isTooBig(textSize, targetWidth, targetHeight)) {
                            higherSize = textSize; 
                    } else {
                            lowerSize = textSize;
                    }
            }
            setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
            measure(widthMeasureSpec, heightMeasureSpec);
            inComputation = false;
    }

    private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            measure(0, 0);
            if(mode == Mode.Both)
                    return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
            if(mode == Mode.Width)
                    return getMeasuredWidth() >= targetWidth;
            else
                    return getMeasuredHeight() >= targetHeight;
    }

    private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
                    return Mode.Both;
            if(widthMode == MeasureSpec.EXACTLY)
                    return Mode.Width;
            if(heightMode == MeasureSpec.EXACTLY)
                    return Mode.Height;
            return Mode.None;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if(!inComputation) {
                    this.widthMeasureSpec = widthMeasureSpec;
                    this.heightMeasureSpec = heightMeasureSpec;
                    mode = getMode(widthMeasureSpec, heightMeasureSpec);
                    resizeText();
            }
    }

    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
            resizeText();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (w != oldw || h != oldh)
                    resizeText();
    }

    public int getMinTextSize() {
            return minTextSize;
    }

    public void setMinTextSize(int minTextSize) {
            this.minTextSize = minTextSize;
            resizeText();
    }

    public int getMaxTextSize() {
            return maxTextSize;
    }

    public void setMaxTextSize(int maxTextSize) {
            this.maxTextSize = maxTextSize;
            resizeText();
    }
}

El archivo de atributos XML:

<resources>
    <declare-styleable name="FontFitTextView">
        <attr name="minTextSize" format="dimension" />
        <attr name="maxTextSize" format="dimension" />
    </declare-styleable>
</resources>

Consulte mi github para obtener la última versión de esta clase. Espero que pueda ser útil para alguien. Si se encuentra un error o si el código necesita explicación, no dude en abrir un problema en Github.

yDelouis
fuente
Funciona perfecto en mi Galaxy Nexus, pero tengo problemas en dispositivos con pantallas pequeñas.
Tony Ceralva
¿Cuál es su problema en dispositivos con pantallas pequeñas?
yDelouis
Lo sentimos, el problema no es con pantallas pequeñas, su vista no funciona en todos los dispositivos con Android 4.0, emulador incluido. Abrí un nuevo número en tu GitHub
Tony Ceralva
12

Muchas gracias a https://stackoverflow.com/users/234270/speedplane . ¡Gran respuesta!

Aquí hay una versión mejorada de su respuesta que también se ocupa de la altura y viene con un atributo maxFontSize para limitar el tamaño de la fuente (fue útil en mi caso, por lo que quería compartirlo):

package com.<your_package>;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;


public class FontFitTextView extends TextView
{

    private Paint mTestPaint;
    private float maxFontSize;
    private static final float MAX_FONT_SIZE_DEFAULT_VALUE = 20f;

    public FontFitTextView(Context context)
    {
        super(context);
        initialise(context, null);
    }

    public FontFitTextView(Context context, AttributeSet attributeSet)
    {
        super(context, attributeSet);
        initialise(context, attributeSet);
    }

    public FontFitTextView(Context context, AttributeSet attributeSet, int defStyle)
    {
        super(context, attributeSet, defStyle);
        initialise(context, attributeSet);
    }

    private void initialise(Context context, AttributeSet attributeSet)
    {
        if(attributeSet!=null)
        {
            TypedArray styledAttributes = context.obtainStyledAttributes(attributeSet, R.styleable.FontFitTextView);
            maxFontSize = styledAttributes.getDimension(R.styleable.FontFitTextView_maxFontSize, MAX_FONT_SIZE_DEFAULT_VALUE);
            styledAttributes.recycle();
        }
        else
        {
            maxFontSize = MAX_FONT_SIZE_DEFAULT_VALUE;
        }

        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth, int textHeight)
    {
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
        float hi = maxFontSize;
        float lo = 2;
//      final float threshold = 0.5f; // How close we have to be
        final float threshold = 1f; // How close we have to be

        mTestPaint.set(this.getPaint());

        Rect bounds = new Rect();

        while ((hi - lo) > threshold)
        {
            float size = (hi + lo) / 2;
            mTestPaint.setTextSize(size);

            mTestPaint.getTextBounds(text, 0, text.length(), bounds);

            if (bounds.width() >= targetWidth || bounds.height() >= targetHeight)
                hi = size; // too big
            else
                lo = size; // too small

//          if (mTestPaint.measureText(text) >= targetWidth)
//              hi = size; // too big
//          else
//              lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth, height);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after)
    {
        refitText(text.toString(), this.getWidth(), this.getHeight());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        if (w != oldw)
        {
            refitText(this.getText().toString(), w, h);
        }
    }
}

Archivo /res/values/attr.xml correspondiente:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="FontFitTextView">
        <attr name="maxFontSize" format="dimension" />
    </declare-styleable>

</resources>

Ejemplo:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:res-auto="http://schemas.android.com/apk/res-auto"
    android:id="@+id/home_Layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background"
    tools:ignore="ContentDescription" >
...

 <com.<your_package>.FontFitTextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:singleLine="true"
                    android:text="Sample Text"
                    android:textSize="28sp"
                    res-auto:maxFontSize="35sp"/>

...
</RelativeLayout>

Para usar el nuevo maxFontSizeatributo, no olvide agregar xmlns:res-auto="http://schemas.android.com/apk/res-auto"como se muestra en el ejemplo.

Pascal
fuente
Lo sentimos, pero aún no funciona en todos los casos. También creo que no maneja líneas múltiples correctamente.
Desarrollador de Android
Oh .. ¿Podrías darme un caso para el que no funciona? (No se admite la línea múltiple, tiene razón)
Pascal
Muchos casos. He creado un probador aleatorio solo para demostrar que es correcto. Aquí hay una muestra: ancho de la vista: 317 px, altura: 137 px, texto: "q7Lr". Lo que veo es esto: tinypic.com/view.php?pic=2dv5yf9&s=6 . Aquí está el proyecto de muestra que hice: mega.co.nz/… . Creo que tal vista debería manejar líneas múltiples, admitir cualquier tamaño de fuente, manejar la altura y no solo el ancho, ... Lamentablemente, ninguna de las muestras que he encontrado ha funcionado bien.
Desarrollador de Android
ok, definitivamente no es una solución completa. lo siento. No hay tiempo para mejorarlo por el momento. Si puede mejorarlo, no dude en publicar su solución.
Pascal
1
He creado un nuevo hilo que muestra mis pruebas, con la esperanza de que alguien pueda hacer una buena solución: stackoverflow.com/questions/16017165/…
desarrollador de Android
7

Tuve el mismo problema y escribí una clase que parece funcionar para mí. Básicamente, usé un diseño estático para dibujar el texto en un lienzo separado y volver a medir hasta que encuentre un tamaño de fuente que se ajuste. Puede ver la clase publicada en el tema a continuación. Espero que ayude.

Texto de escala automática Ver texto para ajustarlo dentro de los límites

Persecución
fuente
6

Ahora puede hacer esto sin una biblioteca de terceros o un widget. Está integrado en TextView en el nivel 26 de API. Agréguelo android:autoSizeTextType="uniform"a su TextViewy configure la altura. Eso es todo.

https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:autoSizeTextType="uniform" />

También puede usar TextViewCompatpor compatibilidad.

arsent
fuente
5

Ligera modificación a onMeasure:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    refitText(this.getText().toString(), parentWidth);
    this.setMeasuredDimension(parentWidth, parentHeight);
}

Y búsqueda binaria en refitText:

private void refitText(String text, int textWidth) 
{ 
    if (textWidth > 0) 
    {
        int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();         
        int trySize = (int)maxTextSize;
        int increment = ~( trySize - (int)minTextSize ) / 2;

        testPaint.setTextSize(trySize);
        while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) 
        {
            trySize += increment;
            increment = ( increment == 0 ) ? -1 : ~increment / 2;
            if (trySize <= minTextSize) 
            {
                trySize = (int)minTextSize;
                break;
            }
            testPaint.setTextSize(trySize);
        }

        this.setTextSize( TypedValue.COMPLEX_UNIT_PX, trySize);
    }
}
gjpc
fuente
3
En lugar de una búsqueda binaria, una escala simple funciona bastante bien: trySize *= availableWidth / measured_width(luego se fija a minTextSize).
Ted Hopp
5

Encontré lo siguiente que funciona muy bien para mí. No se repite y representa tanto la altura como el ancho. Tenga en cuenta que es importante especificar la unidad PX al llamar a setTextSize en la vista. Gracias al consejo en una publicación anterior por esto!

Paint paint = adjustTextSize(getPaint(), numChars, maxWidth, maxHeight);
setTextSize(TypedValue.COMPLEX_UNIT_PX,paint.getTextSize());

Aquí está la rutina que uso, pasando el getPaint () desde la vista. Se utiliza una cadena de 10 caracteres con un carácter 'ancho' para estimar el ancho independientemente de la cadena real.

private static final String text10="OOOOOOOOOO";
public static Paint adjustTextSize(Paint paint, int numCharacters, int widthPixels, int heightPixels) {
    float width = paint.measureText(text10)*numCharacters/text10.length();
    float newSize = (int)((widthPixels/width)*paint.getTextSize());
    paint.setTextSize(newSize);

    // remeasure with font size near our desired result
    width = paint.measureText(text10)*numCharacters/text10.length();
    newSize = (int)((widthPixels/width)*paint.getTextSize());
    paint.setTextSize(newSize);

    // Check height constraints
    FontMetricsInt metrics = paint.getFontMetricsInt();
    float textHeight = metrics.descent-metrics.ascent;
    if (textHeight > heightPixels) {
        newSize = (int)(newSize * (heightPixels/textHeight));
        paint.setTextSize(newSize);
    }

    return paint;
}
Glenn
fuente
¿Cómo lo incorporas en un diseño xml?
AlikElzin-kilaka
Este código es útil para colocar texto dentro de una vista existente en un área de tamaño restringido o puede crear su propia clase derivada desde TextView y anular onMeasure como se muestra en otras publicaciones. Por sí solo no se puede usar en un diseño.
Glenn
Asegúrese de verificar las dimensiones de la vista después de que se haya dibujado. Hacer esto durante onCreate () es demasiado temprano. Utilicé ViewTreeObserver para asegurarme de que las mediciones se tomaron en el momento adecuado.
Scott Biggs
4

Funciona con modificaciones

Debe establecer el tamaño de la vista de texto de esta manera porque, de lo contrario, setTextSize supone que el valor está en unidades SP:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

Y necesitabas agregar explícitamente este código.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    refitText(this.getText().toString(), parentWidth);
}
Gregm
fuente
4

Úselo app:autoSizeTextType="uniform"para la compatibilidad con versiones anteriores porque android:autoSizeTextType="uniform"solo funciona en API Nivel 26 y superior.

Suraj Vaishnav
fuente
2

Utilicé una variación de la solución Dunni anterior, pero ese código en particular no funcionó para mí. En particular, cuando se intenta utilizar el conjunto de objetos Paint para tener los rasgos del objeto Paint de la vista, y luego llamar a measureText (), no devuelve el mismo valor que llamar directamente al objeto Paint de la vista. Quizás haya algunas diferencias en la forma en que se configuran mis puntos de vista que hacen que el comportamiento sea diferente.

Mi solución fue usar directamente la pintura de la vista, aunque podría haber algunas penalizaciones de rendimiento al cambiar el tamaño de fuente de la vista varias veces.

ThomasW
fuente
2

He estado trabajando para mejorar la excelente solución de speedplane, y se me ocurrió esto. Gestiona la altura, incluida la configuración del margen de manera que el texto debe centrarse correctamente verticalmente.

Utiliza la misma función para obtener el ancho, ya que parece funcionar mejor, pero utiliza una función diferente para obtener la altura, ya que la altura no se proporciona en ninguna parte. Hay algunas correcciones que deben hacerse, pero descubrí una forma de hacerlo, a la vez que parecía agradable a la vista.

import android.content.Context;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialize();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    private void initialize() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());

        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth,int textHeight) 
    { 
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
        float hi = Math.min(targetHeight,100);
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        Rect bounds = new Rect();

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            mTestPaint.setTextSize(size);
            mTestPaint.getTextBounds(text, 0, text.length(), bounds);
            if((mTestPaint.measureText(text)) >= targetWidth || (1+(2*(size+(float)bounds.top)-bounds.bottom)) >=targetHeight) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX,(float) lo);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth,height);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth(),this.getHeight());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {

        if (w != oldw) {
            refitText(this.getText().toString(), w,h);
        }
    }

    //Attributes
    private Paint mTestPaint;
}
PearsonArtPhoto
fuente
1

Tuve este dolor en mis proyectos por tanto tiempo hasta que encontré esta biblioteca:

compile 'me.grantland:autofittextview:0.2.+'

Solo necesita agregar el xml según sus necesidades y listo. Por ejemplo:

<me.grantland.widget.AutofitTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:maxLines="2"
android:textSize="40sp"
autofit:minTextSize="16sp"
/>
Giedrius Šlikas
fuente
0

Inspirado por los carteles anteriores, quería compartir mi solución. Funciona con un factor de escala que se aplica al tamaño de fuente anterior para que se ajuste al espacio disponible. Además de evitar un comportamiento inesperado del método TextViews onDraw, simplemente dibuja el texto por sí solo.

public class FontFitTextView extends TextView {

    // How much of the available space should be used in percent.
    private static final float MARGINHEIGHT = 0.8f;
    private static final float MARGINWIDTH = 0.8f;

    private Paint paint;
    private int viewWidth;
    private int viewHeight;
    private float textHeight;
    private float textWidth;

    public FontFitTextView(Context c) {
        this(c, null);
    }

    public FontFitTextView(Context c, AttributeSet attrs) {
        super(c, attrs);
        initComponent();
    }

    // Default constructor override
    public FontFitTextView(Context c, AttributeSet attrs, int defStyle) {
        super(c, attrs, defStyle);
        initComponent();
    }

    private void initComponent() {
        paint = new Paint();
        paint.setTextSize(30);
        paint.setTextAlign(Align.CENTER);
        paint.setAntiAlias(true);
    }

    public void setFontColor(int c) {
        paint.setColor(c);
    }

    private void calcTextSize(String s, Canvas c) {

        float availableHeight = viewHeight;
        float availableWidth = viewWidth;

        // This value scales the old font up or down to match the available
        // space.
        float scale = 1.0f;

        // Rectangle for measuring the text dimensions
        Rect rect = new Rect();
        float oldFontSize = paint.getTextSize();

        // Calculate the space used with old font size
        paint.getTextBounds(s, 0, s.length(), rect);
        textWidth = rect.width();
        textHeight = rect.height();

        // find scale-value to fit the text horizontally
        float scaleWidth = 1f;
        if (textWidth > 0.0f) {
            scaleWidth = (availableWidth) / textWidth * MARGINWIDTH;
        }

        // find scale-value to fit the text vertically
        float scaleHeight = 1f;
        if (textHeight > 0.0f) {
            scaleHeight = (availableHeight) / textHeight * MARGINHEIGHT;
        }

        // We are always limited by the smaller one
        if (scaleWidth < scaleHeight) {
            scale = scaleWidth;
        } else {
            scale = scaleHeight;
        }

        // We apply the scale to the old font size to make it bigger or smaller
        float newFontSize = (oldFontSize * scale);
        paint.setTextSize(newFontSize);
    }

    /**
     * Calculates the origin on the Y-Axis (width) for the text in this view.
     * 
     * @return
     */
    private float calcStartDrawingPosX() {
        float left = getMeasuredWidth();
        float centerY = left - (viewWidth / 2);
        return centerY;
    }

    /**
     * Calculates the origin on the Y-Axis (height) for the text in this view.
     * 
     * @return
     */
    private float calcStartDrawingPosY() {
        float bottom = getMeasuredHeight();
        // The paint only centers horizontally, origin on the Y-Axis stays at
        // the bottom, thus we have to lift the origin additionally by the
        // height of the font.
        float centerX = bottom - (viewHeight / 2) + (textHeight / 2);
        return centerX;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        String text = getText().toString();
        if (text.length() > 0) {
            calcTextSize(text, canvas);
            canvas.drawText(text, calcStartDrawingPosX(),
                    calcStartDrawingPosY(), paint);
        }
    };

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        viewWidth = w;
        viewHeight = h;
        super.onSizeChanged(w, h, oldw, oldh);
    }
}
noSinn
fuente
0
/* get your context */
Context c = getActivity().getApplicationContext();

LinearLayout l = new LinearLayout(c);
l.setOrientation(LinearLayout.VERTICAL);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0);

l.setLayoutParams(params);
l.setBackgroundResource(R.drawable.border);

TextView tv=new TextView(c);
tv.setText(" your text here");

/* set typeface if needed */
Typeface tf = Typeface.createFromAsset(c.getAssets(),"fonts/VERDANA.TTF");  
tv.setTypeface(tf);

// LayoutParams lp = new LayoutParams();

tv.setTextColor(Color.parseColor("#282828"));

tv.setGravity(Gravity.CENTER | Gravity.BOTTOM);
//  tv.setLayoutParams(lp);

tv.setTextSize(20);
l.addView(tv);

return l;
Dhwanik Gandhi
fuente
1
Simplemente leyendo su código, no creo que esto funcione. Recuerde, la pregunta era acerca de ajustar automáticamente el tamaño de fuente de la vista de texto para que se ajuste a la vista. Establece un tamaño de fuente fijo.
sulai
0

Esta debería ser una solución simple:

public void correctWidth(TextView textView, int desiredWidth)
{
    Paint paint = new Paint();
    Rect bounds = new Rect();

    paint.setTypeface(textView.getTypeface());
    float textSize = textView.getTextSize();
    paint.setTextSize(textSize);
    String text = textView.getText().toString();
    paint.getTextBounds(text, 0, text.length(), bounds);

    while (bounds.width() > desiredWidth)
    {
        textSize--;
        paint.setTextSize(textSize);
        paint.getTextBounds(text, 0, text.length(), bounds);
    }

    textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
Hamzeh Soboh
fuente
0

Extienda TextView y anule onDraw con el siguiente código. Mantendrá la relación de aspecto del texto pero lo ajustará para llenar el espacio. Puede modificar fácilmente el código para estirarlo si es necesario.

  @Override
  protected void onDraw(@NonNull Canvas canvas) {
    TextPaint textPaint = getPaint();
    textPaint.setColor(getCurrentTextColor());
    textPaint.setTextAlign(Paint.Align.CENTER);
    textPaint.drawableState = getDrawableState();

    String text = getText().toString();
    float desiredWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2;
    float desiredHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2;
    float textSize = textPaint.getTextSize();

    for (int i = 0; i < 10; i++) {
      textPaint.getTextBounds(text, 0, text.length(), rect);
      float width = rect.width();
      float height = rect.height();

      float deltaWidth = width - desiredWidth;
      float deltaHeight = height - desiredHeight;

      boolean fitsWidth = deltaWidth <= 0;
      boolean fitsHeight = deltaHeight <= 0;

      if ((fitsWidth && Math.abs(deltaHeight) < 1.0)
          || (fitsHeight && Math.abs(deltaWidth) < 1.0)) {
        // close enough
        break;
      }

      float adjustX = desiredWidth / width;
      float adjustY = desiredHeight / height;

      textSize = textSize * (adjustY < adjustX ? adjustY : adjustX);

      // adjust text size
      textPaint.setTextSize(textSize);
    }
    float x = desiredWidth / 2f;
    float y = desiredHeight / 2f - rect.top - rect.height() / 2f;
    canvas.drawText(text, x, y, textPaint);
  }
Greg Bacchus
fuente
0

Escribí una clase de ayuda breve que hace que una vista de texto se ajuste a un cierto ancho y agrega elipsis "..." al final si no se puede lograr el tamaño de texto mínimo.

Tenga en cuenta que solo hace que el texto sea más pequeño hasta que encaje o hasta que se alcance el tamaño mínimo de texto. Para probar con tamaños grandes, configure el tamaño del texto en un número grande antes de llamar al método de ayuda.

Se necesitan píxeles, por lo que si está utilizando valores de dimen, puede llamarlo así:


float minTextSizePx = getResources().getDimensionPixelSize(R.dimen.min_text_size);
float maxTextWidthPx = getResources().getDimensionPixelSize(R.dimen.max_text_width);
WidgetUtils.fitText(textView, text, minTextSizePx, maxTextWidthPx);

Esta es la clase que uso:


public class WidgetUtils {

    public static void fitText(TextView textView, String text, float minTextSizePx, float maxWidthPx) {
        textView.setEllipsize(null);
        int size = (int)textView.getTextSize();
        while (true) {
            Rect bounds = new Rect();
            Paint textPaint = textView.getPaint();
            textPaint.getTextBounds(text, 0, text.length(), bounds);
            if(bounds.width() < maxWidthPx){
                break;
            }
            if (size <= minTextSizePx) {
                textView.setEllipsize(TextUtils.TruncateAt.END);
                break;
            }
            size -= 1;
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
        }
    }
}
Björn Kechel
fuente
¿Esto soporta múltiples líneas de texto?
Roymunson
0

Si se establece una transformación como allCaps, el enfoque de speedplane es defectuoso. Lo arreglé, dando como resultado el siguiente código (lo siento, mi reputación no me permite agregar esto como un comentario a la solución de speedplane):

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialise();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialise();
    }

    private void initialise() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) 
    { 
        if (getTransformationMethod() != null) {
            text = getTransformationMethod().getTransformation(text, this).toString();
        }

        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        float hi = 100;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            if(mTestPaint.measureText(text) >= targetWidth) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
      }
    }

    //Attributes
    private Paint mTestPaint;
}
dpoetzsch
fuente
0

No sé si esta es la forma correcta o no, pero está funcionando ... tome su vista y verifique OnGlobalLayoutListener () y obtenga textview linecount y luego configure textSize.

 yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (textView.getLineCount()>=3) {
                textView.setTextSize(20);
            }else{
                //add somthing
              }
        }
    });

Su código de línea muy simple.

Mathan Chinna
fuente