TextView à ajustement automatique pour Android

231

Contexte

Plusieurs fois, nous devons ajuster automatiquement la police de TextView aux limites qui lui sont données.

Le problème

Malheureusement, même s'il existe de nombreux fils et messages (et des solutions suggérées) parlant de ce problème (exemple ici , ici et ici ), aucun d'entre eux ne fonctionne vraiment bien.

C'est pourquoi, j'ai décidé de tester chacun d'eux jusqu'à ce que je trouve la vraie affaire.

Je pense que les exigences d'une telle textView devraient être:

  1. Devrait permettre d'utiliser n'importe quelle police, police de caractères, style et jeu de caractères.

  2. Devrait gérer à la fois la largeur et la hauteur

  3. Pas de troncature à moins que le texte ne puisse pas tenir à cause de la limitation, nous l'avons donnée (exemple: texte trop long, taille disponible trop petite). Cependant, nous pourrions demander une barre de défilement horizontale / verticale si nous le souhaitons, juste pour ces cas.

  4. Devrait autoriser plusieurs lignes ou une seule ligne. Dans le cas de plusieurs lignes, autorisez les lignes max et min.

  5. Ne devrait pas être lent dans le calcul. Vous utilisez une boucle pour trouver la meilleure taille? Optimisez-le au moins et n'incrémentez pas votre échantillonnage de 1 à chaque fois.

  6. Dans le cas de plusieurs lignes, devrait permettre de préférer le redimensionnement ou l'utilisation de plusieurs lignes, et / ou permettre de choisir les lignes nous-mêmes en utilisant le caractère "\ n".

Ce que j'ai essayé

J'ai essayé tellement d'échantillons (y compris ceux des liens dont j'ai parlé), et j'ai également essayé de les modifier pour gérer les cas, j'en ai parlé, mais aucun ne fonctionne vraiment.

J'ai fait un exemple de projet qui me permet de voir visuellement si le TextView s'adapte automatiquement correctement.

Actuellement, mon exemple de projet ne randomise que le texte (l'alphabet anglais plus les chiffres) et la taille du textView, et le laisse avec une seule ligne, mais même cela ne fonctionne pas bien sur aucun des échantillons que j'ai essayés.

Voici le code (également disponible ici ):

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

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

Quelqu'un connaît-il une solution à ce problème courant qui fonctionne réellement?

Même une solution qui a beaucoup moins de fonctionnalités que ce que j'ai écrit, par exemple une qui a juste un nombre constant de lignes de texte, et ajuste sa police en fonction de sa taille, mais n'a jamais de problèmes bizarres et d'avoir le texte trop grand / petit par rapport à son espace disponible.


Projet GitHub

Comme il s'agit d'un TextView si important, j'ai décidé de publier une bibliothèque, afin que tout le monde puisse facilement l'utiliser et y contribuer ici .

développeur android
la source
Avez-vous essayé celui-ci? androidviews.net/2012/12/autoscale-textview
Thrakbad
@Thrakbad c'est l'un des liens que j'ai mentionnés. Il ne réussit pas non plus le test.
développeur android
Ah désolé, j'ai raté le dernier exemple en quelque sorte
Thrakbad
Oui, croyez-moi, j'ai essayé de nombreux exemples et j'ai également essayé de les modifier pour résoudre les problèmes que j'ai trouvés, mais sans succès. Si vous trouvez quelque chose qui pourrait fonctionner, veuillez le tester. J'ai publié un exemple de code juste pour cela.
développeur Android
1
@rule c'est l'un des articles que j'ai déjà lu, et j'ai testé tous les exemples de code. je pense aussi que vous avez posté deux fois.
développeur Android

Réponses:

148

Merci à solution simple de MartinH ici , ce code prend également en charge android:drawableLeft, android:drawableRight, android:drawableTopet les android:drawableBottommots clés.


Ma réponse ici devrait vous rendre heureux Mettre automatiquement le texte à l'échelle du texte pour qu'il s'adapte aux limites

J'ai modifié votre cas de test:

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

Je publie du code ici à la demande du développeur Android :

Effet final:

Entrez la description de l'image ici

Exemple de fichier de mise en page:

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

Et le code 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();
        }
    }
}

Avertissement:

Méfiez-vous de ce bogue résolu dans Android 3.1 (Honeycomb).

M-WaJeEh
la source
ok, j'ai testé le code, et ça semble bien. cependant, vous avez utilisé getMaxLines (), qui correspond à l'API 16, mais vous pouvez stocker les maxLines et les obtenir en remplaçant setMaxLines et stocker sa valeur. Je l'ai changé et maintenant cela fonctionne bien. aussi, puisque parfois le texte peut être encore trop long pour la plus petite taille, j'ai essayé d'utiliser setEllipsize, et cela a fonctionné aussi! Je pense que nous avons un gagnant. veuillez poster votre code ici afin que je puisse le marquer comme étant le bon.
développeur Android
vous n'avez pas supprimé getMaxLines () et utilisé le vôtre. cela ne fonctionnera toujours qu'à partir d'API16 ... veuillez le modifier pour que tout le monde puisse l'utiliser. je suggère de remplacer setMaxLines pour stocker le paramètre dans un champ, puis d'accéder au champ au lieu d'utiliser getMaxLines.
développeur Android
vérifiez maintenant, ajout de la prise en charge des lignes max.
M-WaJeEh
semble bien. vous devez ajouter "@TargetApi (Build.VERSION_CODES.JELLY_BEAN)" à la méthode onTestSize () afin que Lint ne donne pas d'erreur / d'avertissement à ce sujet et l'initialise à la place dans le CTOR. définir la recherche binaire sur statique est également une bonne chose, et vous pouvez faire l'initialisation sur le plus grand CTOR et l'appeler à partir des autres CTOR. cependant, c'est vraiment la meilleure réponse et vous méritez un V. c'est enfin une bonne réponse à cette vieille question. bon travail!
développeur Android
@ M-WaJeEh vérifier le nouveau message dans ce fil. un utilisateur appelé "MartinH" dit qu'il a un correctif pour votre code.
développeur Android
14

J'ai un peu modifié la réponse de M-WaJeEh pour tenir compte des drawables composés sur les côtés.

Les getCompoundPaddingXXXX()méthodes reviennent padding of the view + drawable space. Voir par exemple: TextView.getCompoundPaddingLeft ()

Problème: cela corrige la mesure de la largeur et de la hauteur de l'espace TextView disponible pour le texte. Si nous ne prenons pas en compte la taille du dessin, elle est ignorée et le texte finira par chevaucher le dessin.


Segment mis à jour 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
la source
vous avez mis à jour la source de M-WaJeEh. pourquoi avez-vous mis cela comme une nouvelle réponse? je sais que vous avez de la bonne volonté, mais est-ce une bonne chose de le mettre comme réponse? je ne suis pas sûr qu'il puisse voir votre réponse ...
développeur Android
1
Je viens de m'inscrire pour pouvoir fournir cette mise à jour. Mon compte de réputation n'est que de 1, ce qui m'empêche de commenter des articles qui ne sont pas les miens (minimum 15).
MartinH
Pas de soucis, vous voudrez peut-être transmettre ma réponse à M-WajeEh car je ne peux même pas le contacter pour le moment: /
MartinH
Pouvez-vous essayer de mieux décrire ce que vous avez fait? avez-vous corrigé un bug? pouvez-vous montrer une capture d'écran afin de démontrer le bug?
développeur Android
1
Salut @MartinH, Bienvenue chez SO. Je vais sûrement examiner cela et mettre à jour ma réponse en mentionnant votre nom et en affichant le lien vers votre message après le test . Donnez-moi juste quelques jours s'il vous plaît. Je suis coincé ailleurs aujourd'hui.
M-WaJeEh
7

Ok, j'ai utilisé la semaine dernière pour réécrire massivement mon code pour correspondre précisément à votre test. Vous pouvez maintenant copier ce 1: 1 et cela fonctionnera immédiatement - y compris setSingleLine(). N'oubliez pas d'ajuster MIN_TEXT_SIZEet MAX_TEXT_SIZEsi vous optez pour des valeurs extrêmes.

L'algorithme de convergence ressemble à ceci:

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

Et toute la classe peut être trouvée ici.

Le résultat est désormais très flexible. Cela fonctionne de la même manière déclarée 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" />

... ainsi que construit par programmation comme dans votre test.

J'espère vraiment que vous pourrez l'utiliser maintenant. Vous pouvez appeler setText(CharSequence text)maintenant pour l'utiliser en passant. La classe prend en charge des exceptions incroyablement rares et devrait être solide comme le roc. La seule chose que l'algorithme ne prend pas encore en charge est:

  • Appels à setMaxLines(x)x >= 2

Mais j'ai ajouté de nombreux commentaires pour vous aider à construire cela si vous le souhaitez!


Notez s'il vous plaît:

Si vous l'utilisez normalement sans le limiter à une seule ligne, il peut y avoir un saut de mot comme vous l'avez mentionné précédemment. Ceci est une fonctionnalité Android , pas la faute du AutoFitText. Android cassera toujours les mots trop longs pour une TextView et c'est en fait assez pratique. Si vous voulez intervenir ici, veuillez consulter mes commentaires et mon code à partir de la ligne 203. J'ai déjà écrit une répartition adéquate et la reconnaissance pour vous, tout ce que vous auriez à faire désormais est de diviser les mots puis de les modifier comme vous le souhaitez .

En conclusion: vous devriez fortement envisager de réécrire votre test pour prendre également en charge les caractères d'espace, comme ceci:

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

Cela produira des résultats très beaux (et plus réalistes).
Vous trouverez également des commentaires pour vous aider à démarrer dans cette affaire.

Bonne chance et meilleures salutations

Patrick
la source
il y a quelques problèmes, mais dans l'ensemble cela fonctionne si bien. les problèmes: ne prend pas en charge plusieurs lignes (comme vous l'avez écrit) et cassera le mot au cas où vous essayez de le faire, incluez la fonction API16 (addOnGlobalLayoutListener) qui, je pense, peut être totalement évitée (ou au moins utilisez OnPreDrawListener à la place), utilise la recherche binaire afin qu'elle puisse tester des tailles qui ne lui conviennent pas du tout (et utiliser plus de mémoire sans raison, ce qui peut provoquer des exceptions si elle est trop grande), ne prend pas en charge la gestion lorsque quelque chose change (comme le texte lui-même).
développeur Android
btw, vous n'avez pas besoin d'utiliser la variable mTestView et de faire les tests directement sur la vue actuelle, et de cette façon, vous n'aurez pas à considérer toutes les choses spéciales. aussi, je pense qu'au lieu de MAX_TEXT_SIZE, vous pouvez utiliser le targetFieldHeight.
développeur Android
je pense qu'au lieu d'une recherche binaire pure (en particulier 2 d'entre eux), vous pouvez obtenir une meilleure estimation par le rapport de la cible par rapport à la taille mesurée. il y a aussi d'autres méthodes (newton et autre chose dont je ne me souviens pas) mais je ne pense pas qu'elles conviennent ici. celle que j'ai suggérée maintenant est meilleure car vous pouvez commencer à partir de la taille minimale au lieu de tester la taille maximale. En fait, vous n'aurez pas besoin d'une taille maximale car vous continuez à faire une meilleure supposition.
développeur Android
Je pense également que les 2 recherches binaires pourraient être fusionnées en une seule ligne lorsque la condition pourrait simplement être: if (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
développeur Android
1
un autre problème que j'ai trouvé est la gravité du texte. je pense qu'il ne peut pas être centré verticalement.
développeur Android
4

Mon exigence est de

  • Cliquez sur ScalableTextView
  • Ouvrez une listActivity et affichez divers éléments de chaîne de longueur.
  • Sélectionnez un texte dans la liste.
  • Redéfinissez le texte sur ScalableTextView dans une autre activité.

J'ai référé le lien: Auto Scale TextView Text to Fit within Bounds (y compris les commentaires) et aussi le DialogTitle.java

J'ai trouvé que la solution fournie est agréable et simple mais elle ne change pas dynamiquement la taille de la zone de texte. Cela fonctionne très bien lorsque la longueur de texte sélectionnée dans la vue de liste est plus grande que la longueur de texte existante dans le ScalableTextView . Lorsque sélectionné le texte ayant une longueur inférieure à celle du texte existant dans le ScalableTextView, il n'augmente pas la taille du texte, affichant le texte dans la plus petite taille.

J'ai modifié ScalableTextView.java pour réajuster la taille du texte en fonction de la longueur du texte. Voici monScalableTextView.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);
            }
        }
    }
}
}

Codage heureux ....

Devendra Vaja
la source
Ne devriez-vous pas le faire en utilisant la recherche binaire au lieu de réduire d'un pour chaque itération? En outre, il semble que ce code oblige TextView à être avec une seule ligne de texte, au lieu de lui permettre d'avoir plusieurs lignes.
développeur Android
C'était la seule solution qui fonctionnait pour moi. Mon problème était lié au fait que TExtView ne correspondait pas à la vue en 4.1 uniquement
FOliveira
il semble que cela ne correspond pas à 100% au parent. voir capture d'écran: s14.postimg.org/93c2xgs75/Screenshot_2.png
user25
4

J'expliquerai comment fonctionne cet attribut des versions Android inférieures étape par étape:

1- Importez la bibliothèque de support Android 26.xx sur votre fichier de gradle de projet. S'il n'y a pas de bibliothèque de support sur IDE, ils seront téléchargés automatiquement.

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- Ouvrez votre fichier XML de mise en page et refactorisez comme cette balise votre TextView. Ce scénario est le suivant: lorsque la taille de police est augmentée sur le système, ajustez le texte à la largeur disponible, pas le retour à la ligne.

<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
la source
Leur implémentation de TextView à ajustement automatique ne fonctionne pas bien cependant. Il y a des moments où au lieu de redimensionner la taille de la police, le texte passe simplement à la ligne suivante, mal ... Il est même affiché sur leurs vidéos, comme si c'était une bonne chose: youtu.be/fjUdJ2aVqE4?t=14 . Remarquez comment le "w" de "TextView" passe à la ligne suivante ... Quoi qu'il en soit, a créé une nouvelle demande à ce sujet ici: issuetracker.google.com/issues/68787108 . Veuillez envisager de le mettre en vedette.
développeur Android
@androiddeveloper Vous avez raison. Je n'ai pas expliqué que la mise en œuvre de cette balise fonctionne sur une seule ligne. Si vous avez besoin de lignes d'habillage, vous devez modifier cet attribut: android: layout_height = "wrap_content". Mais android.support.v7.widget.AppCompatTextView et la garantie d'implémentation gradle fonctionnent cet attribut.
Sinan Ergin
Je ne pense pas que tu comprennes. Je ne veux pas que les mots partiels passent à la ligne suivante, à moins qu'il n'y ait pas d'autre moyen de le résoudre. Dans la vidéo, vous pouvez facilement voir que les polices auraient pu devenir plus petites à la place. Voilà le problème. Voulez-vous dire que vous savez comment y remédier? Cela signifie qu'il n'y aura pas de retour à la ligne et que la taille de la police changera?
développeur Android
@androiddeveloper Il existe de nombreux scénarios. Mon scénario est le suivant: ajustement automatique du texte à corriger avec et non retour à la ligne. Lors de l'augmentation de la taille de la police système, le texte ne doit pas afficher le retour automatique à la ligne dans l'affichage texte. Si l'habillage de mots n'a pas d'importance pour vous, ne vous embêtez pas à définir une hauteur fixe. Cette propriété n'utilisant pas uniquement le retour à la ligne, vous pouvez ajuster automatiquement le texte à la largeur fixe du TextView.
Sinan Ergin
Selon mes tests, les tailles de police ne changent pas correctement et le problème de retour à la ligne peut se produire.
développeur Android
3

Avertissement, bug dans Android 3 (Honeycomb) et Android 4.0 (Ice Cream Sandwich)

Les versions d'Androids: 3.1 - 4.04 ont un bug, qui setTextSize () à l'intérieur de TextView ne fonctionne que pour la première fois (première invocation).

Le bogue est décrit dans le problème 22493: bogue de hauteur TextView dans Android 4.0 et problème 17343: la hauteur et le texte du bouton ne reviennent pas à leur état d'origine après avoir augmenté et diminué la taille du texte sur HoneyComb .

La solution consiste à ajouter un caractère de nouvelle ligne au texte affecté à TextView avant de modifier la taille:

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

Je l'utilise dans mon code comme suit:

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

J'ajoute ce caractère "\ u3000" à gauche et à droite de mon texte, pour le garder centré. Si vous l'avez aligné à gauche, ajoutez-le uniquement à droite. Bien sûr, il peut également être intégré au widget AutoResizeTextView, mais je voulais garder le code fixe à l'extérieur.

Malachiasz
la source
pouvez-vous indiquer exactement sur quelles lignes (avant / après quelles lignes) et dans quelle fonction?
développeur Android
OK merci. j'espère que cela corrige tout, et que celui qui lira ceci le trouvera utile. actuellement je ne peux pas le vérifier. peut-être la prochaine fois que je l'utiliserai.
développeur Android
Ce code est appelé de l'extérieur du code de la vue. Connaissez-vous peut-être une bonne alternative pour gérer cela à la place dans le code de la vue?
développeur Android
vous pouvez remplacer textView.setText () et mettre ce code à l'intérieur de la classe TextView
Malachiasz
Cela ne signifierait-il pas également que «getText ()» retournerait le texte avec les caractères supplémentaires?
développeur Android
3

Il existe maintenant une solution officielle à ce problème. La taille automatique des TextViews introduites avec Android O est disponible dans la bibliothèque de support 26 et est rétrocompatible jusqu'à Android 4.0.

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

Je ne sais pas pourquoi https://stackoverflow.com/a/42940171/47680 qui comprenait également ces informations a été supprimé par un administrateur.

Artem Russakovskii
la source
1
Oui, j'en ai entendu parler aussi, mais à quel point est-ce bon? Y a-t-il un échantillon de son utilisation? Même dans leur vidéo, j'ai remarqué un bug: une lettre d'un mot est arrivée à la ligne après (enveloppé): youtu.be/1N9KveJ-FU8?t=781 (remarquez le "W")
développeur Android
3

À partir de juin 2018, Android prend officiellement en charge cette fonctionnalité pour Android 4.0 (API niveau 14) et supérieur.
Avec Android 8.0 (API niveau 26) et supérieur:

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

Versions d'Android antérieures à Android 8.0 (API niveau 26):

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

Découvrez ma réponse détaillée .

Pensez deux fois au code une fois
la source
1

Convertissez la vue texte en image et mettez l'échelle à l'échelle dans les limites.

Voici un exemple sur la façon de convertir une vue en image: convertir une vue en bitmap sans l'afficher dans Android?

Le problème est que votre texte ne sera pas sélectionnable, mais il devrait faire l'affaire. Je ne l'ai pas essayé, donc je ne sais pas à quoi il ressemblerait (à cause de la mise à l'échelle).

Dr NotSoKind
la source
C'est une bonne idée, mais cela rendra également le texte pixélisé et supprimera toute fonctionnalité que je peux utiliser avec textView.
développeur Android
0

Ci-dessous est avalancha TextView avec des fonctionnalités supplémentaires pour la police personnalisée.

Usage:

<?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'oubliez pas d'ajouter: xmlns: foo = "http://schemas.android.com/apk/res-auto". La police doit se trouver dans le répertoire des ressources

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

bogues connus: ne fonctionne pas avec Android 4.03 - les polices sont invisibles ou très petites (l'avalancha d'origine ne fonctionne pas trop) ci-dessous est une solution de contournement pour ce bogue: https://stackoverflow.com/a/21851239/2075875

Malachiasz
la source
0

Essaye ça

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) { }
        };
Nom oublié
la source
Je ne pense pas que cela fonctionnera, car chaque fois que vous modifiez la taille du texte, cela ne provoquera le dessin qu'après la fin du bloc (ce qui signifie qu'il sera ajouté à la file d'attente des événements). Cela signifie que la boucle ne fonctionnera qu'une fois au maximum à chaque fois que le texte change, et cela ne garantit pas que le nombre de lignes ne dépassera pas 3 lignes de texte.
développeur Android
0

Si vous cherchez quelque chose de plus simple:

 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
la source
Cela semble très court, mais peut-il passer le test que j'ai préparé? De plus, je ne reçois pas lorsque cette fonction est appelée.
développeur Android
Non, c'est encore plus une suggestion. Je vais l'améliorer afin qu'il puisse passer votre test dès que possible.
Sander
Eh bien, si c'est une suggestion, pouvez-vous la publier directement dans Github?
développeur Android
0

Solution rapide pour le problème décrit par @Malachiasz

J'ai résolu le problème en ajoutant une prise en charge personnalisée pour cela dans la classe de redimensionnement automatique:

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

Il suffit d'appeler yourView.setTextCompat(newTextValue)au lieu deyourView.setText(newTextValue)

Ionut Negru
la source
Pourquoi créer une nouvelle fonction de setTextCompat, au lieu de remplacer setText? Et quel problème?
développeur Android
setText () est une méthode finale de TextView, vous ne pourrez donc pas la remplacer. Une autre option serait de le faire en dehors du TextView personnalisé, mais cette solution consiste à l'utiliser à l'intérieur du TextView.
Ionut Negru
Pas exactement. Certains des "setText" sont privés, certains sont publics et certains du public ne sont pas définitifs. Il semble que la plupart peuvent être gérés en remplaçant setText public void (texte CharSequence, type BufferType). Il y a cependant une fonction finale dont je ne connais pas le but: public final void setText (char [] text, int start, int len). Peut-être regarder de plus près le code.
développeur Android
Toutes ces variantes de setText () appelleraient en fait la méthode setText (texte CharSequence) à la fin. Si vous souhaitez garantir le même comportement, vous devrez remplacer cette méthode, sinon il serait préférable d'ajouter simplement votre propre méthode setText ().
Ionut Negru
Oui, mais peut-être que dans la plupart des cas, ça va.
développeur Android
0

Essayez d'ajouter LayoutParamset MaxWidthet MaxHeightà TextView. Cela forcera la disposition à respecter le conteneur parent et non à déborder.

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

int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);` 
user3891647
la source
Je ne sais pas de quoi vous parlez. Suggérez-vous d'ajouter ceci à l'exemple textView, afin qu'il n'ait pas le petit problème que je vois parfois? si oui, pourquoi n'en avez-vous pas posté sur le site github?
développeur android
0

Depuis Android O, il est possible de redimensionner automatiquement le texte 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 vous permet de demander à un TextView de laisser la taille du texte se développer ou se contracter automatiquement pour remplir sa mise en page en fonction des caractéristiques et des limites du TextView. Ce paramètre facilite l'optimisation de la taille du texte sur différents écrans avec un contenu dynamique.

La bibliothèque de prise en charge 26.0 Beta prend entièrement en charge la fonction de redimensionnement automatique de TextView sur les appareils exécutant des versions d'Android antérieures à Android O. La bibliothèque prend en charge Android 4.0 (API niveau 14) et supérieur. Le package android.support.v4.widget contient la classe TextViewCompat pour accéder aux fonctionnalités de façon rétrocompatible.

Javatar
la source
Malheureusement, selon mes tests, et même dans la vidéo de Google IO, j'ai remarqué qu'il y avait des problèmes, tels que des mots partiels mal remplis, au lieu de redimensionner la police. Je l'ai signalé à ce sujet ici: issuetracker.google.com/issues/38468964 , et c'est pourquoi je ne l'utilise toujours pas.
développeur Android
0

Après avoir essayé Autosizing TextView officiel d'Android , j'ai trouvé si votre version d'Android est antérieure à Android 8.0 (API niveau 26), vous devez l'utiliser android.support.v7.widget.AppCompatTextViewet assurez-vous que la version de votre bibliothèque de support est supérieure à 26.0.0. Exemple:

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

mettre à jour :

Selon la réponse de @ android-developer, je vérifie le AppCompatActivitycode source et trouve ces deux lignes dansonCreate

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

et dans AppCompatDelegateImpll »createView

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

il utilise la AppCompatViewInflatervue gonfleur, quand AppCompatViewInflatercreateView utilisera AppCompatTextView pour "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;
    ...
}

Dans mon projet, je n'utilise pas AppCompatActivity, j'ai donc besoin d'une utilisation <android.support.v7.widget.AppCompatTextView>en XML.

weei.zh
la source
1
Dans la plupart des cas, le gonfleur utilise AppCompatTextView lorsque vous écrivez uniquement "TextView", vous n'avez donc pas à le faire.
développeur Android
@androiddeveloper Mais lorsque j'utilise TextView, le dimensionnement automatique ne fonctionne pas.
weei.zh
@androiddeveloper Merci, la raison en est que je ne l'utilise pas AppCompatActivity. AppCompatActivity utilise la disposition du gonfleur AppCompatViewInflater.
weei.zh
Je ne comprends pas
développeur Android