web-dev-qa-db-fra.com

Comment redimensionner/redimensionner le texte pour l'adapter à TextView?

J'essaie de créer une méthode de redimensionnement d'un texte multiligne dans une variable TextView telle qu'elle s'adapte aux limites (dimensions X et Y) de la variable TextView.

Pour le moment, j'ai quelque chose à faire, mais tout ce que cela fait est de redimensionner le texte de sorte que seule la première lettre/caractère du texte remplisse les dimensions de la variable TextView (c'est-à-dire que seule la première lettre est visible et qu'elle est énorme). J'en ai besoin pour que toutes les lignes du texte tiennent dans les limites de TextView.

Voici ce que j'ai jusqu'à présent:

public static void autoScaleTextViewTextToHeight(TextView tv)
{
    final float initSize = tv.getTextSize();
    //get the width of the view's back image (unscaled).... 
    float minViewHeight;
    if(tv.getBackground()!=null)
    {
      minViewHeight = tv.getBackground().getIntrinsicHeight();
    }
    else
    {
      minViewHeight = 10f;//some min.
    }
    final float maxViewHeight = tv.getHeight() - (tv.getPaddingBottom()+tv.getPaddingTop())-12;// -12 just to be sure
    final String s = tv.getText().toString();

    //System.out.println(""+tv.getPaddingTop()+"/"+tv.getPaddingBottom());

    if(minViewHeight >0 && maxViewHeight >2)
    {
      Rect currentBounds = new Rect();
      tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
      //System.out.println(""+initSize);
      //System.out.println(""+maxViewHeight);
      //System.out.println(""+(currentBounds.height()));

      float resultingSize = 1;
      while(currentBounds.height() < maxViewHeight)
      {
        resultingSize ++;
        tv.setTextSize(resultingSize);

        tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
        //System.out.println(""+(currentBounds.height()+tv.getPaddingBottom()+tv.getPaddingTop()));
        //System.out.println("Resulting: "+resultingSize);
      }
      if(currentBounds.height()>=maxViewHeight)
      {
        //just to be sure, reduce the value
        tv.setTextSize(resultingSize-1);
      }
    }
}

Je pense que le problème réside dans l'utilisation de tv.getPaint().getTextBounds(...). Il renvoie toujours de petits nombres pour les limites du texte ... petit par rapport aux valeurs tv.getWidth() et tv.getHeight() ... même si la taille du texte est beaucoup plus grande que la largeur ou la hauteur de la TextView.

27
RyanM

La bibliothèque AutofitTextView de MavenCentral s’occupe bien de cela. La source hébergée sur Github (1k + étoiles) à https://github.com/grantland/Android-autofittextview

Ajoutez ce qui suit à votre app/build.gradle

repositories {
    mavenCentral()
}

dependencies {
    compile 'me.grantland:autofittextview:0.2.+'
}

Activez n’importe quelle vue qui étend TextView dans le code:

AutofitHelper.create(textView);

Activez n’importe quelle vue qui étend TextView en XML:

<me.grantland.widget.AutofitLayout
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    >
    <Button
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:singleLine="true"
        />
</me.grantland.widget.AutofitLayout>

Utilisez le widget intégré en code ou XML:

<me.grantland.widget.AutofitTextView
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:singleLine="true"
    />
20
13rac1

Nouveau depuis Android O:

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

<TextView
  Android:layout_width="wrap_content"
  Android:layout_height="wrap_content"
  Android:autoSizeTextType="uniform"
  Android:autoSizeMinTextSize="12sp"
  Android:autoSizeMaxTextSize="100sp"
  Android:autoSizeStepGranularity="2sp"
/>
5
Javatar

Cela fait un certain temps que je joue à cela, essayant de corriger la taille de mes polices sur une grande variété de tablettes 7 "(Kindle Fire, Nexus7 et quelques-unes peu coûteuses en Chine avec des écrans basse résolution) et des périphériques.

L'approche qui a finalement fonctionné pour moi est la suivante. Le "32" est un facteur arbitraire qui donne environ 70 caractères sur une ligne horizontale de 7 ", qui correspond à la taille de la police que je recherchais. Réglez en conséquence.

textView.setTextSize(getFontSize(activity));


public static int getFontSize (Activity activity) { 

    DisplayMetrics dMetrics = new DisplayMetrics();
    activity.getWindowManager().getDefaultDisplay().getMetrics(dMetrics);

    // lets try to get them back a font size realtive to the pixel width of the screen
    final float WIDE = activity.getResources().getDisplayMetrics().widthPixels;
    int valueWide = (int)(WIDE / 32.0f / (dMetrics.scaledDensity));
    return valueWide;
}
3
Mark W

J'ai pu répondre à ma propre question en utilisant le code suivant (voir ci-dessous), mais ma solution était très spécifique à l'application. Par exemple, cela ne sera probablement bon et/ou ne fonctionnera que pour un TextView de taille env. La moitié de l'écran (avec également une marge supérieure de 40 pixels et des marges latérales de 20 pixels ... pas de marge inférieure).

En utilisant cette approche, vous pouvez créer votre propre implémentation similaire. La méthode statique se contente essentiellement de regarder le nombre de caractères et détermine un facteur d’échelle à appliquer à la taille du texte du TextView, puis augmente progressivement la taille du texte jusqu’à la hauteur totale (hauteur estimée - en utilisant la largeur du texte, le texte hauteur, et la largeur de TextView) est juste en dessous de celle de TextView. Les paramètres nécessaires pour déterminer le facteur d’échelle (c’est-à-dire les instructions if/else if) ont été définis par essai-et-vérification. Vous devrez probablement jouer avec les chiffres pour le faire fonctionner pour votre application particulière.

Ce n’est pas la solution la plus élégante, même s’il était facile de coder et que cela fonctionne pour moi. Est-ce que quelqu'un a une meilleure approche?

public static void autoScaleTextViewTextToHeight(final TextView tv, String s)
    {       
        float currentWidth=tv.getPaint().measureText(s);
        int scalingFactor = 0;
        final int characters = s.length();
        //scale based on # of characters in the string
        if(characters<5)
        {
            scalingFactor = 1;
        }
        else if(characters>=5 && characters<10)
        {
            scalingFactor = 2;
        }
        else if(characters>=10 && characters<15)
        {
            scalingFactor = 3;
        }
        else if(characters>=15 && characters<20)
        {
            scalingFactor = 3;
        }
        else if(characters>=20 && characters<25)
        {
            scalingFactor = 3;
        }
        else if(characters>=25 && characters<30)
        {
            scalingFactor = 3;
        }
        else if(characters>=30 && characters<35)
        {
            scalingFactor = 3;
        }
        else if(characters>=35 && characters<40)
        {
            scalingFactor = 3;
        }
        else if(characters>=40 && characters<45)
        {
            scalingFactor = 3;
        }
        else if(characters>=45 && characters<50)
        {
            scalingFactor = 3;
        }
        else if(characters>=50 && characters<55)
        {
            scalingFactor = 3;
        }
        else if(characters>=55 && characters<60)
        {
            scalingFactor = 3;
        }
        else if(characters>=60 && characters<65)
        {
            scalingFactor = 3;
        }
        else if(characters>=65 && characters<70)
        {
            scalingFactor = 3;
        }
        else if(characters>=70 && characters<75)
        {
            scalingFactor = 3;
        }
        else if(characters>=75)
        {
            scalingFactor = 5;
        }

        //System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
        //the +scalingFactor is important... increase this if nec. later
        while((((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor)*tv.getTextSize())<tv.getHeight())
        {
            tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, tv.getTextSize()+0.25f);
            currentWidth=tv.getPaint().measureText(s);
            //System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
        }

        tv.setText(s);
    }

Merci.

3
RyanM

Je suis tombé sur ça en cherchant moi-même une solution ... J'avais essayé toutes les autres solutions que je pouvais voir sur le dépassement de capacité de la pile, etc.

Fondamentalement, en encapsulant la vue du texte dans une présentation linéaire personnalisée, j'ai pu mesurer correctement le texte en veillant à ce qu'il soit mesuré avec une largeur fixe.

<!-- TextView wrapped in the custom LinearLayout that expects one child TextView -->
<!-- This view should specify the size you would want the text view to be displayed at -->
<com.custom.ResizeView
    Android:layout_width="fill_parent"
    Android:layout_height="0dp"
    Android:layout_margin="10dp"
    Android:layout_weight="1"
    Android:orientation="horizontal" >

    <TextView
        Android:id="@+id/CustomTextView"
        Android:layout_width="fill_parent"
        Android:layout_height="fill_parent"
</com.custom.ResizeView>

Puis le code de mise en page linéaire

public class ResizeView extends LinearLayout {

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

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

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        // oldWidth used as a fixed width when measuring the size of the text
        // view at different font sizes
        final int oldWidth = getMeasuredWidth() - getPaddingBottom() - getPaddingTop();
        final int oldHeight = getMeasuredHeight() - getPaddingLeft() - getPaddingRight();

        // Assume we only have one child and it is the text view to scale
        TextView textView = (TextView) getChildAt(0);

        // This is the maximum font size... we iterate down from this
        // I've specified the sizes in pixels, but sp can be used, just modify
        // the call to setTextSize

        float size = getResources().getDimensionPixelSize(R.dimen.solutions_view_max_font_size);

        for (int textViewHeight = Integer.MAX_VALUE; textViewHeight > oldHeight; size -= 0.1f) {
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);

            // measure the text views size using a fixed width and an
            // unspecified height - the unspecified height means measure
            // returns the textviews ideal height
            textView.measure(MeasureSpec.makeMeasureSpec(oldWidth, MeasureSpec.EXACTLY), MeasureSpec.UNSPECIFIED);

            textViewHeight = textView.getMeasuredHeight();
        }
    }
}

J'espère que ça aide quelqu'un.

3
appmattus

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 le remesurer jusqu’à ce que je trouve une taille de police qui convient. Vous pouvez voir la classe postée dans le sujet ci-dessous. J'espère que ça aide.

Le texte TextView à l'échelle automatique s'adapte aux limites

3
Chase

Une solution consisterait à spécifier différentes dimensions sp pour chacune des tailles d'écran généralisées. Par exemple, fournissez 8 ps pour les petits écrans, 12 ps pour les écrans normaux, 16 sp pour les grands et 20 sp pour les xlarge. Ensuite, il vous suffit de faire référence à @dimen text_size ou quoi que ce soit d'autre et vous pouvez être rassuré, car la densité est prise en charge via l'unité sp. Voir le lien suivant pour plus d'informations sur cette approche.

http://www.developer.Android.com/guide/topics/resources/more-resources.html#Dimension

Je dois toutefois noter que supporter plus de langues signifie plus de travail pendant la phase de test, en particulier si vous souhaitez conserver le texte sur une ligne, car certaines langues ont des mots beaucoup plus longs. Dans ce cas, créez un fichier dimens.xml dans le dossier values-de-large, par exemple, et ajustez la valeur manuellement. J'espère que cela t'aides.

2
Mark

essayez peut-être de définir setHoriztonallyScrolling () sur true avant de prendre des mesures de texte afin que textView n'essaye pas de mettre en forme votre texte sur plusieurs lignes

2
snctln

Voici une solution que j'ai créée à partir d'autres commentaires. Cette solution vous permet de définir la taille du texte en XML qui sera la taille maximale et s’ajustera à la hauteur de la vue. Réglage de la taille TextView

 private float findNewTextSize(int width, int height, CharSequence text) {
            TextPaint textPaint = new TextPaint(getPaint());

            float targetTextSize = textPaint.getTextSize();

            int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            while(textHeight > height && targetTextSize > mMinTextSize) {
                    targetTextSize = Math.max(targetTextSize - 1, mMinTextSize);
                    textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            }
            return targetTextSize;
    }
private int getTextHeight(CharSequence source, TextPaint Paint, int width, float textSize) {
            Paint.setTextSize(textSize);
            StaticLayout layout = new StaticLayout(source, Paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
            return layout.getHeight();
    }
1
Elliott

Si votre seule exigence est que le texte soit automatiquement divisé et maintenu à la ligne suivante et que la hauteur ne soit pas importante, faites-le simplement comme ceci.

<TextView
    Android:layout_height="wrap_content"
    Android:layout_width="wrap_content"
    Android:maxEms="integer"
    Android:width="integer"/>

Cela aura pour effet que votre TextView retourne à son contenu verticalement en fonction de votre valeur maxEms.

0
Octavian Damiean

Ceci est basé sur la réponse de mattmook. Cela a bien fonctionné sur certains appareils, mais pas sur tous. J'ai déplacé le redimensionnement vers l'étape de mesure, transformé la taille de police maximale en attribut personnalisé, pris en compte les marges et étendu FrameLayout au lieu de LineairLayout.

 public class ResizeView extends FrameLayout {
    protected float max_font_size;

    public ResizeView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.ResizeView,
                0, 0);
        max_font_size = a.getDimension(R.styleable.ResizeView_maxFontSize, 30.0f);
    }

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

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        // Use the parent's code for the first measure
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Assume we only have one child and it is the text view to scale
        final TextView textView = (TextView) getChildAt(0);

        // Check if the default measure resulted in a fitting textView
        LayoutParams childLayout = (LayoutParams) textView.getLayoutParams();
        final int textHeightAvailable = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - childLayout.topMargin - childLayout.bottomMargin;
        int textViewHeight = textView.getMeasuredHeight();
        if (textViewHeight < textHeightAvailable) {
            return;
        }

        final int textWidthSpec = MeasureSpec.makeMeasureSpec(
                MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight() - childLayout.leftMargin - childLayout.rightMargin, 
                MeasureSpec.EXACTLY);
        final int textHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

        for (float size = max_font_size; size >= 1.05f; size-=0.1f) {
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
            textView.measure(textWidthSpec, textHeightSpec);

            textViewHeight = textView.getMeasuredHeight();
            if (textViewHeight <= textHeightAvailable) {
                break;
            }
        }
    }
}

Et ceci dans attrs.xml:

<declare-styleable name="ResizeView">
    <attr name="maxFontSize" format="reference|dimension"/>
</declare-styleable>

Et finalement utilisé comme ça:

<PACKAGE_NAME.ui.ResizeView xmlns:custom="http://schemas.Android.com/apk/res/PACKAGE_NAME"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:gravity="start|center_vertical"
    Android:padding="5dp"
    custom:maxFontSize="@dimen/normal_text">

    <TextView Android:id="@+id/tabTitle2"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"/>

</PACKAGE_NAME.ui.ResizeView>
0
Black Mantha
0
vsm

Essaye ça...

tv.setText("Give a very large text anc check , this xample is very usefull");
    countLine=tv.getLineHeight();
    System.out.println("LineCount " + countLine);
    if (countLine>=40){
        tv.setTextSize(15);
    }
0
Mathan Chinna
0
Lassi Kinnunen