Ajuste automático do TextView para Android

231

fundo

Muitas vezes, precisamos ajustar automaticamente a fonte do TextView aos limites dados a ele.

O problema

Infelizmente, embora existam muitos tópicos e postagens (e soluções sugeridas) falando sobre esse problema (exemplo aqui , aqui e aqui ), nenhum deles realmente funciona bem.

Por isso, decidi testar cada um deles até encontrar o negócio real.

Eu acho que os requisitos desse textView devem ser:

  1. Deve permitir o uso de qualquer fonte, tipo de letra, estilo e conjunto de caracteres.

  2. Deve lidar com largura e altura

  3. Sem truncamento, a menos que o texto não possa caber devido à limitação que fornecemos (exemplo: texto muito longo, tamanho disponível muito pequeno). No entanto, poderíamos solicitar barra de rolagem horizontal / vertical, se desejarmos, apenas para esses casos.

  4. Deve permitir várias linhas ou uma linha. No caso de várias linhas, permita linhas máxima e mínima.

  5. Não deve ser lento no cálculo. Usando um loop para encontrar o melhor tamanho? Pelo menos, otimize-a e não aumente sua amostragem em 1 a cada vez.

  6. No caso de várias linhas, deve-se preferir redimensionar ou usar mais linhas e / ou escolher as linhas usando o caractere "\ n".

O que eu tentei

Eu tentei tantas amostras (incluindo as dos links sobre os quais escrevi) e também tentei modificá-las para lidar com os casos de que já falei, mas nenhuma realmente funciona.

Fiz um projeto de amostra que me permite ver visualmente se o TextView se ajusta automaticamente.

Atualmente, meu projeto de amostra aleatoriamente apenas o texto (o alfabeto inglês mais dígitos) e o tamanho do textView, deixando-o com uma única linha, mas mesmo isso não funciona bem em nenhum dos exemplos que eu tentei.

Aqui está o código (também disponível aqui ):

Arquivo 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>

Arquivo 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();
    }
  }

A questão

Alguém sabe de uma solução para esse problema comum que realmente funciona?

Mesmo uma solução com muito menos recursos do que eu escrevi, por exemplo, que possui apenas um número constante de linhas de texto e ajusta sua fonte de acordo com seu tamanho, mas nunca apresenta falhas estranhas e o texto fica muito grande / pequeno em comparação com o espaço disponível.


Projeto GitHub

Como esse é um TextView tão importante, decidi publicar uma biblioteca, para que todos pudessem usá-lo facilmente e contribuir com ele aqui .

desenvolvedor android
fonte
Você já tentou este? androidviews.net/2012/12/autoscale-textview
Thrakbad
@Thrakbad é um dos links que eu mencionei. Também não passa no teste.
desenvolvedor android
Ah desculpe, eu perdi o último exemplo de alguma forma
Thrakbad
Sim, acredite, tentei muitas amostras e também as modifiquei para corrigir os problemas que encontrei, mas nunca consegui. Se você encontrar algo que acha que pode funcionar, teste-o. Eu publiquei um código de exemplo apenas para isso.
desenvolvedor android
1
@ regra este é um dos posts que eu já li, e eu testei todos os exemplos de código. Também acho que você postou duas vezes.
desenvolvedor android

Respostas:

147

Graças à correção simples do MartinH aqui , este código também cuida de android:drawableLeft, android:drawableRight, android:drawableTope android:drawableBottomtags.


Minha resposta aqui deve fazer você feliz em Escalonar automaticamente o texto do TextView para caber dentro dos limites

Eu modifiquei seu caso de teste:

@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);
        }
    });
}

Estou postando código aqui, a pedido do desenvolvedor Android :

Efeito final:

Digite a descrição da imagem aqui

Arquivo de layout de amostra:

<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>

E o 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();
        }
    }
}

Aviso:

Cuidado com esse bug resolvido no Android 3.1 (Honeycomb).

M-WaJeEh
fonte
ok, eu testei o código e parece bom. no entanto, você usou getMaxLines (), que é para a API 16, mas é possível armazenar os maxLines e obtê-lo substituindo setMaxLines e armazenar seu valor. Eu mudei e agora funciona bem. Além disso, como às vezes o texto pode ser longo demais para o menor tamanho, tentei usar o setEllipsize e isso também funcionou! Acho que temos um vencedor. poste seu código aqui para que eu possa marcá-lo como o correto.
desenvolvedor android
você não removeu o getMaxLines () e usou o seu próprio. ainda funcionará apenas a partir da API16 ... altere-o para que todos possam usá-lo. Sugiro substituir setMaxLines para armazenar o parâmetro em um campo e, em seguida, acessar o campo em vez de usar getMaxLines.
desenvolvedor android
verifique agora, suporte adicionado para linhas máximas.
M-Wajeeh
parece bem. você deve adicionar "@TargetApi (Build.VERSION_CODES.JELLY_BEAN)" ao método onTestSize () para que o Lint não dê um erro / aviso sobre ele e, em vez disso, inicialize-o no CTOR. configurar a pesquisa binária como estática também é uma coisa boa, e você pode fazer a inicialização no maior CTOR e chamá-lo dos outros CTORs. no entanto, é realmente a melhor resposta e você merece um V. esta é finalmente uma boa resposta para essa pergunta antiga. bom trabalho!
desenvolvedor android
@ M-WaJeEh verifique a nova postagem neste tópico. um usuário chamado "MartinH" diz que tem uma correção para o seu código.
desenvolvedor android
14

Eu modifiquei um pouco a resposta do M-WaJeEh para levar em conta drawables compostos nas laterais.

Os getCompoundPaddingXXXX()métodos retornam padding of the view + drawable space. Veja, por exemplo: TextView.getCompoundPaddingLeft ()

Problema: isso corrige a medida da largura e altura do espaço do TextView disponível para o texto. Se não levarmos em consideração o tamanho do drawable, ele será ignorado e o texto acabará se sobrepondo ao drawable.


Segmento atualizado 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));
}
MartinH
fonte
você atualizou a fonte de M-WaJeEh. por que você colocou isso como uma nova resposta? Eu sei que você tem boa vontade, mas é uma coisa boa colocá-lo como resposta? Não tenho certeza se ele pode ver a sua resposta ...
desenvolvedor Android
1
Acabei de me inscrever para poder fornecer essa atualização. Minha contagem de reputação é apenas 1, o que me impede de comentar postagens que não são minhas (o mínimo é 15).
MartinH
Não se preocupe, talvez você gostaria de transmitir minha resposta a M-WajeEh, já que não consigo entrar em contato com ele no momento: /
MartinH 28/10
Você pode, por favor, tentar descrever melhor o que você fez? você consertou um bug? você pode mostrar uma captura de tela para demonstrar o bug?
desenvolvedor android
1
Olá @MartinH, Bem-vindo ao SO. Analisarei isso com firmeza e atualizarei minha resposta mencionando seu nome e postando o link na sua postagem após o teste . Apenas me dê alguns dias, por favor. Hoje estou preso em outro lugar.
M-WaJeEh
7

Ok, eu usei a última semana para reescrever massivamente meu código para ajustar-se precisamente ao seu teste. Agora você pode copiar esse 1: 1 e ele funcionará imediatamente - inclusive setSingleLine(). Lembre-se de ajustar MIN_TEXT_SIZEeMAX_TEXT_SIZE se estiver buscando valores extremos.

O algoritmo de convergência se parece com isso:

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

E toda a classe pode ser encontrada aqui.

O resultado é muito flexível agora. Isso funciona da mesma maneira declarada no 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" />

... assim como construído programaticamente como em seu teste.

Eu realmente espero que você possa usar isso agora. Você pode ligar setText(CharSequence text)agora para usá-lo a propósito. A classe cuida de exceções estupendamente raras e deve ser sólida. A única coisa que o algoritmo ainda não suporta é:

  • Chamadas para setMaxLines(x)ondex >= 2

Mas eu adicionei extensos comentários para ajudá-lo a construir isso, se desejar!


Observe:

Se você apenas usar isso normalmente sem limitá-lo a uma única linha, poderá haver quebra de palavras, como você mencionou antes. Este é um recurso do Android , não é culpa do AutoFitText. O Android sempre quebra palavras muito longas para um TextView e é realmente uma conveniência. Se você quiser intervir aqui, consulte meus comentários e código começando na linha 203. Já escrevi uma divisão adequada e o reconhecimento para você, tudo o que você precisa fazer a partir de agora é dividir as palavras e modificá-las conforme desejar. .

Em conclusão: você deve considerar reescrever seu teste para também suportar caracteres de espaço, assim:

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());

Isso produzirá resultados muito bonitos (e mais realistas).
Você encontrará comentários para começar também neste assunto.

Boa sorte e cumprimentos

Patrick
fonte
existem alguns problemas, mas no geral ele funciona tão bem. os problemas: não suporta várias linhas (como você escreveu) e quebrará a palavra caso você tente fazê-lo, inclua a função API16 (addOnGlobalLayoutListener) que eu acho que pode ser totalmente evitada (ou pelo menos use o OnPreDrawListener), usa pesquisa binária para que ele possa testar tamanhos que não se encaixam (e usar mais memória sem motivo, o que pode causar exceções se for muito grande), não suporta manipulação quando algo muda (como o próprio texto).
desenvolvedor android
Aliás, você não precisa usar a variável mTestView e fazer o teste diretamente na exibição atual. Dessa forma, não será necessário considerar todas as coisas especiais. Além disso, acho que em vez de MAX_TEXT_SIZE você pode usar o targetFieldHeight.
desenvolvedor android
Eu acho que, em vez de uma pesquisa binária pura (especialmente 2 deles), você pode obter um palpite melhor pela proporção do alvo em relação ao tamanho medido. também existem outros métodos (newton e outra coisa que não me lembro), mas acho que não se encaixam aqui. o que eu sugeri agora é melhor, pois você pode começar com o tamanho mínimo em vez de testar o tamanho máximo. o fato é que você não precisará de um tamanho máximo, pois continua tentando adivinhar.
desenvolvedor android
Eu também acho que as 2 pesquisas binárias podem ser mescladas em uma única linha quando a condição pode ser simplesmente: if (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
desenvolvedor Android
1
Outra questão que encontrei é a gravidade do texto. Eu acho que não pode ser centralizado verticalmente.
desenvolvedor android
4

Minha exigência é

  • Clique no ScalableTextView
  • Abra uma listActivity e exiba vários itens de string de comprimento.
  • Selecione um texto da lista.
  • Defina o texto novamente para o ScalableTextView em outra atividade.

Consultei o link: Dimensionar automaticamente o texto do TextView para caber dentro dos limites (incluindo comentários) e também o DialogTitle.java

Descobri que a solução fornecida é agradável e simples, mas não altera dinamicamente o tamanho da caixa de texto. Funciona muito bem quando o tamanho do texto selecionado na exibição em lista é maior em tamanho que o comprimento do texto existente no ScalableTextView . Quando selecionado o texto com comprimento menor que o texto existente no ScalableTextView, ele não aumenta o tamanho do texto, mostrando o texto no tamanho menor.

Modifiquei o ScalableTextView.java para reajustar o tamanho do texto com base no comprimento do texto. Aqui está o meuScalableTextView.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);
            }
        }
    }
}
}

Feliz codificação ....

Devendra Vaja
fonte
Você não deveria fazer isso usando a pesquisa binária em vez de reduzir em um para cada iteração? Além disso, parece que esse código força o TextView a estar com uma única linha de texto, em vez de permitir que ele tenha várias linhas.
desenvolvedor android
Esta foi a única solução que funcionou para mim. Meu problema estava relacionado com textView não cabendo a visão em 4,1 única
FOliveira
parece que não corresponde a 100% dos pais. veja a captura de tela: s14.postimg.org/93c2xgs75/Screenshot_2.png
user25
4

Vou explicar como funciona esse atributo em versões mais baixas do Android, passo a passo:

1- Importe a biblioteca de suporte android 26.xx para o arquivo de classificação do projeto. Se não houver biblioteca de suporte no IDE, eles serão baixados automaticamente.

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 seu arquivo XML de layout e refatorar como esta tag seu TextView. Este cenário é: quando o tamanho da fonte é aumentado no sistema, ajuste o texto à largura disponível, não a quebra de linha.

<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" />
Sinan Ergin
fonte
A implementação do TextView de ajuste automático não funciona bem. Há momentos em que, em vez de redimensionar o tamanho da fonte, o texto passa para a próxima linha, mal ... É até mostrado nos vídeos, como se fosse uma coisa boa: youtu.be/fjUdJ2aVqE4?t=14 . Observe como o "w" do "TextView" vai para a próxima linha ... Enfim, criou uma nova solicitação aqui: issuetracker.google.com/issues/68787108 . Por favor, considere estrelando.
desenvolvedor android
@androiddeveloper Você está certo. Não expliquei que essa implementação de tag funciona em linha única. Se você precisar quebrar linhas, precisará alterar este atributo: android: layout_height = "wrap_content". Mas android.support.v7.widget.AppCompatTextView e garantia de implementação gradle funcionam com esse atributo.
Sinan Ergin
Eu acho que você não entende. Não quero que as palavras parciais se enquadrem na próxima linha, a menos que não haja outra maneira de resolvê-lo. No vídeo, você pode ver facilmente que as fontes podem ter ficado menores. Essa é a questão. Você está dizendo que sabe como consertá-lo? O que significa que não haverá quebra de palavras e apenas o tamanho da fonte será alterado?
developer android
@androiddeveloper Existem muitos cenários. Meu cenário é: ajuste automático do texto para correção com e não quebra de linha. Durante o aumento do tamanho da fonte do sistema, o texto deve mostrar não quebra de linha na visualização de texto. Se a quebra de linha não for importante para você, não se preocupe em definir a altura fixa. Esta propriedade não usa apenas quebra automática de linha, você pode ajustar automaticamente o texto com largura fixa no TextView.
Sinan Ergin
De acordo com meus testes, o tamanho da fonte não muda corretamente e o problema de quebra de linha pode ocorrer.
developer android
3

Aviso, bug no Android 3 (Honeycomb) e Android 4.0 (Ice Cream Sandwich)

Versões do Android: 3.1 - 4.04 têm um bug, que setTextSize () dentro do TextView funciona apenas pela primeira vez (primeira chamada).

O bug é descrito no problema 22493: erro de altura do TextView no Android 4.0 e no problema 17343: a altura e o texto do botão não retornam ao seu estado original após aumentar e diminuir o tamanho do texto no HoneyComb .

A solução alternativa é adicionar um caractere de nova linha ao texto atribuído ao TextView antes de alterar o tamanho:

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

Eu o uso no meu código da seguinte maneira:

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);

Eu adiciono esse caractere "\ u3000" nas partes esquerda e direita do meu texto, para mantê-lo centralizado. Se você o alinhar à esquerda, anexe apenas à direita. Claro que também pode ser incorporado ao widget AutoResizeTextView, mas eu queria manter o código de correção fora.

Malachiasz
fonte
você pode mostrar exatamente em quais linhas (antes / depois de quais linhas) e em qual função?
desenvolvedor android
ok obrigado. Espero que isso conserte tudo, e que quem lê isso ache útil. Atualmente eu não posso dar uma olhada. talvez da próxima vez que eu vou usá-lo.
desenvolvedor android
Esse código é chamado de fora do código da visualização. Você conhece talvez uma boa maneira alternativa de lidar com isso dentro do código da visualização?
desenvolvedor android
você poderia substituir textView.setText () e colocar isso dentro do código de classe TextView
Malachiasz
Isso também não significaria que "getText ()" retornaria o texto com os caracteres extras?
desenvolvedor android
3

Agora existe uma solução oficial para esse problema. O dimensionamento automático de TextViews introduzidas no Android O está disponível na Biblioteca de suporte 26 e é compatível com versões anteriores até o Android 4.0.

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

Não sei por que o https://stackoverflow.com/a/42940171/47680, que também inclui essas informações, foi excluído por um administrador.

Artem Russakovskii
fonte
1
Sim, eu também ouvi falar disso, mas como é bom? Existe alguma amostra de usá-lo? Mesmo no vídeo, notei um bug: uma carta de uma palavra chegou à linha depois (embrulhada): youtu.be/1N9KveJ-FU8?t=781 (observe o "W")
desenvolvedor android
3

A partir de junho de 2018, o Android suporta oficialmente esse recurso para Android 4.0 (API nível 14) e superior.
Com o Android 8.0 (nível API 26) e superior:

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

Versões do Android anteriores ao Android 8.0 (nível 26 da API):

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

Confira minha resposta detalhada .

Pense duas vezes no código uma vez
fonte
1

Converta a exibição de texto em uma imagem e dimensione a imagem dentro dos limites.

Aqui está um exemplo de como converter uma exibição em uma imagem: convertendo uma exibição em bitmap sem exibi-la no Android?

O problema é que seu texto não será selecionável, mas deve funcionar. Eu não tentei, então não tenho certeza de como ficaria (por causa do dimensionamento).

Dr NotSoKind
fonte
É uma boa idéia, mas isso também tornará o texto pixelizado e removerá qualquer recurso que eu possa usar com o textView.
desenvolvedor android
0

Abaixo está o avalancha TextView com funcionalidade adicional para a fonte personalizada.

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>

Não se esqueça de adicionar: xmlns: foo = "http://schemas.android.com/apk/res-auto". A fonte deve estar no diretório de ativos

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

bugs conhecidos: não funciona com o Android 4.03 - as fontes são invisíveis ou muito pequenas (a avalancha original também não funciona) abaixo é uma solução alternativa para esse bug: https://stackoverflow.com/a/21851239/2075875

Malachiasz
fonte
0

Tente isto

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) { }
        };
Esqueceu o Nome
fonte
Eu não acho que isso funcione, pois cada vez que você altera o tamanho do texto, ele causa o desenho somente após o término do bloco (o que significa que ele será adicionado à fila de eventos). Isso significa que o loop funcionará apenas uma vez no máximo cada vez que o texto for alterado e não garante que o número de linhas não exceda 3 linhas de texto.
desenvolvedor android
0

Se você está procurando algo mais 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);
}
Sander
fonte
Parece muito curto, mas pode passar no teste que eu preparei? Além disso, eu não entendo quando essa função é chamada.
desenvolvedor android
Não, é mais uma sugestão. Vou melhorar para que ele possa passar no teste o mais rápido possível.
Sander
Bem, se é uma sugestão, você pode publicá-la diretamente no Github?
developer android
0

Solução rápida para o problema descrito por @Malachiasz

Corrigi o problema adicionando suporte personalizado para isso na classe de redimensionamento 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;
    }
}

Basta ligar em yourView.setTextCompat(newTextValue)vez deyourView.setText(newTextValue)

Ionut Negru
fonte
Por que criar uma nova função de setTextCompat, em vez de substituir setText? Além disso, qual problema?
desenvolvedor android
setText () é um método final do TextView, portanto você não poderá substituí-lo. Outra opção seria fazer isso fora do TextView personalizado, mas essa solução é para usá-lo dentro do TextView.
Ionut Negru
Não exatamente. Alguns dos "setText" são privados, outros são públicos e outros não são finais. Parece que a maioria pode ser tratada substituindo setText público vazio (texto CharSequence, tipo BufferType). Existe uma função final que eu ainda não conheço sua finalidade: public final void setText (char [] text, int start, int len). Talvez tenha uma análise mais profunda do código.
desenvolvedor android
Todas essas variantes de setText () realmente chamariam o método setText (texto CharSequence) no final. Se você deseja garantir o mesmo comportamento, seria necessário substituir esse método, caso contrário, seria muito melhor simplesmente adicionar seu próprio método setText () personalizado.
Ionut Negru 11/10
Sim, mas talvez para a maioria dos casos esteja tudo bem.
desenvolvedor android
0

Tente adicionar LayoutParamse MaxWidthe MaxHeightao TextView. Isso forçará o layout a respeitar o contêiner pai e não estourar.

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

int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);` 
user3891647
fonte
Não tenho certeza do que você está falando. Você sugere adicionar isso ao exemplo textView, para que ele não tenha o pequeno problema que às vezes vejo? se sim, por que você não postou sobre isso no site do github?
desenvolvedor android
0

Desde o Android O, é possível redimensionar automaticamente o texto em 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"
  />

O Android O permite que você instrua um TextView para permitir que o tamanho do texto se expanda ou contrate automaticamente para preencher seu layout com base nas características e limites do TextView. Essa configuração facilita a otimização do tamanho do texto em diferentes telas com conteúdo dinâmico.

A Biblioteca de suporte 26.0 Beta fornece suporte completo ao recurso TextView de dimensionamento automático em dispositivos executando versões do Android anteriores ao Android O. A biblioteca fornece suporte ao Android 4.0 (nível de API 14) e superior. O pacote android.support.v4.widget contém a classe TextViewCompat para acessar os recursos de uma maneira compatível com versões anteriores.

Javatar
fonte
Infelizmente, de acordo com meus testes, e mesmo no vídeo do Google IO, notei que há problemas, como agrupar palavras-chave incorretamente, em vez de redimensionar a fonte. Eu relatei sobre isso aqui: issuetracker.google.com/issues/38468964 , e é por isso que ainda não o uso.
desenvolvedor android
0

Depois de experimentar o Autosizing TextView oficial do Android , descobri que se a sua versão do Android é anterior ao Android 8.0 (API nível 26), é necessário usá-lo android.support.v7.widget.AppCompatTextViewe verifique se a versão da biblioteca de suporte está acima de 26.0.0. Exemplo:

<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" />

update :

De acordo com a resposta do @ android-developer, verifiquei o AppCompatActivitycódigo-fonte e encontrei essas duas linhas emonCreate

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

e em AppCompatDelegateImpl'screateView

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

ele usa a AppCompatViewInflatervisualização AppCompatViewInflaterinflada ; quando createView, ele usa 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;
    ...
}

No meu projeto eu não uso AppCompatActivity, então eu preciso usar <android.support.v7.widget.AppCompatTextView>em xml.

weei.zh
fonte
1
Na maioria dos casos, o inflator usa AppCompatTextView quando você escreve apenas "TextView", para que você não precise fazer isso.
desenvolvedor android
@androiddeveloper Mas quando eu uso o TextView, o dimensionamento automático não funciona.
Weei.zh 30/05/19
@androiddeveloper Obrigado, o motivo é que eu não uso AppCompatActivity. AppCompatActivity usa o layout do inflador AppCompatViewInflater.
weei.zh 30/05/19
Eu não entendo
desenvolvedor Android