web-dev-qa-db-fra.com

Ajuster automatiquement TextView pour Android

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 nombreuses discussions et publications (et des solutions suggérées) concernant ce problème (exemple ici , ici et ici ), aucun d'entre eux fonctionnent réellement 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'un tel textView devraient être:

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

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

  3. Aucune troncature, à moins que le texte ne puisse tenir à cause de la limitation que nous lui 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, uniquement pour ces cas.

  4. Devrait permettre plusieurs lignes ou une seule ligne. En cas de multiligne, autoriser les lignes max et min.

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

  6. En cas de multiligne, devrait permettre de préférer redimensionner ou utiliser plus de 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é beaucoup d'échantillons (y compris ceux des liens sur lesquels j'ai écrit) et j'ai également essayé de les modifier pour gérer les cas dont j'ai parlé, mais aucun ne fonctionne vraiment.

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

Actuellement, mon exemple de projet ne fait que randomiser le texte (l'alphabet anglais plus les chiffres) et la taille de textView, et le laisse rester sur une seule ligne, même si cela ne fonctionne pas avec les é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

Est-ce que quelqu'un connaît une solution à ce problème commun qui fonctionne réellement?

Même une solution qui a beaucoup moins de fonctionnalités que ce que j'ai écrit à propos, par exemple une qui a juste un nombre constant de lignes de texte et ajuste sa police en fonction de sa taille, sans jamais avoir de petits problèmes bizarres et ayant le texte trop grand/petit comparé à 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 .

228
android developer

Grâce au correctif simple ici de MartinH, ce code prend également en charge les balises Android:drawableLeft, Android:drawableRight, Android:drawableTop et Android:drawableBottom.


Ma réponse ici devrait vous rendre heureux Mise à l'échelle automatique du texte TextView pour l'adapter 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 poste le code ici à l'adresse per Android du développeur request:

Effet final:

Enter image description here

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:

Attention cependant à ce résolubug dans Android 3.1 (Honeycomb).

144
M-WaJeEh

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

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

Problème: Correction de la mesure de la largeur et de la hauteur de l'espace TextView disponible pour le texte. Si nous ne tenons pas compte de la taille pouvant être dessinée, celle-ci est ignorée et le texte finira par recouvrir 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));
}
14
MartinH

Ok, j’ai utilisé la semaine dernière pour réécrire massivement mon code afin de correspondre exactement à votre test. Vous pouvez maintenant copier ce 1: 1 et cela fonctionnera immédiatement - y compris setSingleLine(). N'oubliez pas de régler MIN_TEXT_SIZE et MAX_TEXT_SIZE si 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 se trouve ici.

Le résultat est très flexible maintenant. Cela fonctionne de la même façon que dans le fichier 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 programme comme dans votre test.

J'espère vraiment que vous pourrez l'utiliser maintenant. Vous pouvez appeler setText(CharSequence text) maintenant pour l’utiliser au passage. La classe s'occupe des exceptions extrêmement rares et devrait être solide comme un roc. La seule chose que l'algorithme ne supporte pas n'est pas encore est:

  • Appels à setMaxLines(x)x >= 2

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


Remarque:

Si vous utilisez simplement ceci normalement sans le limiter à une seule ligne, il se peut que ce soit un casse-mot comme vous l'avez mentionné auparavant. Ceci est une fonction Android , et non le faute de la AutoFitText. Android cassera toujours les mots trop longs pour TextView, ce qui est très pratique. Si vous souhaitez intervenir ici, veuillez lire mes commentaires et le code commençant à la ligne 203. J'ai déjà écrit une scission adéquate et une reconnaissance pour vous, il ne vous reste plus qu'à diviser les mots, puis à les modifier à votre guise. .

En conclusion: Vous devriez fortement envisager de réécrire votre test pour qu'il prenne é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 lancer dans cette affaire.

Bonne chance et meilleures salutations

7
avalancha

Mon exigence est de

  • Cliquez sur le 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é.

Je me suis référé au lien: Texte TextView à l’échelle automatique pour s’adapter aux limites (incluant les commentaires) et aussi le DialogTitle.Java

J'ai trouvé que la solution fournie était simple et agréable, mais qu'elle ne modifiait pas dynamiquement la taille de la zone de texte. Ça marche bien quand la longueur du texte sélectionné dans la liste est plus grande que la longueur du texte existant dans la liste ScalableTextView. Lorsque vous sélectionnez un texte dont la longueur est inférieure à celle du texte existant dans la section ScalableTextView, il n’augmente pas la taille du texte, le texte étant ainsi réduit.

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

Bonne codage ....

4
Devendra Vaja

Attention, bug dans Android 3 (Nid d'abeille) et Android 4.0 (Sandwich à la crème glacée)

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

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

La solution de contournement consiste à ajouter un caractère de nouvelle ligne au texte attribué à 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 maintenir centré. Si vous l'avez aligné à gauche, ajoutez uniquement à droite. Bien sûr, il peut également être incorporé avec le widget AutoResizeTextView, mais je voulais garder le code fixe à l’extérieur.

3
Malachiasz

Il existe maintenant une solution officielle à ce problème. Le redimensionnement automatique des TextViews introduit avec Android O est disponible dans la bibliothèque de support technique 26 et est compatible avec les versions antérieures jusqu'à Android 4.0.

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

Je ne sais pas pourquoi https://stackoverflow.com/a/42940171/4768 qui incluait également cette information a été supprimé par un administrateur.

3

Je vais expliquer comment fonctionne cet attribut inférieur Android versions:

1- Importez Android la bibliothèque de support 26.x.x dans votre fichier de graduation de projet. S'il n'y a pas de bibliothèque de support sur l'EDI, 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 votre refactor comme ceci: balisez votre TextView. Ce scénario est le suivant: lors de l’incrémentation de la taille de la police sur le système, ajustez le texte à une largeur disponible, et non à un 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" />
3
Sinan Ergin

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

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

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

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

Découvrez mes détails réponse .

3

Convertissez la vue texte en image et redimensionnez l'image dans les limites.

Voici un exemple sur la façon de convertir une vue en image: Conversion d'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 n'ai pas essayé, donc je ne sais pas à quoi ça ressemblerait (à cause de la mise à l'échelle).

1
Dr NotSoKind

Essayez d’ajouter LayoutParams et MaxWidth et MaxHeight à la TextView. Cela forcera la mise en page à respecter le conteneur parent et à ne pas déborder.

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

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

Solution rapide au problème décrit par @Malachiasz

J'ai résolu le problème en ajoutant un support personnalisé 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 de yourView.setText(newTextValue)

0
Ionut Negru

Depuis Android O, il est possible de redimensionner automatiquement le texte au format 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 s'agrandir 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 à contenu dynamique.

La bibliothèque de support 26.0 bêta offre une prise en charge complète de la fonctionnalité de redimensionnement automatique de TextView sur les appareils exécutant les versions de Androidantérieures à Android O. La bibliothèque prend en charge Android 4.0 (API niveau 14) et plus. Le package Android.support.v4.widget contient la classe TextViewCompat permettant d'accéder aux fonctionnalités de manière rétro-compatible.

0
Javatar

Vous trouverez ci-dessous aval TextView avec des fonctionnalités supplémentaires pour les polices personnalisées.

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 être dans le répertoire des actifs

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 connus: Ne fonctionne pas avec Android 4.03 - les polices sont invisibles ou très petites (l’avalanche d'origine ne fonctionne pas aussi). La solution de contournement pour ce bogue est la suivante: https://stackoverflow.com/a/21851239/2075875

0
Malachiasz

Après avoir essayé Android officiel Autosizing TextView , j’ai trouvé si votre version de Android est antérieure à Android 8.0 (API niveau 26), vous devez utiliser Android.support.v7.widget.AppCompatTextView et vous assurer 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 code source AppCompatActivity et trouve ces deux lignes dans onCreate

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

et dans AppCompatDelegateImpl 'createView

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

il utilise la vue AppCompatViewInflater inflater, lorsque AppCompatViewInflater createView 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'utiliser <Android.support.v7.widget.AppCompatTextView> in xml.

0
weei.zh

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) { }
        };
0
Forgot Name

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