So passen Sie die Textschriftgröße an die Textansicht an


178

Gibt es in Android eine Möglichkeit, die Textgröße in einer Textansicht an den Platz anzupassen, den sie einnimmt?

ZB verwende ich a TableLayoutund füge TextViewjeder Zeile mehrere s hinzu . Da ich nicht möchte, dass das TextViews den Text umschließt, sehe ich eher, dass es die Schriftgröße des Inhalts verringert.

Irgendwelche Ideen?

Ich habe es versucht measureText, aber da ich die Größe der Spalte nicht kenne, scheint es schwierig zu sein, sie zu verwenden. Dies ist der Code, in dem ich die Schriftgröße so ändern möchte, dass sie passt

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

Überprüfen Sie meine Lösung anhand des Dunni-Codes hier. Stackoverflow.com/questions/5033012/… Hinweis: Ich habe sie nicht mit einer Schleife implementiert. PS: danke dunni
vsm

Antworten:


129

Die folgende Lösung enthält alle Vorschläge hier. Es beginnt mit dem, was ursprünglich von Dunni gepostet wurde. Es verwendet eine binäre Suche wie die von gjpc, ist aber etwas besser lesbar. Es enthält auch die Fehlerbehebungen von Gregm und eine eigene Fehlerbehebung.

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

9
Vielen Dank für die Kombination aller Rückmeldungen. Ich sehe, dass die Lösung nur die Breite berücksichtigt. Mein Problem ist, dass der Fond die Höhe überschreitet.
AlikElzin-Kilaka

3
Oh, es scheint teilweise zur Laufzeit zu funktionieren. Auf einem Gerät des Emulators wird der Text auf halber Höhe geschnitten. Im Eclipse-Layout-Editor sieht es gut aus. Irgendwelche Ideen?
AlikElzin-Kilaka

1
Dieser Beitrag scheint
Ed Sinek

7
Das war eine großartige Lösung! Falls jemand anderes neu in der Android-Entwicklung ist und nicht genau weiß, wie man eine erweiterte Ansicht in XML implementiert, sieht es so aus: <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
Gute Arbeit! Habe es auch mit Knöpfen zum Laufen gebracht. Um sich um die Höhe der Textansicht zu kümmern, stellen Sie einfachfloat hi = this.getHeight() - this.getPaddingBottom() - this.getPaddingTop();
AlexGuti

28

Ich habe eine Klasse geschrieben, die TextView erweitert und dies tut. Es wird nur MeasureText verwendet, wie Sie vorschlagen. Grundsätzlich hat es eine maximale Textgröße und eine minimale Textgröße (die geändert werden kann) und durchläuft die Größen zwischen ihnen nur in Dekrementen von 1, bis die größte gefunden wird, die passt. Nicht besonders elegant, aber ich kenne keinen anderen Weg.

Hier ist der 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;

}

11
Eine binäre Suche wäre im Allgemeinen schneller als eine lineare Suche.
Ted Hopp

Ich denke, es wäre vernünftiger, refitText bei "onLayout" zu machen, als bei "onSizeChanged"
Jacky

Können Sie bitte einen Blick auf mein Problem stackoverflow.com/questions/36265448/…
Muhammad

17

Dies ist SpeedplaneFontFitTextView , verringert jedoch nur bei Bedarf die Schriftgröße, um den Text anzupassen, und behält ansonsten die Schriftgröße bei. Die Schriftgröße wird nicht an die Höhe angepasst.

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

}

Hier ist ein Beispiel, wie es in XML verwendet werden könnte:

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

Dies würde die Schriftgröße auf 60sp halten, solange der Text in die Breite passt. Wenn der Text länger ist, wird die Schriftgröße verringert. In diesem Fall TextViewändert sich auch die Höhe von s aufgrund von height=wrap_content.

Wenn Sie Fehler finden, können Sie diese jederzeit bearbeiten.


Wie hast du es benutzt? Ich habe meiner Antwort ein Beispiel hinzugefügt, wie ich es verwende. Ich habe es mit verschiedenen Android-Versionen, Emulatoren und ADT Graphical Layout Editor getestet.
Sulai

Im Wesentlichen habe ich keine bestimmte Höhe angegeben. Mit Ihrer onMeasure kann die Ansicht übernommen werden, wenn dies gewünscht wird. Ich habe es geschafft, eine Lösung zu finden und die letzte Zeile in der Routine inthis.setMeasuredDimension(parentWidth, height);
PearsonArtPhoto

1
Tolle Eingabe, ich habe den Code korrigiert :) Jetzt funktioniert es mit match_parentund wrap_content.
Sulai

Warum testen Sie, ob leere Zeichenfolgen "" .equals (s) statt einfach s.isEmpty () geschrieben werden? Oder an Länge () == 0? Verstehe nicht, warum ich diese Gleichheitstests manchmal sehen kann.
Zordid

1
@PratikButani danke für den Hinweis. das Äquivalent zu text.isEmpty()wäre "".equals(text).
Sulai

14

Hier ist meine Lösung, die auf Emulator und Telefonen funktioniert, aber auf dem Eclipse-Layout-Editor nicht sehr gut funktioniert. Es ist vom Kilaka-Code inspiriert, aber die Größe des Textes wird nicht vom Paint erhalten, sondern vom Messen des TextView-Aufrufs measure(0, 0).

Die Java-Klasse:

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

Die XML-Attributdatei:

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

Überprüfen Sie meinen Github für die neueste Version dieser Klasse. Ich hoffe, es kann für jemanden nützlich sein. Wenn ein Fehler gefunden wird oder der Code erklärt werden muss, können Sie ein Problem mit Github eröffnen.


Es funktioniert perfekt auf meinem Galaxy Nexus, aber ich habe Probleme auf Geräten mit kleinen Bildschirmen.
Tony Ceralva

Was ist Ihr Problem bei Geräten mit kleinen Bildschirmen?
yDelouis

Das Problem liegt leider nicht bei kleinen Bildschirmen. Ihre Ansicht funktioniert nicht auf allen Geräten mit Android 4.0, einschließlich Emulator. Ich habe eine neue Ausgabe in Ihrem GitHub eröffnet
Tony Ceralva

12

Vielen Dank an https://stackoverflow.com/users/234270/speedplane . Gute Antwort!

Hier ist eine verbesserte Version seiner Antwort, die sich auch um die Höhe kümmert und mit einem maxFontSize-Attribut zur Begrenzung der Schriftgröße geliefert wird (war in meinem Fall nützlich, daher wollte ich es teilen):

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

Entsprechende Datei /res/values/attr.xml:

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

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

</resources>

Beispiel:

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

Um das neue maxFontSizeAttribut zu verwenden, vergessen Sie nicht, xmlns:res-auto="http://schemas.android.com/apk/res-auto"wie im Beispiel gezeigt hinzuzufügen .


Entschuldigung, aber es funktioniert immer noch nicht in allen Fällen. Ich denke auch, dass es mehrere Zeilen nicht richtig behandelt.
Android-Entwickler

Oh .. Könnten Sie mir einen Fall geben, für den es nicht funktioniert? (Multi Line wird nicht unterstützt, Sie haben Recht)
Pascal

Viele Fälle. Ich habe einen zufälligen Tester erstellt, um zu beweisen, dass er korrekt ist. Hier ist ein Beispiel: Breite der Ansicht: 317px, Höhe: 137px, Text: "q7Lr". Was ich sehe, ist Folgendes : tinypic.com/view.php?pic=2dv5yf9&s=6 . Hier ist das Beispielprojekt, das ich gemacht habe: mega.co.nz/… . Ich denke, dass eine solche Ansicht mehrzeilig sein sollte, Schriftgrößen jeder Größe unterstützen, die Höhe und nicht nur die Breite behandeln sollte ... Leider hat keines der Beispiele, die ich gefunden habe, gut funktioniert.
Android-Entwickler

ok, definitiv keine vollständige lösung. Es tut uns leid. Keine Zeit, es vorerst zu verbessern. Wenn Sie es verbessern können, zögern Sie nicht, Ihre Lösung zu veröffentlichen.
Pascal

1
Ich habe einen neuen Thread erstellt, der meine Tests zeigt, in der Hoffnung, dass jemand eine gute Lösung finden kann: stackoverflow.com/questions/16017165/…
Android-Entwickler


6

Sie können dies jetzt ohne eine Drittanbieter-Bibliothek oder ein Widget tun. Es ist in APIV Level 26 in TextView integriert. Fügen android:autoSizeTextType="uniform"Sie es hinzu TextViewund legen Sie die Höhe fest. Das ist alles.

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

Sie können auch TextViewCompataus Kompatibilitätsgründen verwenden.


5

Leichte Änderung an 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);
}

Und binäre Suche auf 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);
    }
}

3
Anstelle einer binären Suche funktioniert eine einfache Skalierung ziemlich gut: trySize *= availableWidth / measured_width(dann auf minTextSize geklemmt).
Ted Hopp

5

Ich fand, dass das Folgende gut für mich funktioniert. Es wird keine Schleife ausgeführt und berücksichtigt sowohl Höhe als auch Breite. Beachten Sie, dass es wichtig ist, die PX-Einheit anzugeben, wenn Sie setTextSize in der Ansicht aufrufen. Danke an den Tipp in einem vorherigen Beitrag dafür!

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

Hier ist die Routine, die ich verwende und die getPaint () aus der Ansicht übergibt. Eine 10-stellige Zeichenfolge mit einem "breiten" Zeichen wird verwendet, um die Breite unabhängig von der tatsächlichen Zeichenfolge zu schätzen.

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

Wie integrieren Sie es in eine Layout-XML?
AlikElzin-kilaka

Dieser Code ist nützlich, um Text in einer vorhandenen Ansicht in einem Bereich mit eingeschränkter Größe zu platzieren, oder Sie können eine eigene abgeleitete Klasse aus TextView erstellen und die onMeasure überschreiben, wie in anderen Beiträgen gezeigt. An sich kann es nicht in einem Layout verwendet werden.
Glenn

Stellen Sie sicher, dass Sie die Abmessungen der Ansicht nach dem Zeichnen überprüfen . Dies während onCreate () zu tun, ist zu früh. Ich habe ViewTreeObserver verwendet, um sicherzustellen, dass die Messungen zum richtigen Zeitpunkt durchgeführt wurden.
Scott Biggs

4

Funktioniert mit Modifikation

Sie müssen die Größe der Textansicht folgendermaßen festlegen, da sonst setTextSize davon ausgeht, dass der Wert in SP-Einheiten angegeben ist:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

Und Sie mussten diesen Code explizit hinzufügen.

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

4

Zur app:autoSizeTextType="uniform"Abwärtskompatibilität verwenden, da android:autoSizeTextType="uniform"nur in API Level 26 und höher funktioniert.


2

Ich habe oben eine Variation der Dunni-Lösung verwendet, aber dieser bestimmte Code hat bei mir nicht funktioniert. Insbesondere wenn Sie versuchen, das Paint-Objekt so zu verwenden, dass es die Merkmale des Paint-Objekts der Ansicht aufweist, und dann MeasureText () aufrufen, wird nicht derselbe Wert zurückgegeben wie beim direkten Aufrufen des Paint-Objekts der Ansicht. Vielleicht gibt es einige Unterschiede in der Art und Weise, wie meine Ansichten aufgebaut sind, die das Verhalten anders machen.

Meine Lösung bestand darin, die Farbe der Ansicht direkt zu verwenden, obwohl es einige Leistungseinbußen geben kann, wenn die Schriftgröße für die Ansicht mehrmals geändert wird.


2

Ich habe daran gearbeitet, die hervorragende Lösung von Speedplane zu verbessern, und mir das ausgedacht. Es verwaltet die Höhe, einschließlich der Einstellung des Randes, sodass der Text vertikal korrekt zentriert werden soll.

Dies verwendet dieselbe Funktion, um die Breite zu ermitteln, da dies am besten zu funktionieren scheint, verwendet jedoch eine andere Funktion, um die Höhe zu ermitteln, da die Höhe nirgendwo angegeben wird. Es müssen einige Korrekturen vorgenommen werden, aber ich habe einen Weg gefunden, dies zu tun, während ich für das Auge angenehm aussehe.

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


1

Ich hatte diesen Schmerz in meinen Projekten soooo lange, bis ich diese Bibliothek fand:

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

Sie müssen nur die XML nach Ihren Wünschen hinzufügen und fertig. Beispielsweise:

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

0

Inspiriert von den vorherigen Postern wollte ich meine Lösung teilen. Es arbeitet mit einem Skalierungsfaktor, der auf die vorherige Schriftgröße angewendet wird, um ihn an den verfügbaren Platz anzupassen. Um ein unerwartetes Verhalten der TextViews onDraw-Methode zu verhindern, wird der Text einfach selbst gezeichnet.

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

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;

1
Wenn Sie einfach Ihren Code lesen, denke ich nicht, dass dies funktionieren wird. Denken Sie daran, bei der Frage ging es darum, die Schriftgröße der Textansicht automatisch an die Ansicht anzupassen. Sie legen eine feste Schriftgröße fest.
Sulai

0

Dies sollte eine einfache Lösung sein:

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

0

Erweitern Sie TextView und überschreiben Sie onDraw mit dem folgenden Code. Das Textseitenverhältnis wird beibehalten, aber die Größe angepasst, um den Raum auszufüllen. Sie können den Code bei Bedarf leicht ändern, um ihn zu dehnen.

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

0

Ich habe eine kurze Hilfsklasse geschrieben, die eine Textansicht in eine bestimmte Breite passt und am Ende die Ellipsengröße "..." hinzufügt, wenn die minimale Textgröße nicht erreicht werden kann.

Beachten Sie, dass der Text nur so lange verkleinert wird, bis er passt oder die minimale Textgröße erreicht ist. Stellen Sie zum Testen mit großen Größen die Textgröße auf eine große Zahl ein, bevor Sie die Hilfemethode aufrufen.

Es werden Pixel benötigt. Wenn Sie also Werte von dimen verwenden, können Sie dies folgendermaßen nennen:


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

Dies ist die Klasse, die ich benutze:


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

Unterstützt dies mehrere Textzeilen?
Roymunson

0

Wenn eine Transformation wie allCaps festgelegt ist, ist der Ansatz des Speedplane fehlerhaft. Ich habe es behoben, was zu folgendem Code führte (mein Ruf erlaubt mir leider nicht, dies als Kommentar zur Lösung von speedplane hinzuzufügen):

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

0

Ich weiß nicht, ob dies der richtige Weg ist oder nicht, aber es funktioniert ... Nehmen Sie Ihre Ansicht und überprüfen Sie OnGlobalLayoutListener () und erhalten Sie die Zeilenanzahl der Textansicht. Legen Sie dann textSize fest.

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

Es ist sehr einfach, ein paar Zeilencode ..

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.