Comment ajuster la taille de la police du texte pour l'adapter à la vue du texte

180

Existe-t-il un moyen dans Android d'ajuster la taille du texte dans une vue de texte pour l'adapter à l'espace qu'elle occupe?

Par exemple, j'utilise un TableLayoutet j'ajoute plusieurs TextViews à chaque ligne. Comme je ne veux pas que les TextViews enveloppent le texte, je vois plutôt que cela réduit la taille de la police du contenu.

Des idées?

J'ai essayé measureText, mais comme je ne connais pas la taille de la colonne, il semble difficile à utiliser. C'est le code où je veux changer la taille de la police en quelque chose qui convient

TableRow row = new TableRow(this);   
for (int i=0; i < ColumnNames.length; i++) {    
    TextView textColumn = new TextView(this);      
    textColumn.setText(ColumnNames[i]);
    textColumn.setPadding(0, 0, 1, 0);
    textColumn.setTextColor(getResources().getColor(R.drawable.text_default));          
    row.addView(textColumn, new TableRow.LayoutParams()); 
} 
table.addView(row, new TableLayout.LayoutParams());  
Rudas
la source
Vérifiez ma solution basée sur le code de dunni ici stackoverflow.com/questions/5033012/... Remarque: je ne l'ai pas implémentée avec une boucle. PS: merci dunni
vsm

Réponses:

131

La solution ci-dessous intègre toutes les suggestions ici. Cela commence par ce qui a été initialement publié par Dunni. Il utilise une recherche binaire comme celle de gjpc, mais c'est un peu plus lisible. Il inclut également les corrections de bogues de gregm et une correction de bogue de ma part.

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

public class FontFitTextView extends TextView {

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

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

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

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

        mTestPaint.set(this.getPaint());

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

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

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

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

    //Attributes
    private Paint mTestPaint;
}
speedplane
la source
9
Merci d'avoir combiné tous les commentaires. Je vois que la solution ne prend en considération que la largeur . Mon problème est que le fond dépasse la hauteur.
AlikElzin-kilaka
3
Oh, cela semble fonctionner partiellement au moment de l'exécution. Sur un appareil ou un émulateur, le texte est coupé à mi-hauteur. Sur l'éditeur de mise en page Eclipse, ça a l'air bien. Des idées?
AlikElzin-kilaka
1
ce message semble faire l'affaire (ajout d'attributs xml personnalisés pour hi / lo): stackoverflow.com/a/8090772/156611
Ed Sinek
7
C'était une excellente solution! Au cas où quelqu'un d'autre serait nouveau dans le développement Android et ne sait pas vraiment comment implémenter une vue étendue en XML, cela ressemble à ceci: <com.example.zengame1.FontFitTextView android:paddingTop="5dip" android:id="@+id/childs_name" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:layout_gravity="center" android:textSize="@dimen/text_size"/>
Casey Murray
2
Bon travail! Ça fonctionne aussi avec des boutons. Pour prendre soin de la hauteur de la vue du texte, il suffit de définirfloat hi = this.getHeight() - this.getPaddingBottom() - this.getPaddingTop();
AlexGuti
29

J'ai écrit une classe qui étend TextView et fait cela. Il utilise simplement measureText comme vous le suggérez. Fondamentalement, il a une taille de texte maximale et une taille de texte minimale (qui peut être modifiée) et il parcourt simplement les tailles entre eux par décréments de 1 jusqu'à ce qu'il trouve la plus grande qui conviendra. Pas particulièrement élégant, mais je ne connais pas d'autre moyen.

Voici le code:

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

public class FontFitTextView extends TextView {

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

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

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

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

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

            this.setTextSize(trySize);
        }
    }

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

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

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

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

    public float getMaxTextSize() {
        return maxTextSize;
    }

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

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

}
Dunni
la source
11
Une recherche binaire serait généralement plus rapide qu'une recherche linéaire.
Ted Hopp
je pense qu'il serait plus raisonnable de faire refitText à "onLayout" plutôt que "onSizeChanged"
LiangWang
Pouvez-vous s'il vous plaît jeter un oeil à mon problème stackoverflow.com/questions/36265448/…
Muhammad
18

Il s'agit de speedplaneFontFitTextView , mais il ne réduit la taille de la police que si nécessaire pour adapter le texte et conserve sa taille de police dans le cas contraire. Il n'augmente pas la taille de la police pour l'adapter à la hauteur.

public class FontFitTextView extends TextView {

    // Attributes
    private Paint mTestPaint;
    private float defaultTextSize;

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

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

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

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

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

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

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

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

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

        }

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

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

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

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

}

Voici un exemple comment il pourrait être utilisé en xml:

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

Cela garderait la taille de la police à 60sp tant que le texte tient dans la largeur. Si le texte est plus long, cela réduira la taille de la police. Dans ce cas, la TextViewhauteur s changera également en raison de height=wrap_content.

Si vous trouvez des bogues, n'hésitez pas à les modifier.

sulai
la source
Comment l'avez-vous utilisé? J'ai ajouté un exemple de la façon dont je l'utilise à ma réponse. Je l'ai testé avec différentes versions d'Android, des émulateurs et l'éditeur de mise en page graphique ADT.
sulai
Essentiellement, je ne spécifiais pas de hauteur spécifique. Votre onMeasure permet à la vue de prendre le relais si elle le souhaite. J'ai réussi à trouver une solution, en changeant la dernière ligne de la routine enthis.setMeasuredDimension(parentWidth, height);
PearsonArtPhoto
1
Excellente contribution, j'ai corrigé le code :) Maintenant, cela fonctionne avec match_parentet wrap_content.
sulai du
Pourquoi testez-vous une chaîne vide en écrivant "" .equals (s) au lieu de simplement s.isEmpty ()? Ou s.length () == 0? Je ne comprends pas pourquoi je peux parfois voir ces tests d'égalité.
Zordid
1
@PratikButani merci d'avoir signalé cela. l'équivalent de text.isEmpty()serait "".equals(text).
sulai
14

Voici ma solution qui fonctionne sur l'émulateur et les téléphones mais pas très bien sur l'éditeur de mise en page Eclipse. Il est inspiré du code de kilaka mais la taille du texte n'est pas obtenue à partir du Paint mais de la mesure du TextView lui-même appelant measure(0, 0).

La classe Java:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    public int getMinTextSize() {
            return minTextSize;
    }

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

    public int getMaxTextSize() {
            return maxTextSize;
    }

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

Le fichier d'attributs XML:

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

Vérifiez mon github pour la dernière version de cette classe. J'espère que cela peut être utile pour quelqu'un. Si un bogue est trouvé ou si le code nécessite des explications, n'hésitez pas à ouvrir un problème sur Github.

yDelouis
la source
Cela fonctionne parfaitement sur mon Galaxy Nexus, mais j'ai des problèmes sur les appareils avec de petits écrans.
Tony Ceralva
Quel est votre problème sur les appareils dotés de petits écrans?
yDelouis
Désolé, le problème n'est pas avec les petits écrans, votre vue ne fonctionne pas sur tous les appareils avec Android 4.0, émulateur inclus. J'ai ouvert un nouveau numéro dans votre GitHub
Tony Ceralva
12

Merci beaucoup à https://stackoverflow.com/users/234270/speedplane . Très bonne réponse!

Voici une version améliorée de sa réponse qui prend également en charge la hauteur et est livrée avec un attribut maxFontSize pour limiter la taille de la police (c'était utile dans mon cas, je voulais donc la partager):

package com.<your_package>;

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


public class FontFitTextView extends TextView
{

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

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

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

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

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

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

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

        mTestPaint.set(this.getPaint());

        Rect bounds = new Rect();

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

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

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

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

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

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

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

Fichier /res/values/attr.xml correspondant:

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

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

</resources>

Exemple:

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

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

...
</RelativeLayout>

Pour utiliser le nouvel maxFontSizeattribut, n'oubliez pas d'ajouter xmlns:res-auto="http://schemas.android.com/apk/res-auto"comme indiqué dans l'exemple.

Pascal
la source
Désolé, mais cela ne fonctionne toujours pas dans tous les cas. Je pense également qu'il ne gère pas correctement les lignes multiples.
développeur android
Oh .. Pouvez-vous me donner un cas pour lequel ça ne marche pas? (La multi-ligne n'est pas prise en charge, vous avez raison)
Pascal
De nombreux cas. J'ai créé un testeur aléatoire juste pour prouver qu'il est correct. Voici un exemple: largeur de la vue: 317px, hauteur: 137px, texte: "q7Lr". Voici ce que je vois: tinypic.com/view.php?pic=2dv5yf9&s=6 . voici l'exemple de projet que j'ai réalisé: mega.co.nz/… . Je pense qu'une telle vue devrait gérer plusieurs lignes, prendre en charge toutes les tailles de polices, gérer la hauteur et pas seulement la largeur, ... Malheureusement, aucun des échantillons que j'ai trouvés n'a bien fonctionné.
développeur android
ok, certainement pas une solution complète. Pardon. Pas le temps de l'améliorer pour le moment .. Si vous pouvez l'améliorer, n'hésitez pas à poster votre solution.
Pascal
1
J'ai créé un nouveau fil de discussion qui montre mes tests, en espérant que quelqu'un serait capable de faire une bonne solution: stackoverflow.com/questions/16017165/...
développeur android
9

Vous pouvez maintenant le faire sans bibliothèque tierce ni widget. Il est intégré à TextView au niveau de l'API 26. Ajoutez- android:autoSizeTextType="uniform"y votre TextViewhauteur et définissez-la. C'est tout. Utiliser app:autoSizeTextType="uniform"pour la compatibilité descendante

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

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

Vous pouvez également utiliser TextViewCompatpour la compatibilité.

arsent
la source
Utilisez app: autoSizeTextType = "uniform" pour la compatibilité descendante, car android: autoSizeTextType = "uniform" ne fonctionne que dans l'API de niveau 26 et supérieur.
Atul Bhardwaj le
7

J'ai eu le même problème et j'ai écrit un cours qui semble fonctionner pour moi. Fondamentalement, j'ai utilisé une mise en page statique pour dessiner le texte dans un canevas séparé et remesurer jusqu'à ce que je trouve une taille de police qui convient. Vous pouvez voir la classe publiée dans le sujet ci-dessous. J'espère que cela aide.

Mise à l'échelle automatique du texte TextView pour s'adapter aux limites

Chasse
la source
5

Légère modification de onMeasure:

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

Et recherche binaire sur refitText:

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

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

        this.setTextSize( TypedValue.COMPLEX_UNIT_PX, trySize);
    }
}
gjpc
la source
3
Au lieu d'une recherche binaire, une simple mise à l'échelle fonctionne plutôt bien: trySize *= availableWidth / measured_width(puis fixée à minTextSize).
Ted Hopp
5

J'ai trouvé que ce qui suit fonctionnait bien pour moi. Il ne boucle pas et tient compte à la fois de la hauteur et de la largeur. Notez qu'il est important de spécifier l'unité PX lors de l'appel de setTextSize sur la vue. Merci à l'astuce dans un article précédent pour cela!

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

Voici la routine que j'utilise, en passant le getPaint () depuis la vue. Une chaîne de 10 caractères avec un caractère «large» est utilisée pour estimer la largeur indépendamment de la chaîne réelle.

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

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

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

    return paint;
}
Glenn
la source
Comment l'incorporer dans une mise en page xml?
AlikElzin-kilaka
Ce code est utile pour placer du texte dans une vue existante dans une zone de taille contrainte ou vous pouvez créer votre propre classe dérivée à partir de TextView et remplacer le onMeasure comme indiqué dans d'autres articles. En soi, il ne peut pas être utilisé dans une mise en page.
Glenn
Assurez-vous de vérifier les dimensions de la vue une fois qu'elle a été dessinée. Faire cela pendant onCreate () est trop tôt. J'ai utilisé ViewTreeObserver pour m'assurer que les mesures étaient prises au bon moment.
SMBiggs
5

À utiliser app:autoSizeTextType="uniform"pour la compatibilité descendante, car android:autoSizeTextType="uniform"ne fonctionne qu'avec le niveau d'API 26 et supérieur.

Suraj Vaishnav
la source
Merci Suraj. C'est la meilleure solution
Atul Bhardwaj le
Je l'ai vérifié sur le SDK 24 et cela ne fonctionne pas. Êtes-vous sûr que app:..cela fonctionnera pour SDK low 26?
Georgiy Chebotarev le
Oui, cela fonctionne comme un espace de noms d'application offrant une compatibilité descendante. Il y a un article détaillé à ce sujet, vérifiez ceci peut-être que cela résoudrait votre problème. medium.com/over-engineering/…
Suraj Vaishnav
4

Fonctionne avec modification

Vous devez définir la taille de la vue du texte comme ceci, car sinon setTextSize suppose que la valeur est en unités SP:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

Et vous deviez ajouter explicitement ce code.

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

J'ai eu cette douleur dans mes projets pendant tellement longtemps jusqu'à ce que je trouve cette bibliothèque:

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

Il vous suffit d'ajouter le xml selon vos besoins et c'est fait. Par exemple:

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

J'ai utilisé une variante de la solution Dunni ci-dessus, mais ce code particulier n'a pas fonctionné pour moi. En particulier, lorsque vous essayez d'utiliser le jeu d'objets Paint pour avoir les caractéristiques de l'objet Paint de la vue, puis que vous appelez measureText (), il ne renvoie pas la même valeur que l'appel direct de l'objet Paint de la vue. Peut-être y a-t-il des différences dans la façon dont mes opinions sont établies qui rendent le comportement différent.

Ma solution consistait à utiliser directement la peinture de la vue, même s'il pouvait y avoir des pénalités de performance en changeant la taille de la police pour la vue plusieurs fois.

ThomasW
la source
2

J'ai travaillé à l'amélioration de l'excellente solution de speedplane et j'ai trouvé ça. Il gère la hauteur, y compris la définition de la marge pour que le texte soit correctement centré verticalement.

Cela utilise la même fonction pour obtenir la largeur, car cela semble fonctionner le mieux, mais il utilise une fonction différente pour obtenir la hauteur, car la hauteur n'est fournie nulle part. Il y a quelques corrections à apporter, mais j'ai trouvé un moyen de le faire, tout en ayant l'air agréable à regarder.

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

public class FontFitTextView extends TextView {

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

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

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

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

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

        Rect bounds = new Rect();

        mTestPaint.set(this.getPaint());

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

    }

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

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

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

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

    //Attributes
    private Paint mTestPaint;
}
PearsonArtPhoto
la source
0

Inspiré des affiches précédentes, j'ai voulu partager ma solution. Il fonctionne avec un facteur d'échelle qui est appliqué à la taille de police précédente pour l'adapter à l'espace disponible. En plus d'éviter un comportement inattendu de la méthode TextViews onDraw, elle dessine simplement le texte seul.

public class FontFitTextView extends TextView {

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

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

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

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

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

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

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

    private void calcTextSize(String s, Canvas c) {

        float availableHeight = viewHeight;
        float availableWidth = viewWidth;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// LayoutParams lp = new LayoutParams();

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

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

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

return l;
Dhwanik Gandhi
la source
1
En lisant simplement votre code, je ne pense pas que cela fonctionnera. N'oubliez pas que la question portait sur l'ajustement automatique de la taille de la police de la vue texte pour qu'elle s'adapte à la vue. Vous définissez une taille de police fixe.
sulai
0

Cela devrait être une solution simple:

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

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

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

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

Étendez TextView et remplacez onDraw avec le code ci-dessous. Il conservera les proportions du texte mais le dimensionnera pour remplir l'espace. Vous pouvez facilement modifier le code pour l'étirer si nécessaire.

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

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

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

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

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

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

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

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

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

J'ai écrit une courte classe d'aide qui fait tenir une vue de texte dans une certaine largeur et ajoute ellipsize "..." à la fin si la taille minimale du texte ne peut pas être atteinte.

Gardez à l'esprit que cela ne réduit le texte que jusqu'à ce qu'il tienne ou jusqu'à ce que la taille minimale du texte soit atteinte. Pour tester avec de grandes tailles, définissez la taille du texte sur un grand nombre avant d'appeler la méthode d'aide.

Cela prend des pixels, donc si vous utilisez des valeurs de dimen, vous pouvez l'appeler comme ceci:


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

C'est la classe que j'utilise:


public class WidgetUtils {

    public static void fitText(TextView textView, String text, float minTextSizePx, float maxWidthPx) {
        textView.setEllipsize(null);
        int size = (int)textView.getTextSize();
        while (true) {
            Rect bounds = new Rect();
            Paint textPaint = textView.getPaint();
            textPaint.getTextBounds(text, 0, text.length(), bounds);
            if(bounds.width() < maxWidthPx){
                break;
            }
            if (size <= minTextSizePx) {
                textView.setEllipsize(TextUtils.TruncateAt.END);
                break;
            }
            size -= 1;
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
        }
    }
}
Björn Kechel
la source
Cela prend-il en charge plusieurs lignes de texte?
Roymunson
0

Si une transformation comme allCaps est définie, l'approche du speedplane est boguée. Je l'ai corrigé, ce qui a donné le code suivant (désolé, ma réputation ne me permet pas d'ajouter cela en commentaire à la solution de speedplane):

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

public class FontFitTextView extends TextView {

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

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

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

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

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

        mTestPaint.set(this.getPaint());

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

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

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

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

    //Attributes
    private Paint mTestPaint;
}
dpoetzsch
la source
0

Je ne savais pas que c'était la bonne façon ou non parce que cela fonctionnait ... prenez votre avis et vérifiez OnGlobalLayoutListener () et obtenez le nombre de lignes textview, puis définissez textSize.

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

Son code en quelques lignes très simple.

Mathan Chinna
la source