TextView de ajuste automático para Android


231

Antecedentes

Muchas veces necesitamos ajustar automáticamente la fuente de TextView a los límites establecidos.

El problema

Lamentablemente, a pesar de que hay muchos hilos y publicaciones (y soluciones sugeridas) que hablan sobre este problema (ejemplo aquí , aquí y aquí ), ninguno de ellos funciona realmente bien.

Por eso, he decidido probar cada uno de ellos hasta encontrar el verdadero negocio.

Creo que los requisitos de tal textView deberían ser:

  1. Debe permitir el uso de cualquier fuente, tipo de letra, estilo y conjunto de caracteres.

  2. Debe manejar tanto el ancho como la altura

  3. Sin truncamiento a menos que el texto no se ajuste debido a la limitación que le hemos dado (ejemplo: texto demasiado largo, tamaño disponible demasiado pequeño). Sin embargo, podríamos solicitar una barra de desplazamiento horizontal / vertical si lo deseamos, solo para esos casos.

  4. Debe permitir varias líneas o una sola línea. En caso de líneas múltiples, permita líneas máximas y mínimas.

  5. No debe ser lento en el cálculo. ¿Usando un bucle para encontrar el mejor tamaño? Al menos optimícelo y no incremente su muestreo en 1 cada vez.

  6. En el caso de líneas múltiples, debería permitir preferir cambiar el tamaño o usar más líneas, y / o permitir elegir las líneas nosotros mismos usando el carácter "\ n".

Lo que he intentado

He probado muchos ejemplos (incluidos los de los enlaces, sobre los que he escrito), y también he tratado de modificarlos para manejar los casos, he hablado, pero ninguno realmente funciona.

Hice un proyecto de muestra que me permite ver visualmente si TextView se ajusta automáticamente.

Actualmente, mi proyecto de muestra solo aleatoriza el texto (el alfabeto inglés más los dígitos) y el tamaño de textView, y lo dejo con una sola línea, pero incluso esto no funciona bien en ninguna de las muestras que he probado.

Aquí está el código (también disponible aquí ):

Expediente res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" tools:context=".MainActivity">
  <Button android:id="@+id/button1" android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true" android:text="Button" />
  <FrameLayout android:layout_width="match_parent"
    android:layout_height="wrap_content" android:layout_above="@+id/button1"
    android:layout_alignParentLeft="true" android:background="#ffff0000"
    android:layout_alignParentRight="true" android:id="@+id/container"
    android:layout_alignParentTop="true" />

</RelativeLayout>

Expediente src/.../MainActivity.java

public class MainActivity extends Activity
  {
  private final Random        _random            =new Random();
  private static final String ALLOWED_CHARACTERS ="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";

  @Override
  protected void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ViewGroup container=(ViewGroup)findViewById(R.id.container);
    findViewById(R.id.button1).setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(final View v)
          {
          container.removeAllViews();
          final int maxWidth=container.getWidth();
          final int maxHeight=container.getHeight();
          final FontFitTextView fontFitTextView=new FontFitTextView(MainActivity.this);
          final int width=_random.nextInt(maxWidth)+1;
          final int height=_random.nextInt(maxHeight)+1;
          fontFitTextView.setLayoutParams(new LayoutParams(width,height));
          fontFitTextView.setSingleLine();
          fontFitTextView.setBackgroundColor(0xff00ff00);
          final String text=getRandomText();
          fontFitTextView.setText(text);
          container.addView(fontFitTextView);
          Log.d("DEBUG","width:"+width+" height:"+height+" text:"+text);
          }
      });
    }

  private String getRandomText()
    {
    final int textLength=_random.nextInt(20)+1;
    final StringBuilder builder=new StringBuilder();
    for(int i=0;i<textLength;++i)
      builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
    return builder.toString();
    }
  }

La pregunta

¿Alguien sabe de una solución para este problema común que realmente funcione?

Incluso una solución que tiene muchas menos características de las que he escrito, por ejemplo, una que solo tiene un número constante de líneas de texto, y ajusta su fuente de acuerdo con su tamaño, pero nunca tiene fallas extrañas y que el texto también grande / pequeño en comparación con su espacio disponible.


Proyecto GitHub

Dado que este es un TextView tan importante, he decidido publicar una biblioteca, para que todos puedan usarla fácilmente, y contribuir aquí .



@Thrakbad es uno de los enlaces que he mencionado. Tampoco pasa la prueba.
Desarrollador de Android

Ah, lo siento, me perdí el último ejemplo de alguna manera
Thrakbad

Sí, créanme, probé muchas muestras y también intenté modificarlas para solucionar los problemas que encontré, pero nunca lo logré. Si encuentra algo que cree que podría funcionar, pruébelo. He publicado un código de muestra solo para esto.
Desarrollador de Android

1
@rule esta es una de las publicaciones que ya leí, y probé todos los ejemplos de código. También creo que has publicado dos veces.
Desarrollador de Android

Respuestas:


147

Gracias a la simple corrección de MartinH aquí , este código también se encarga de android:drawableLeft, android:drawableRight, android:drawableTopy android:drawableBottomlas etiquetas.


Mi respuesta aquí debería hacerte feliz Auto Scale TextView Text para encajar dentro de los límites

He modificado tu caso de prueba:

@Override
protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ViewGroup container = (ViewGroup) findViewById(R.id.container);
    findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(final View v) {
            container.removeAllViews();
            final int maxWidth = container.getWidth();
            final int maxHeight = container.getHeight();
            final AutoResizeTextView fontFitTextView = new AutoResizeTextView(MainActivity.this);
            final int width = _random.nextInt(maxWidth) + 1;
            final int height = _random.nextInt(maxHeight) + 1;
            fontFitTextView.setLayoutParams(new FrameLayout.LayoutParams(
                    width, height));
            int maxLines = _random.nextInt(4) + 1;
            fontFitTextView.setMaxLines(maxLines);
            fontFitTextView.setTextSize(500);// max size
            fontFitTextView.enableSizeCache(false);
            fontFitTextView.setBackgroundColor(0xff00ff00);
            final String text = getRandomText();
            fontFitTextView.setText(text);
            container.addView(fontFitTextView);
            Log.d("DEBUG", "width:" + width + " height:" + height
                    + " text:" + text + " maxLines:" + maxLines);
        }
    });
}

Estoy publicando código aquí a pedido del desarrollador de Android :

Efecto final:

Ingrese la descripción de la imagen aquí

Archivo de diseño de muestra:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp" >

<com.vj.widgets.AutoResizeTextView
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:ellipsize="none"
    android:maxLines="2"
    android:text="Auto Resized Text, max 2 lines"
    android:textSize="100sp" /> <!-- maximum size -->

<com.vj.widgets.AutoResizeTextView
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:ellipsize="none"
    android:gravity="center"
    android:maxLines="1"
    android:text="Auto Resized Text, max 1 line"
    android:textSize="100sp" /> <!-- maximum size -->

<com.vj.widgets.AutoResizeTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Auto Resized Text"
    android:textSize="500sp" /> <!-- maximum size -->

</LinearLayout>

Y el código Java:

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Build;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;

public class AutoResizeTextView extends TextView {
    private interface SizeTester {
        /**
         *
         * @param suggestedSize
         *            Size of text to be tested
         * @param availableSpace
         *            available space in which text must fit
         * @return an integer < 0 if after applying {@code suggestedSize} to
         *         text, it takes less space than {@code availableSpace}, > 0
         *         otherwise
         */
        public int onTestSize(int suggestedSize, RectF availableSpace);
    }

    private RectF mTextRect = new RectF();

    private RectF mAvailableSpaceRect;

    private SparseIntArray mTextCachedSizes;

    private TextPaint mPaint;

    private float mMaxTextSize;

    private float mSpacingMult = 1.0f;

    private float mSpacingAdd = 0.0f;

    private float mMinTextSize = 20;

    private int mWidthLimit;

    private static final int NO_LINE_LIMIT = -1;
    private int mMaxLines;

    private boolean mEnableSizeCache = true;
    private boolean mInitializedDimens;

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

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

    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize();
    }

    private void initialize() {
        mPaint = new TextPaint(getPaint());
        mMaxTextSize = getTextSize();
        mAvailableSpaceRect = new RectF();
        mTextCachedSizes = new SparseIntArray();
        if (mMaxLines == 0) {
            // no value was assigned during construction
            mMaxLines = NO_LINE_LIMIT;
        }
    }

    @Override
    public void setTextSize(float size) {
        mMaxTextSize = size;
        mTextCachedSizes.clear();
        adjustTextSize();
    }

    @Override
    public void setMaxLines(int maxlines) {
        super.setMaxLines(maxlines);
        mMaxLines = maxlines;
        adjustTextSize();
    }

    public int getMaxLines() {
        return mMaxLines;
    }

    @Override
    public void setSingleLine() {
        super.setSingleLine();
        mMaxLines = 1;
        adjustTextSize();
    }

    @Override
    public void setSingleLine(boolean singleLine) {
        super.setSingleLine(singleLine);
        if (singleLine) {
            mMaxLines = 1;
        } else {
            mMaxLines = NO_LINE_LIMIT;
        }
        adjustTextSize();
    }

    @Override
    public void setLines(int lines) {
        super.setLines(lines);
        mMaxLines = lines;
        adjustTextSize();
    }

    @Override
    public void setTextSize(int unit, float size) {
        Context c = getContext();
        Resources r;

        if (c == null)
            r = Resources.getSystem();
        else
            r = c.getResources();
        mMaxTextSize = TypedValue.applyDimension(unit, size,
                r.getDisplayMetrics());
        mTextCachedSizes.clear();
        adjustTextSize();
    }

    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the lower text size limit and invalidate the view
     *
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        adjustTextSize();
    }

    private void adjustTextSize() {
        if (!mInitializedDimens) {
            return;
        }
        int startSize = (int) mMinTextSize;
        int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
                - getCompoundPaddingTop();
        mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
                - getCompoundPaddingRight();
        mAvailableSpaceRect.right = mWidthLimit;
        mAvailableSpaceRect.bottom = heightLimit;
        super.setTextSize(
                TypedValue.COMPLEX_UNIT_PX,
                efficientTextSizeSearch(startSize, (int) mMaxTextSize,
                        mSizeTester, mAvailableSpaceRect));
    }

    private final SizeTester mSizeTester = new SizeTester() {
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        @Override
        public int onTestSize(int suggestedSize, RectF availableSPace) {
            mPaint.setTextSize(suggestedSize);
            String text = getText().toString();
            boolean singleline = getMaxLines() == 1;
            if (singleline) {
                mTextRect.bottom = mPaint.getFontSpacing();
                mTextRect.right = mPaint.measureText(text);
            } else {
                StaticLayout layout = new StaticLayout(text, mPaint,
                        mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
                        mSpacingAdd, true);

                // Return early if we have more lines
                if (getMaxLines() != NO_LINE_LIMIT
                        && layout.getLineCount() > getMaxLines()) {
                    return 1;
                }
                mTextRect.bottom = layout.getHeight();
                int maxWidth = -1;
                for (int i = 0; i < layout.getLineCount(); i++) {
                    if (maxWidth < layout.getLineWidth(i)) {
                        maxWidth = (int) layout.getLineWidth(i);
                    }
                }
                mTextRect.right = maxWidth;
            }

            mTextRect.offsetTo(0, 0);
            if (availableSPace.contains(mTextRect)) {

                // May be too small, don't worry we will find the best match
                return -1;
            } else {
                // too big
                return 1;
            }
        }
    };

    /**
     * Enables or disables size caching, enabling it will improve performance
     * where you are animating a value inside TextView. This stores the font
     * size against getText().length() Be careful though while enabling it as 0
     * takes more space than 1 on some fonts and so on.
     *
     * @param enable
     *            Enable font size caching
     */
    public void enableSizeCache(boolean enable) {
        mEnableSizeCache = enable;
        mTextCachedSizes.clear();
        adjustTextSize(getText().toString());
    }

    private int efficientTextSizeSearch(int start, int end,
            SizeTester sizeTester, RectF availableSpace) {
        if (!mEnableSizeCache) {
            return binarySearch(start, end, sizeTester, availableSpace);
        }
        int key = getText().toString().length();
        int size = mTextCachedSizes.get(key);
        if (size != 0) {
            return size;
        }
        size = binarySearch(start, end, sizeTester, availableSpace);
        mTextCachedSizes.put(key, size);
        return size;
    }

    private static int binarySearch(int start, int end, SizeTester sizeTester,
            RectF availableSpace) {
        int lastBest = start;
        int lo = start;
        int hi = end - 1;
        int mid = 0;
        while (lo <= hi) {
            mid = (lo + hi) >>> 1;
            int midValCmp = sizeTester.onTestSize(mid, availableSpace);
            if (midValCmp < 0) {
                lastBest = lo;
                lo = mid + 1;
            } else if (midValCmp > 0) {
                hi = mid - 1;
                lastBest = hi;
            } else {
                return mid;
            }
        }
        // Make sure to return the last best.
        // This is what should always be returned.
        return lastBest;

    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        super.onTextChanged(text, start, before, after);
        adjustTextSize();
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldwidth,
            int oldheight) {
        mInitializedDimens = true;
        mTextCachedSizes.clear();
        super.onSizeChanged(width, height, oldwidth, oldheight);
        if (width != oldwidth || height != oldheight) {
            adjustTextSize();
        }
    }
}

Advertencia:

Sin embargo, tenga cuidado con este error resuelto en Android 3.1 (Honeycomb).


ok, probé el código y parece estar bien. sin embargo, usó getMaxLines (), que es para API 16, pero puede almacenar maxLines y obtenerlo anulando setMaxLines y almacenar su valor. Lo he cambiado y ahora funciona bien. Además, dado que a veces el texto puede ser demasiado largo para el tamaño más pequeño, he intentado usar setEllipsize, ¡y eso también funcionó! Creo que tenemos un ganador. publique su código aquí para que pueda marcarlo como el correcto.
desarrollador de Android

no eliminaste getMaxLines () y usaste el tuyo propio. seguirá funcionando solo desde API16 ... cámbielo para que todos puedan usarlo. sugiero anular setMaxLines para almacenar el parámetro en un campo y luego acceder al campo en lugar de usar getMaxLines.
desarrollador de Android

compruebe ahora, soporte agregado para líneas máximas.
M-WaJeEh

se ve bien. debe agregar "@TargetApi (Build.VERSION_CODES.JELLY_BEAN)" al método onTestSize () para que Lint no dé un error / advertencia al respecto, e inicialícelo en el CTOR. establecer la búsqueda binaria en estático también es una buena cosa, y podría hacer la inicialización en el CTOR más grande y llamarlo desde los otros CTOR. Sin embargo, es realmente la mejor respuesta y te mereces una V. Esta es finalmente una buena respuesta para esta vieja pregunta. ¡buen trabajo!
desarrollador de Android

@ M-WaJeEh revisa la nueva publicación en este hilo. un usuario llamado "MartinH" dice que tiene una solución para su código.
desarrollador de Android

14

He modificado un poco la respuesta de M-WaJeEh para tener en cuenta los elementos dibujables compuestos en los lados.

Los getCompoundPaddingXXXX()métodos vuelven padding of the view + drawable space. Ver por ejemplo: TextView.getCompoundPaddingLeft ()

Problema: esto corrige la medida del ancho y la altura del espacio TextView disponible para el texto. Si no tomamos en cuenta el tamaño del dibujo, se ignora y el texto terminará superponiéndose al dibujo.


Segmento actualizado adjustTextSize(String):

private void adjustTextSize(final String text) {
  if (!mInitialized) {
    return;
  }
  int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
  mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();

  mAvailableSpaceRect.right = mWidthLimit;
  mAvailableSpaceRect.bottom = heightLimit;

  int maxTextSplits = text.split(" ").length;
  AutoResizeTextView.super.setMaxLines(Math.min(maxTextSplits, mMaxLines));

  super.setTextSize(
      TypedValue.COMPLEX_UNIT_PX,
      binarySearch((int) mMinTextSize, (int) mMaxTextSize,
                   mSizeTester, mAvailableSpaceRect));
}

has actualizado la fuente de M-WaJeEh. ¿Por qué lo pones como una nueva respuesta? Sé que tienes buena voluntad, pero ¿es bueno decirlo como respuesta? No estoy seguro de que pueda ver tu respuesta ...
desarrollador de Android

1
Me acabo de registrar para poder proporcionar esa actualización. Mi cuenta de reputación es solo 1, lo que me impide comentar publicaciones que no son mías (el mínimo es 15).
MartinH

No se preocupe, tal vez le gustaría transmitir mi respuesta a M-WajeEh, ya que ni siquiera puedo contactarlo en este momento: /
MartinH

¿Puedes por favor tratar de describir mejor qué es lo que has hecho? ¿arreglaste un error? ¿Puedes mostrar una captura de pantalla para demostrar el error?
desarrollador de Android

1
Hola @ Martin, bienvenido a SO. Examinaré esto y actualizaré mi respuesta mencionando su nombre y publicando el enlace a su publicación después de la prueba . Solo dame unos días por favor. Estoy atrapado en otro lugar hoy en día.
M-WaJeEh

7

Ok, he usado la última semana para reescribir masivamente mi código para que se ajuste con precisión a tu prueba. Ahora puede copiar este 1: 1 y funcionará de inmediato, incluido setSingleLine(). Recuerde ajustar MIN_TEXT_SIZEy MAX_TEXT_SIZEsi va por valores extremos.

El algoritmo convergente se ve así:

for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

    // Go to the mean value...
    testSize = (upperTextSize + lowerTextSize) / 2;

    // ... inflate the dummy TextView by setting a scaled textSize and the text...
    mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
    mTestView.setText(text);

    // ... call measure to find the current values that the text WANTS to occupy
    mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    int tempHeight = mTestView.getMeasuredHeight();

    // ... decide whether those values are appropriate.
    if (tempHeight >= targetFieldHeight) {
        upperTextSize = testSize; // Font is too big, decrease upperSize
    }
    else {
        lowerTextSize = testSize; // Font is too small, increase lowerSize
    }
}

Y toda la clase se puede encontrar aquí.

El resultado es muy flexible ahora. Esto funciona de la misma manera declarada en xml:

<com.example.myProject.AutoFitText
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="4"
    android:text="@string/LoremIpsum" />

... así como construido mediante programación como en su prueba.

Realmente espero que puedas usar esto ahora. Puede llamar setText(CharSequence text)ahora para usarlo por cierto. La clase se encarga de excepciones excepcionalmente raras y debe ser sólida como una roca. Lo único que el algoritmo aún no admite es:

  • Llamadas a setMaxLines(x)dondex >= 2

¡Pero he agregado comentarios extensos para ayudarlo a construir esto si lo desea!


Tenga en cuenta:

Si solo usa esto normalmente sin limitarlo a una sola línea, entonces puede haber saltos de palabras como mencionó anteriormente. Esta es una función de Android , no es culpa del AutoFitText. Android siempre separará palabras que son demasiado largas para un TextView y en realidad es bastante conveniente. Si desea intervenir aquí, consulte mis comentarios y mi código que comienzan en la línea 203. Ya he escrito una división adecuada y el reconocimiento para usted, todo lo que debe hacer en adelante es dividir las palabras y luego modificarlas como desee .

En conclusión: Debería considerar reescribir su prueba para admitir también caracteres espaciales, así:

final Random _random = new Random();
final String ALLOWED_CHARACTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
final int textLength = _random.nextInt(80) + 20;
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < textLength; ++i) {
    if (i % 7 == 0 && i != 0) {
        builder.append(" ");
    }
    builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
}
((AutoFitText) findViewById(R.id.textViewMessage)).setText(builder.toString());

Esto producirá resultados muy hermosos (y más realistas).
También encontrará comentarios para comenzar en este asunto.

Buena suerte y saludos cordiales


Hay algunos problemas, pero en general funciona muy bien. los problemas: no admite líneas múltiples (como escribió) y correrá la voz en caso de que intente hacerlo, incluya la función API16 (addOnGlobalLayoutListener) que creo que puede evitarse por completo (o al menos usar OnPreDrawListener), utiliza la búsqueda binaria para que pueda probar tamaños que no se ajustan en absoluto (y usar más memoria sin motivo, lo que puede causar excepciones si es demasiado grande), no admite el manejo cuando algo cambia (como el texto en sí).
Desarrollador de Android

por cierto, no tiene que usar la variable mTestView y hacer la prueba directamente en la vista actual, y de esta manera no tendrá que considerar todas las cosas especiales. Además, creo que en lugar de MAX_TEXT_SIZE puedes usar targetFieldHeight.
Desarrollador de Android

Creo que en lugar de una búsqueda binaria pura (especialmente 2 de ellas), puede obtener una mejor suposición por la relación del objetivo con el tamaño medido. También hay otros métodos (Newton y algo más que no recuerdo) pero no creo que encajen aquí. el que sugerí ahora es mejor ya que puede comenzar desde el tamaño mínimo en lugar de probar el tamaño máximo. El hecho es que no necesitará un tamaño máximo ya que sigue adivinando mejor.
Desarrollador de Android

También creo que las 2 búsquedas binarias podrían fusionarse en una sola línea cuando la condición podría ser simplemente: if (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
desarrollador de Android

1
Otro problema que he encontrado es la gravedad del texto. Creo que no puede centrarse verticalmente.
Desarrollador de Android el

4

Mi requerimiento es

  • Haga clic en la vista de texto escalable
  • Abra una listaActividad y muestre varios elementos de cadena de longitud.
  • Seleccione un texto de la lista.
  • Establezca el texto nuevamente en el escalableTextView en otra actividad.

Remití el enlace: Auto Scale TextView Text para encajar dentro de los límites (incluidos los comentarios) y también DialogTitle.java

Descubrí que la solución proporcionada es agradable y simple, pero no cambia dinámicamente el tamaño del cuadro de texto. Funciona muy bien cuando la longitud del texto seleccionado de la vista de lista es mayor en tamaño que la longitud del texto existente en el ScalableTextView . Cuando se selecciona el texto que tiene una longitud más pequeña que el texto existente en el ScalableTextView, no aumenta el tamaño del texto, mostrando el texto en el tamaño más pequeño.

Modifiqué el escalableTextView.java para reajustar el tamaño del texto en función de la longitud del texto. Aquí está miScalableTextView.java

public class ScalableTextView extends TextView
{
float defaultTextSize = 0.0f;

public ScalableTextView(Context context, AttributeSet attrs, int defStyle)
{
    super(context, attrs, defStyle);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

public ScalableTextView(Context context, AttributeSet attrs)
{
    super(context, attrs);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

public ScalableTextView(Context context)
{
    super(context);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    final Layout layout = getLayout();
    if (layout != null)
    {
        final int lineCount = layout.getLineCount();
        if (lineCount > 0)
        {
            int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
            while (ellipsisCount > 0)
            {
                final float textSize = getTextSize();

                // textSize is already expressed in pixels
                setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));

                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                ellipsisCount = layout.getEllipsisCount(lineCount - 1);
            }
        }
    }
}
}

Happy Coding ...


¿No deberías hacerlo usando la búsqueda binaria en lugar de reducirlo en uno para cada iteración? Además, parece que este código obliga a TextView a estar con una sola línea de texto, en lugar de permitir que tenga varias líneas.
Desarrollador de Android

Esta fue la única solución que funcionó para mí. Mi problema estaba relacionado con que TExtView no se ajustaba a la vista solo en 4.1
FOliveira

parece que no coincide con el padre en un 100%. mira la captura de pantalla: s14.postimg.org/93c2xgs75/Screenshot_2.png
user25

4

Explicaré cómo funciona este atributo versiones inferiores de Android paso a paso:

1- Importa la biblioteca de soporte de Android 26.xx en tu archivo de proyecto gradle. Si no hay una biblioteca de soporte en IDE, se descargarán automáticamente.

dependencies {
    compile 'com.android.support:support-v4:26.1.0'
    compile 'com.android.support:appcompat-v7:26.1.0'
    compile 'com.android.support:support-v13:26.1.0' }

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    } }

2- Abra su archivo XML de diseño y refactorice como esta su TextView. Este escenario es: cuando se aumenta el tamaño de fuente en el sistema, ajuste el texto al ancho disponible, no el ajuste de texto.

<android.support.v7.widget.AppCompatTextView
            android:id="@+id/textViewAutoSize"
            android:layout_width="match_parent"
            android:layout_height="25dp"
            android:ellipsize="none"
            android:text="Auto size text with compatible lower android versions."
            android:textSize="12sp"
            app:autoSizeMaxTextSize="14sp"
            app:autoSizeMinTextSize="4sp"
            app:autoSizeStepGranularity="0.5sp"
            app:autoSizeTextType="uniform" />

Sin embargo, su implementación de TextView de ajuste automático no funciona bien. Hay veces que, en lugar de cambiar el tamaño del tamaño de fuente, el texto simplemente pasa a la siguiente línea, mal ... Incluso se muestra en sus videos, como si fuera algo bueno: youtu.be/fjUdJ2aVqE4?t=14 . Observe cómo la "w" de "TextView" pasa a la siguiente línea ... De todos modos, creó una nueva solicitud al respecto aquí: issuetracker.google.com/issues/68787108 . Por favor considere protagonizarlo.
Desarrollador de Android el

@androiddeveloper Tienes razón. No expliqué que esta implementación de etiqueta funciona en una sola línea. Si necesita líneas de ajuste, debe cambiar este atributo: android: layout_height = "wrap_content". Pero android.support.v7.widget.AppCompatTextView y la garantía de implementación de gradle funcionan con este atributo.
Sinan Ergin

No creo que lo entiendas. No quiero que las palabras parciales se ajusten a la siguiente línea, a menos que no haya otra forma de resolverlo. En el video, puede ver fácilmente que las fuentes podrían haberse vuelto más pequeñas. Ese es el problema. ¿Estás diciendo que sabes cómo solucionarlo? ¿Significa que no habrá ajustes de palabras y solo cambiará el tamaño de fuente?
Desarrollador de Android

@androiddeveloper Hay muchos escenarios. Mi escenario es: autoajustar texto a fijo con y no ajuste de palabra. Durante el aumento del tamaño de la fuente del sistema, el texto no debe mostrar el ajuste de la palabra en la vista de texto. Si el ajuste de palabras no es importante para usted, no se moleste en establecer una altura fija. Esta propiedad no usa solo ajuste de texto, puede ajustar automáticamente el texto que tiene el ancho fijo de TextView.
Sinan Ergin

Según mis pruebas, los tamaños de fuente no cambian correctamente y puede producirse el problema de ajuste de la palabra.
Desarrollador de Android

3

Advertencia, error en Android 3 (Honeycomb) y Android 4.0 (Ice Cream Sandwich)

Las versiones de Android: 3.1 - 4.04 tienen un error, que setTextSize () dentro de TextView funciona solo por primera vez (primera invocación).

El error se describe en el problema 22493: error de altura de TextView en Android 4.0 y problema 17343: la altura y el texto del botón no vuelven a su estado original después de aumentar y disminuir el tamaño del texto en HoneyComb .

La solución consiste en agregar un carácter de nueva línea al texto asignado a TextView antes de cambiar el tamaño:

final String DOUBLE_BYTE_SPACE = "\u3000";
textView.append(DOUBLE_BYTE_SPACE);

Lo uso en mi código de la siguiente manera:

final String DOUBLE_BYTE_SPACE = "\u3000";
AutoResizeTextView textView = (AutoResizeTextView) view.findViewById(R.id.aTextView);
String fixString = "";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1
   && android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {  
    fixString = DOUBLE_BYTE_SPACE;
}
textView.setText(fixString + "The text" + fixString);

Agrego este carácter "\ u3000" a la izquierda y derecha de mi texto, para mantenerlo centrado. Si lo tiene alineado a la izquierda, agregue solo a la derecha. Por supuesto, también se puede incrustar con el widget AutoResizeTextView, pero quería mantener el código fijo fuera.


¿puede mostrar exactamente en qué líneas (antes / después de qué líneas) y en qué función?
Desarrollador de Android

OK gracias. Espero que esto solucione todo, y que quien lea esto lo encuentre útil. Actualmente no puedo comprobarlo. tal vez la próxima vez que lo use.
Desarrollador de Android

Este código se llama desde fuera del código de la vista. ¿Conoces quizás una buena forma alternativa de manejar esto dentro del código de la vista?
Desarrollador de Android

puede anular textView.setText () y poner este código dentro de la clase TextView
Malachiasz

¿No significaría también que "getText ()" devolvería el texto con los caracteres adicionales?
Desarrollador de Android

3

Ahora hay una solución oficial para este problema. Los TextView de tamaño automático introducidos con Android O están disponibles en la Biblioteca de soporte 26 y son compatibles con versiones anteriores hasta Android 4.0.

https://developer.android.com/preview/features/autosizing-textview.html

No estoy seguro de por qué un administrador eliminó https://stackoverflow.com/a/42940171/47680 que también incluía esta información.


1
Sí, también he oído hablar de eso, pero ¿qué tan bueno es? ¿Hay alguna muestra de usarlo? Incluso en su video, noté un error: una carta de una palabra llegó a la línea después (envuelta): youtu.be/1N9KveJ-FU8?t=781 (observe la "W")
desarrollador de Android

3

A partir de junio de 2018, Android admite oficialmente esta función para Android 4.0 (API nivel 14) y superior.
Con Android 8.0 (API nivel 26) y superior:

setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, 
        int autoSizeStepGranularity, int unit);

Versiones de Android anteriores a Android 8.0 (API nivel 26):

TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(TextView textView,
int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)

Mira mi respuesta detallada .


1

Convierta la vista de texto en una imagen y escale la imagen dentro de los límites.

Aquí hay un ejemplo sobre cómo convertir una vista en una imagen: ¿ Convertir una vista en mapa de bits sin mostrarla en Android?

El problema es que su texto no será seleccionable, pero debería ser el truco. No lo he probado, así que no estoy seguro de cómo se vería (debido a la escala).


Es una buena idea, pero esto también hará que el texto se pixele y elimine cualquier característica que pueda usar con textView.
Desarrollador de Android

0

A continuación se muestra Avalancha TextView con funcionalidad adicional para fuentes personalizadas.

Uso:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:foo="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="match_parent" >

                <de.meinprospekt.androidhd.view.AutoFitText
                android:layout_width="wrap_content"
                android:layout_height="10dp"
                android:text="Small Text"
                android:textColor="#FFFFFF"
                android:textSize="100sp"
                foo:customFont="fonts/Roboto-Light.ttf" />

</FrameLayout>

No olvide agregar: xmlns: foo = "http://schemas.android.com/apk/res-auto". La fuente debe estar en la carpeta de activos

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.TextView;
import de.meinprospekt.androidhd.R;
import de.meinprospekt.androidhd.adapter.BrochuresHorizontalAdapter;
import de.meinprospekt.androidhd.util.LOG;

/**
 * https://stackoverflow.com/a/16174468/2075875 This class builds a new android Widget named AutoFitText which can be used instead of a TextView to
 * have the text font size in it automatically fit to match the screen width. Credits go largely to Dunni, gjpc, gregm and speedplane from
 * Stackoverflow, method has been (style-) optimized and rewritten to match android coding standards and our MBC. This version upgrades the original
 * "AutoFitTextView" to now also be adaptable to height and to accept the different TextView types (Button, TextClock etc.)
 * 
 * @author pheuschk
 * @createDate: 18.04.2013
 * 
 * combined with: https://stackoverflow.com/a/7197867/2075875
 */
@SuppressWarnings("unused")
public class AutoFitText extends TextView {

    private static final String TAG = AutoFitText.class.getSimpleName();

    /** Global min and max for text size. Remember: values are in pixels! */
    private final int MIN_TEXT_SIZE = 10;
    private final int MAX_TEXT_SIZE = 400;

    /** Flag for singleLine */
    private boolean mSingleLine = false;

    /**
     * A dummy {@link TextView} to test the text size without actually showing anything to the user
     */
    private TextView mTestView;

    /**
     * A dummy {@link Paint} to test the text size without actually showing anything to the user
     */
    private Paint mTestPaint;

    /**
     * Scaling factor for fonts. It's a method of calculating independently (!) from the actual density of the screen that is used so users have the
     * same experience on different devices. We will use DisplayMetrics in the Constructor to get the value of the factor and then calculate SP from
     * pixel values
     */
    private float mScaledDensityFactor;

    /**
     * Defines how close we want to be to the factual size of the Text-field. Lower values mean higher precision but also exponentially higher
     * computing cost (more loop runs)
     */
    private final float mThreshold = 0.5f;

    /**
     * Constructor for call without attributes --> invoke constructor with AttributeSet null
     * 
     * @param context
     */
    public AutoFitText(Context context) {
        this(context, null);
    }

    public AutoFitText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public AutoFitText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        //TextViewPlus part https://stackoverflow.com/a/7197867/2075875
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoFitText);
        String customFont = a.getString(R.styleable.AutoFitText_customFont);
        setCustomFont(context, customFont);
        a.recycle();

        // AutoFitText part
        mScaledDensityFactor = context.getResources().getDisplayMetrics().scaledDensity;
        mTestView = new TextView(context);

        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());

        this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

            @Override
            public void onGlobalLayout() {
                // make an initial call to onSizeChanged to make sure that refitText is triggered
                onSizeChanged(AutoFitText.this.getWidth(), AutoFitText.this.getHeight(), 0, 0);
                // Remove the LayoutListener immediately so we don't run into an infinite loop
                //AutoFitText.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                removeOnGlobalLayoutListener(AutoFitText.this, this);
            }
        });
    }

    public boolean setCustomFont(Context ctx, String asset) {
        Typeface tf = null;
        try {
        tf = Typeface.createFromAsset(ctx.getAssets(), asset);  
        } catch (Exception e) {
            LOG.e(TAG, "Could not get typeface: "+e.getMessage());
            return false;
        }

        setTypeface(tf);  
        return true;
    }

    @SuppressLint("NewApi")
    public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener){
        if (Build.VERSION.SDK_INT < 16) {
            v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
        } else {
            v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
        }
    }

    /**
     * Main method of this widget. Resizes the font so the specified text fits in the text box assuming the text box has the specified width. This is
     * done via a dummy text view that is refit until it matches the real target width and height up to a certain threshold factor
     * 
     * @param targetFieldWidth The width that the TextView currently has and wants filled
     * @param targetFieldHeight The width that the TextView currently has and wants filled
     */
    private void refitText(String text, int targetFieldWidth, int targetFieldHeight) {

        // Variables need to be visible outside the loops for later use. Remember size is in pixels
        float lowerTextSize = MIN_TEXT_SIZE;
        float upperTextSize = MAX_TEXT_SIZE;

        // Force the text to wrap. In principle this is not necessary since the dummy TextView
        // already does this for us but in rare cases adding this line can prevent flickering
        this.setMaxWidth(targetFieldWidth);

        // Padding should not be an issue since we never define it programmatically in this app
        // but just to to be sure we cut it off here
        targetFieldWidth = targetFieldWidth - this.getPaddingLeft() - this.getPaddingRight();
        targetFieldHeight = targetFieldHeight - this.getPaddingTop() - this.getPaddingBottom();

        // Initialize the dummy with some params (that are largely ignored anyway, but this is
        // mandatory to not get a NullPointerException)
        mTestView.setLayoutParams(new LayoutParams(targetFieldWidth, targetFieldHeight));

        // maxWidth is crucial! Otherwise the text would never line wrap but blow up the width
        mTestView.setMaxWidth(targetFieldWidth);

        if (mSingleLine) {
            // the user requested a single line. This is very easy to do since we primarily need to
            // respect the width, don't have to break, don't have to measure...

            /*************************** Converging algorithm 1 ***********************************/
            for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                // Go to the mean value...
                testSize = (upperTextSize + lowerTextSize) / 2;

                mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                mTestView.setText(text);
                mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

                if (mTestView.getMeasuredWidth() >= targetFieldWidth) {
                    upperTextSize = testSize; // Font is too big, decrease upperSize
                } else {
                    lowerTextSize = testSize; // Font is too small, increase lowerSize
                }
            }
            /**************************************************************************************/

            // In rare cases with very little letters and width > height we have vertical overlap!
            mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

            if (mTestView.getMeasuredHeight() > targetFieldHeight) {
                upperTextSize = lowerTextSize;
                lowerTextSize = MIN_TEXT_SIZE;

                /*************************** Converging algorithm 1.5 *****************************/
                for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                    // Go to the mean value...
                    testSize = (upperTextSize + lowerTextSize) / 2;

                    mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                    mTestView.setText(text);
                    mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

                    if (mTestView.getMeasuredHeight() >= targetFieldHeight) {
                        upperTextSize = testSize; // Font is too big, decrease upperSize
                    } else {
                        lowerTextSize = testSize; // Font is too small, increase lowerSize
                    }
                }
                /**********************************************************************************/
            }
        } else {

            /*********************** Converging algorithm 2 ***************************************/
            // Upper and lower size converge over time. As soon as they're close enough the loop
            // stops
            // TODO probe the algorithm for cost (ATM possibly O(n^2)) and optimize if possible
            for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                // Go to the mean value...
                testSize = (upperTextSize + lowerTextSize) / 2;

                // ... inflate the dummy TextView by setting a scaled textSize and the text...
                mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                mTestView.setText(text);

                // ... call measure to find the current values that the text WANTS to occupy
                mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
                int tempHeight = mTestView.getMeasuredHeight();
                // int tempWidth = mTestView.getMeasuredWidth();

                // LOG.debug("Measured: " + tempWidth + "x" + tempHeight);
                // LOG.debug("TextSize: " + testSize / mScaledDensityFactor);

                // ... decide whether those values are appropriate.
                if (tempHeight >= targetFieldHeight) {
                    upperTextSize = testSize; // Font is too big, decrease upperSize
                } else {
                    lowerTextSize = testSize; // Font is too small, increase lowerSize
                }
            }
            /**************************************************************************************/

            // It is possible that a single word is wider than the box. The Android system would
            // wrap this for us. But if you want to decide fo yourself where exactly to break or to
            // add a hyphen or something than you're going to want to implement something like this:
            mTestPaint.setTextSize(lowerTextSize);
            List<String> words = new ArrayList<String>();

            for (String s : text.split(" ")) {
                Log.i("tag", "Word: " + s);
                words.add(s);
            }
            for (String word : words) {
                if (mTestPaint.measureText(word) >= targetFieldWidth) {
                    List<String> pieces = new ArrayList<String>();
                    // pieces = breakWord(word, mTestPaint.measureText(word), targetFieldWidth);

                    // Add code to handle the pieces here...
                }
            }
        }

        /**
         * We are now at most the value of threshold away from the actual size. To rather undershoot than overshoot use the lower value. To match
         * different screens convert to SP first. See {@link http://developer.android.com/guide/topics/resources/more-resources.html#Dimension} for
         * more details
         */
        this.setTextSize(TypedValue.COMPLEX_UNIT_SP, lowerTextSize / mScaledDensityFactor);
        return;
    }

    /**
     * This method receives a call upon a change in text content of the TextView. Unfortunately it is also called - among others - upon text size
     * change which means that we MUST NEVER CALL {@link #refitText(String)} from this method! Doing so would result in an endless loop that would
     * ultimately result in a stack overflow and termination of the application
     * 
     * So for the time being this method does absolutely nothing. If you want to notify the view of a changed text call {@link #setText(CharSequence)}
     */
    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        // Super implementation is also intentionally empty so for now we do absolutely nothing here
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        if (width != oldWidth && height != oldHeight) {
            refitText(this.getText().toString(), width, height);
        }
    }

    /**
     * This method is guaranteed to be called by {@link TextView#setText(CharSequence)} immediately. Therefore we can safely add our modifications
     * here and then have the parent class resume its work. So if text has changed you should always call {@link TextView#setText(CharSequence)} or
     * {@link TextView#setText(CharSequence, BufferType)} if you know whether the {@link BufferType} is normal, editable or spannable. Note: the
     * method will default to {@link BufferType#NORMAL} if you don't pass an argument.
     */
    @Override
    public void setText(CharSequence text, BufferType type) {

        int targetFieldWidth = this.getWidth();
        int targetFieldHeight = this.getHeight();

        if (targetFieldWidth <= 0 || targetFieldHeight <= 0 || text.equals("")) {
            // Log.v("tag", "Some values are empty, AutoFitText was not able to construct properly");
        } else {
            refitText(text.toString(), targetFieldWidth, targetFieldHeight);
        }
        super.setText(text, type);
    }

    /**
     * TODO add sensibility for {@link #setMaxLines(int)} invocations
     */
    @Override
    public void setMaxLines(int maxLines) {
        // TODO Implement support for this. This could be relatively easy. The idea would probably
        // be to manipulate the targetHeight in the refitText-method and then have the algorithm do
        // its job business as usual. Nonetheless, remember the height will have to be lowered
        // dynamically as the font size shrinks so it won't be a walk in the park still
        if (maxLines == 1) {
            this.setSingleLine(true);
        } else {
            throw new UnsupportedOperationException("MaxLines != 1 are not implemented in AutoFitText yet, use TextView instead");
        }
    }

    @Override
    public void setSingleLine(boolean singleLine) {
        // save the requested value in an instance variable to be able to decide later
        mSingleLine = singleLine;
        super.setSingleLine(singleLine);
    }
}

errores conocidos: no funciona con Android 4.03: las fuentes son invisibles o muy pequeñas (la avalancha original tampoco funciona) a continuación se encuentra una solución para ese error: https://stackoverflow.com/a/21851239/2075875


0

Prueba esto

TextWatcher changeText = new TextWatcher() {
     @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                tv3.setText(et.getText().toString());
                tv3.post(new Runnable() {           
                    @Override
                    public void run() {
                    while(tv3.getLineCount() >= 3){                     
                            tv3.setTextSize((tv3.getTextSize())-1);                     
                        }
                    }
                });
            }

            @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

            @Override public void afterTextChanged(Editable s) { }
        };

No creo que esto funcione, ya que cada vez que cambie el tamaño del texto, causará el dibujo solo después de que termine con el bloque (lo que significa que se agregará a la cola de eventos). Esto significa que el bucle solo funcionará una vez como máximo cada vez que cambie el texto, y no garantiza que el número de líneas no supere las 3 líneas de texto.
Desarrollador de Android

0

Si buscas algo más fácil:

 public MyTextView extends TextView{

    public void resize(String text, float textViewWidth, float textViewHeight) {
       Paint p = new Paint();
       Rect bounds = new Rect();
       p.setTextSize(1);
       p.getTextBounds(text, 0, text.length(), bounds);
       float widthDifference = (textViewWidth)/bounds.width();
       float heightDifference = (textViewHeight);
       textSize = Math.min(widthDifference, heightDifference);
       setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}

Parece muy corto, pero ¿puede pasar el examen que he preparado? Además, no entiendo cuando se llama a esta función.
Desarrollador de Android

No, es más una sugerencia. Lo mejoraré para que pueda pasar tu prueba lo antes posible.
Sander

Bueno, si es una sugerencia, ¿puedes publicarla directamente en Github?
Desarrollador de Android

0

Solución rápida para el problema descrito por @Malachiasz

He solucionado el problema agregando soporte personalizado para esto en la clase de cambio de tamaño automático:

public void setTextCompat(final CharSequence text) {
    setTextCompat(text, BufferType.NORMAL);
}

public void setTextCompat(final CharSequence text, BufferType type) {
    // Quick fix for Android Honeycomb and Ice Cream Sandwich which sets the text only on the first call
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
        super.setText(DOUBLE_BYTE_WORDJOINER + text + DOUBLE_BYTE_WORDJOINER, type);
    } else {
        super.setText(text, type);
    }
}

@Override
public CharSequence getText() {
    String originalText = super.getText().toString();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
        // We try to remove the word joiners we added using compat method - if none found - this will do nothing.
        return originalText.replaceAll(DOUBLE_BYTE_WORDJOINER, "");
    } else {
        return originalText;
    }
}

Solo llama en yourView.setTextCompat(newTextValue)lugar deyourView.setText(newTextValue)


¿Por qué crear una nueva función de setTextCompat, en lugar de anular setText? Además, ¿qué problema?
Desarrollador de Android

setText () es un método final de TextView, por lo que no podrá anularlo. Otra opción sería hacer esto fuera del TextView personalizado, pero esta solución es para usarlo dentro del TextView.
Ionut Negru

No exactamente. Algunos de los "setText" son privados, algunos son públicos y otros no son definitivos. Parece que la mayoría se puede manejar anulando setText de void público (texto CharSequence, tipo BufferType). Sin embargo, hay una función final que no conozco su propósito: public final void setText (char [] text, int start, int len). Quizás eche un vistazo más profundo al código.
Desarrollador de Android

Todas esas variantes de setText () en realidad llamarían al método setText (CharSequence text) al final. Si desea garantizar el mismo comportamiento, deberá anular ese método; de lo contrario, sería mucho mejor simplemente agregar su propio método setText () personalizado.
Ionut Negru

Sí, pero tal vez para la mayoría de los casos, está bien.
Desarrollador de Android

0

Intenta agregar LayoutParamsy MaxWidthy MaxHeightal TextView. Obligará al diseño a respetar el contenedor principal y no a desbordarse.

textview.setLayoutParams(new LayoutParams(LinearLayout.MATCH_PARENT,LinearLayout.WRAP_CONTENT));

int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);` 

No estoy seguro de qué estás hablando. ¿Sugiere agregar esto a la muestra textView, para que no tenga el pequeño problema que a veces veo? Si es así, ¿por qué no publicaste al respecto en el sitio web de Github?
Desarrollador de Android

0

Desde Android O, es posible cambiar automáticamente el tamaño del texto en xml:

https://developer.android.com/preview/features/autosizing-textview.html

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:autoSizeTextType="uniform"
    app:autoSizeMinTextSize="12sp"
    app:autoSizeMaxTextSize="100sp"
    app:autoSizeStepGranularity="2sp"
  />

Android O le permite instruir a TextView para que el tamaño del texto se expanda o contraiga automáticamente para llenar su diseño en función de las características y límites de TextView. Esta configuración facilita la optimización del tamaño del texto en diferentes pantallas con contenido dinámico.

Support Library 26.0 Beta proporciona soporte completo para la función de AutoView TextView en dispositivos que ejecutan versiones de Android anteriores a Android O. La biblioteca brinda soporte para Android 4.0 (API nivel 14) y superior. El paquete android.support.v4.widget contiene la clase TextViewCompat para acceder a las funciones de una manera compatible con versiones anteriores.


Lamentablemente, de acuerdo con mis pruebas, e incluso en el video de Google IO, he notado que tiene problemas, como el ajuste incorrecto de las palabras parciales, en lugar de cambiar el tamaño de la fuente. He informado sobre esto aquí: issuetracker.google.com/issues/38468964 , y es por eso que todavía no lo uso.
Desarrollador de Android

0

Después de probar el AutosView TextView oficial de Android , descubrí que si tu versión de Android es anterior a Android 8.0 (nivel de API 26), debes usarla android.support.v7.widget.AppCompatTextViewy asegurarme de que la versión de tu biblioteca de soporte sea superior a 26.0.0. Ejemplo:

<android.support.v7.widget.AppCompatTextView
    android:layout_width="130dp"
    android:layout_height="32dp"
    android:maxLines="1"
    app:autoSizeMaxTextSize="22sp"
    app:autoSizeMinTextSize="12sp"
    app:autoSizeStepGranularity="2sp"
    app:autoSizeTextType="uniform" />

actualizar :

De acuerdo con la respuesta de @ android-developer, verifiqué el AppCompatActivitycódigo fuente y encontré estas dos líneas enonCreate

final AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory();

y en AppCompatDelegateImpl'screateView

    if (mAppCompatViewInflater == null) {
        mAppCompatViewInflater = new AppCompatViewInflater();
    }

usa AppCompatViewInflatervista de inflador, cuando AppCompatViewInflatercreateView usará AppCompatTextView para "TextView".

public final View createView(){
    ...
    View view = null;
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        case "ImageView":
            view = new AppCompatImageView(context, attrs);
            break;
        case "Button":
            view = new AppCompatButton(context, attrs);
            break;
    ...
}

En mi proyecto no lo uso AppCompatActivity, así que necesito usarlo <android.support.v7.widget.AppCompatTextView>en xml.


1
En la mayoría de los casos, el inflador utiliza AppCompatTextView cuando escribe solo "TextView", por lo que no tiene que hacerlo.
Desarrollador de Android

@androiddeveloper Pero cuando uso TextView, el tamaño automático no funciona.
weei.zh

@androiddeveloper Gracias, la razón es que no estoy usando AppCompatActivity. AppCompatActivity utiliza el diseño del inflador AppCompatViewInflater.
weei.zh

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.