Comment ajuster automatiquement la taille du texte sur un TextView multilignes en fonction des dimensions de la vue max?

j'ai cherché un moyen d'ajuster automatiquement un texte dans un textview. Par ma recherche j'ai trouvé de nombreuses solutions comme:

mais comme beaucoup d'autres cela ne résout pas mon problème. Ils ne fonctionnent pas comme prévu lorsque nous utilisons un TextView avec multiligne.

en gros, mon but est celui-ci:

Phase 1 Phase 2 Phase 3 Phase 4

comme vous pouvez le voir, le texte se redimensionne en fonction de la largeur, de la hauteur et fait également attention à la rupture de ligne, créant un textview multilignes. Également être en mesure de modifier la police de caractères.

une de mes idées pour résoudre ceci était quelque chose comme ceci:

int size = CONSTANT_MAX_SIZE;
TextView tv = (TextView) findViewById(R.id.textview1)
while(Math.abs(tv.getMeasuredHeight()) >= TEXTVIEW_MAX_HEIGHT) {
    size--;
    tv.setTextSize(size);
    tv.measure(MeasureSpec.UNSPECIFIED,MeasureSpec.UNSPECIFIED);
    i++;
}

CONSTANT_MAX_SIZE est une constante qui définit la taille maximale de la police (textsize propriety in a TextView)

TEXTVIEW_MAX_HEIGHT est une constante qui définit la taille maximale que peut avoir textview.

cela s'appelait à chaque fois que le texte dans le textview était modifié.

le xml de textview était quelque chose comme ceci:

<TextView
     android:id="@+id/textview1"
     android:layout_width="200dp"
     android:layout_height="wrap_content"
     android:singleLine="false"
     android:inputType="textMultiLine"
     android:text=""
     android:textSize="150sp" />

puisque la largeur serait limité dans le XML, seule la hauteur de la vue doit être prise en compte car son ajustement android créerait automatiquement un multiligne si nécessaire.

bien qu'il s'agisse d'une solution potentielle ne fonctionne pas parfaitement (loin de là) et il ne supporte pas redimensionner (lorsque vous supprimez du texte).

des suggestions et / ou des idées?

20
demandé sur Community 2013-04-18 16:00:02

6 réponses

pendant que j'attendais une solution possible à cette question, j'ai essayé et essayé de la comprendre.

la solution la plus proche de ce problème était basée sur une méthode à partir de la peinture.

essentiellement la peinture a une méthode appelée 'breaktext' qui:

public int breakText (CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float [] measuredWidth)

ajouté au niveau API 1

mesurer le texte, en s'arrêtant tôt si la largeur mesurée dépasse maxWidth. Retourne le nombre de caractères qui ont été mesurés, et si measuredWidth n'est pas null, y retourner la largeur réelle mesurée.

je le combine avec la peinture "getTextBounds" qui:

public void getTextBounds (String text, int start, int end, Rect bounds)

ajouté au niveau API 1

renvoie dans les limites (attribuées par l'appelant) le plus petit rectangle qui entoure tous les >caractères, avec une origine implicite à (0,0).

Alors maintenant, je peux obtenir le nombre de caractères qui correspondent à une largeur et la hauteur de ces caractères.

en utilisant un certain temps vous pouvez continuer à déplacer les caractères de suppression de la chaîne que vous voulez mesurer et obtenir le nombre de lignes (en utilisant un while (index < string.longueur)) et le multiplier par la hauteur obtenue dans les getTextBounds.

en outre, vous devrez ajouter une hauteur variable pour chaque deux lignes qui représentent l'espace entre les lignes (qui n'est pas compté dans les getTextBounds).

comme exemple de code, la fonction pour connaître la hauteur d'un texte de ligne multiple est quelque chose comme ceci:

public int getHeightOfMultiLineText(String text,int textSize, int maxWidth) {
    paint = new TextPaint();
    paint.setTextSize(textSize);
    int index = 0;
    int linecount = 0;
    while(index < text.length()) {
        index += paint.breakText(text,index,text.length,true,maxWidth,null);
        linecount++;
    }

    Rect bounds = new Rect();
    paint.getTextBounds("Yy", 0, 2, bounds);
    // obtain space between lines
    double lineSpacing = Math.max(0,((lineCount - 1) * bounds.height()*0.25)); 

    return (int)Math.floor(lineSpacing + lineCount * bounds.height());

Note: la variable maxWidth est en pixels

alors vous devrez appeler cette méthode à l'intérieur d'un certain temps pour déterminer quelle est la taille de police maximale pour cette hauteur. Un exemple de code serait:

textSize = 100;
int maxHeight = 50;
while(getHeightOfMultiLineText(text,textSize,maxWidth) > maxHeight)
  textSize--;

malheureusement c'était la seule façon (pour autant que je sache) que j'ai pu obtenir l'aspect des images ci-dessus.

espérons que cela puisse aider quiconque tente de surmonter cet obstacle.

17
répondu nunofmendes 2014-12-12 11:47:14

vous avez une assez bonne solution sur ce problème, mais j'ai pris un regard à ce d'un point de vue différent. Je pense que la logique est plus simple et plus compréhensible, au moins pour les nouveaux développeurs android. L'idée est basée sur l'insertion du caractère '\n' entre les espaces de la grande chaîne. Ensuite, j'ai placé la chaîne de caractères éditée dans textView en tant que texte d'affichage. Donc, voici le code..

private String insertNewLineCharAtString(String str, int step){
    StringBuilder sb = new StringBuilder();
    int spaceCounter = 0;
    String[] strArray = str.split(" ");
    for(int i = 0; i < strArray.length; i++){



        if(spaceCounter == step){
            sb.append('\n');
            sb.append(strArray[i]);
            spaceCounter = 0;
        }else{
            sb.append(" "+strArray[i]);
        }
        spaceCounter++;


    }
    return sb.toString();
}

Les paramètres sont simples. Str est la corde que nous vont éditer et la variable step définit dans combien d'espaces la méthode va insérer le caractère '/n'. Ensuite, vous appelez quelque chose comme ceci:

myTextView.setText(insertNewLineCharactersAtString(yourDisplayingStr);

espérons que cela aide beaucoup d'entre vous que vous ne voulez pas aller plus loin avec les fonctions TextPaint , Rects, and math..

1
répondu karvoynistas 2013-09-26 15:49:42

merci pour votre solution vous êtes superman! J'ai implémenté votre code dans la classe dérivée de TextView. Le code est converti en code C# pour Xamarin android. Vous pouvez facilement convertir en Java si vous avez besoin(supprimer le premier constructeur pour Java).

public class AutoFitTextView : TextView
{
    public AutoFitTextView(System.IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer)
        : base(javaReference, transfer)
    {
    }

    public AutoFitTextView(Context context)
        : base(context)
    {

    }

    public AutoFitTextView(Context context, IAttributeSet attrs)
        : base(context, attrs)
    {

    }


    public void ResizeFontSize()
    {
        int textSize = 50;
        int maxHeight = this.Height;
        while (GetHeightOfMultiLineText(this.Text, textSize, this.Width) > maxHeight)
        {
            textSize--;
        }
        float scaleFactor = Context.Resources.DisplayMetrics.ScaledDensity;
        float additionalFactor = 1.2f;

        TextSize = ((float)(textSize / (additionalFactor * scaleFactor)));
    }


    private int GetHeightOfMultiLineText(string text, int textSize, int maxWidth)
    {
        TextPaint paint = new TextPaint();
        paint.TextSize = textSize;
        int index = 0;
        int lineCount = 0;
        while (index < text.Length)
        {
            index += paint.BreakText(text, index, text.Length, true, maxWidth, null);
            lineCount++;
        }

        Rect bounds = new Rect();
        paint.GetTextBounds("Yy", 0, 2, bounds);
        // obtain space between lines
        double lineSpacing = Math.Max(0, ((lineCount - 1) * bounds.Height() * 0.25));

        return (int)Math.Floor(lineSpacing + lineCount * bounds.Height());
    }

}

voici le code AXML

        <touchtest.AutoFitTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/tvLabel2"
        android:singleLine="false"
        android:inputType="textMultiLine"
        android:text="I am here To test this text" />
1
répondu Xusan 2013-11-21 04:06:14

enter image description here enter image description here enter image description here

une réponse un peu améliorée de phamducgiam. Il montre le texte avec la taille déjà ajustée, pas skretchs après avoir montré. Looks Laid In editor en raison de commencer textSize 100, mais fonctionne comme il devrait dans l'exécution. Voici le code:

import android.content.Context;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

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

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

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

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // start decreasing font size from 100
        setTextSize(TypedValue.COMPLEX_UNIT_PX, 100);

        // calculate dimensions. don't forget about padding
        final float maxWidth = getWidth()-(getPaddingLeft()+getPaddingRight());
        final float maxHeight = getHeight()-(getPaddingTop()+getPaddingBottom());

        if (maxWidth < 1.0f || maxHeight < 1.0f) {
            return;
        }

        CharSequence text = getText();
        int lineCount = getLineCount(maxWidth, text);
        float height = getHeight(lineCount);
        // keep decreasing font size until it fits
        while (height > maxHeight) {
            final float textSize = getTextSize();
            setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
            height = getHeight(getLineCount(maxWidth, getText()));
        }
        // show fitted textView
        requestLayout();
    }

    private float getHeight(int lineCount) {
        return lineCount * getLineHeight() + (lineCount > 0 ? (lineCount - 1) * getPaint().getFontSpacing() : 0);
    }

    private int getLineCount(float maxWidth, CharSequence text) {
        int lineCount = 0;
        int index = 0;
        final TextPaint paint = getPaint();
        while (index < text.length()) {
            index += paint.breakText(text, index, text.length(), true, maxWidth, null);
            lineCount++;
        }
        return lineCount;
    }
}
1
répondu Red Hot Chili Coder 2015-09-10 04:05:00

en utilisant l'idée de nunofmendes , j'ai écrit une classe dérivée de TextView qui redimensionne automatiquement le texte et supporte plusieurs lignes.

import android.content.Context;
import android.os.Handler;
import android.text.Layout;
import android.text.TextPaint;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.TypedValue;

public class AutoResizeTextView extends TextView {

    private Handler measureHandler = new Handler();
    private Runnable requestLayout = new Runnable() {
        @Override
        public void run() {
            requestLayout();
        }
    };

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

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

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

    @Override
    protected void onMeasure(final int widthMeasureSpec,
                             final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final float maxWidth = getWidth();
        final float maxHeight = getHeight();
        if (maxWidth < 1.0f || maxHeight < 1.0f) {
            return;
        }

        int index = 0;
        int lineCount = 0;
        CharSequence text = getText();
        final TextPaint paint = getPaint();
        while (index < text.length()) {
            index += paint.breakText(text, index, text.length(), true, maxWidth, null);
            lineCount++;
        }
        final float height = lineCount * getLineHeight() + (lineCount > 0 ?
                (lineCount - 1) * paint.getFontSpacing() : 0);
        if (height > maxHeight) {
            final float textSize = getTextSize();
            setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
            measureHandler.post(requestLayout);
        }
    }
}
1
répondu phamducgiam 2017-05-23 11:54:12

La compilable, à code fixe: copier-coller de celui-ci, pas la accepté, parce que vous avez besoin pour résoudre le problème:)

public static int getHeightOfMultiLineText(String text, int textSize, int maxWidth) {
    TextPaint paint = new TextPaint();
    paint.setTextSize(textSize);
    int index = 0;
    int lineCount = 0;
    while (index < text.length()) {
        index += paint.breakText(text, index, text.length(), true, maxWidth, null);
        lineCount++;
    }

    Rect bounds = new Rect();
    paint.getTextBounds("Yy", 0, 2, bounds);
    // obtain space between lines
    double lineSpacing = Math.max(0, ((lineCount - 1) * bounds.height() * 0.25));

    return (int) Math.floor(lineSpacing + lineCount * bounds.height());
}

à utiliser, merci.

Important: vous devez tenir compte du facteur d'échelle

    int textSize = 50;
    int maxHeight = boundsTv.height();
    while (getHeightOfMultiLineText(text, textSize, params.width) > maxHeight) {
        textSize--;
    }
    float scaleFactor =  mainActivity.getResources().getDisplayMetrics().scaledDensity;     
    float temp = 1.2f;

    tvText.setTextSize((float) (textSize / (temp*scaleFactor)));
-2
répondu pandalion98 2015-12-20 05:31:35