Jak dopasować rozmiar czcionki do widoku tekstu

178

Czy w Androidzie jest jakiś sposób, aby dostosować rozmiar tekstu w widoku tekstu, aby pasował do zajmowanej przestrzeni?

Np. Używam a TableLayouti dodaję kilka TextViews do każdego wiersza. Ponieważ nie chcę, aby TextViews zawijały tekst, raczej widzę, że zmniejsza rozmiar czcionki zawartości.

Jakieś pomysły?

Próbowałem measureText, ale ponieważ nie znam wielkości kolumny, korzystanie z niej wydaje się kłopotliwe. To jest kod, w którym chcę zmienić rozmiar czcionki na coś, co pasuje

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());  
Rudy
źródło
Sprawdź moje rozwiązanie na podstawie kodu dunni tutaj stackoverflow.com/questions/5033012/… Uwaga: Nie wdrożyłem go za pomocą pętli. PS: dzięki dunni
vsm

Odpowiedzi:

129

Poniższe rozwiązanie zawiera wszystkie sugestie tutaj. Zaczyna się od tego, co pierwotnie opublikował Dunni. Korzysta z wyszukiwania binarnego, takiego jak gjpc, ale jest nieco bardziej czytelny. Zawiera również poprawki błędów gregm i własne poprawki błędów.

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;
}
szybowiec
źródło
9
Dziękujemy za połączenie wszystkich opinii. Widzę, że rozwiązanie bierze pod uwagę tylko szerokość . Mój problem polega na tym, że lubiący przewyższa wysokość.
AlikElzin-kilaka
3
Och, wydaje się, że częściowo działa w czasie wykonywania. Na emulatorze urządzenia tekst jest ucinany do połowy. W edytorze układu Eclipse wygląda dobrze. Jakieś pomysły?
AlikElzin-kilaka
1
ten post wygląda na to, że to
załatwi sprawę
7
To było świetne rozwiązanie! W przypadku, gdy ktoś inny jest nowy w tworzeniu Androida i nie do końca wie, jak zaimplementować rozszerzony widok w XML, wygląda to tak: <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
Świetna robota! Mam działa również z przyciskami. Aby zadbać o wysokość float hi = this.getHeight() - this.getPaddingBottom() - this.getPaddingTop();
widoku tekstu,
28

Napisałem klasę, która rozszerza TextView i robi to. Po prostu używa MeasureText, jak sugerujesz. Zasadniczo ma maksymalny rozmiar tekstu i minimalny rozmiar tekstu (który można zmienić) i po prostu przegląda rozmiary między nimi w odstępach 1, aż znajdzie największy, który będzie pasował. Niezbyt elegancki, ale nie znam innego sposobu.

Oto kod:

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
źródło
11
Wyszukiwanie binarne byłoby na ogół szybsze niż wyszukiwanie liniowe.
Ted Hopp
myślę, że bardziej uzasadnione byłoby wykonanie refitText w „onLayout” zamiast „onSizeChanged”
Jacky
Czy możesz spojrzeć na mój problem stackoverflow.com/questions/36265448/…
Muhammad
17

Jest to płaszczyzna prędkościFontFitTextView , ale zmniejsza rozmiar czcionki tylko w razie potrzeby, aby dopasować tekst, a poza tym zachowuje rozmiar czcionki. Nie zwiększa rozmiaru czcionki do wysokości.

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

}

Oto przykład, jak można go użyć w 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" />

Spowodowałoby to utrzymanie rozmiaru czcionki do 60sp, o ile tekst mieści się w szerokości. Jeśli tekst jest dłuższy, zmniejszy rozmiar czcionki. W takim przypadku TextViewwysokość s również ulegnie zmianie z powodu height=wrap_content.

Jeśli znajdziesz jakieś błędy, możesz je edytować.

sulai
źródło
Jak go wykorzystałeś? Do mojej odpowiedzi dodałem przykład, w jaki sposób go wykorzystuję. Przetestowałem to z różnymi wersjami Androida, emulatorami i edytorem graficznym ADT.
sulai
Zasadniczo nie określałem określonej wysokości. Twój onMeasure pozwala przejąć widok, jeśli chce. Udało mi się znaleźć rozwiązanie, zmieniając ostatnią linię rutyny nathis.setMeasuredDimension(parentWidth, height);
PearsonArtPhoto
1
Świetne wejście, poprawiłem kod :) Teraz działa z match_parenti wrap_content.
sulai
Dlaczego testujesz puste ciągi znaków „” .equals (s) zamiast po prostu s.isEmpty () ?? Lub s.length () == 0? Nie rozumiem, dlaczego czasami widzę te testy równości.
Zordid
1
@PratikButani dzięki za zwrócenie na to uwagi. odpowiednikiem text.isEmpty()byłoby "".equals(text).
sulai
14

Oto moje rozwiązanie, które działa na emulatorze i telefonach, ale niezbyt dobrze na edytorze układu Eclipse. Jest inspirowany kodem kilaki, ale rozmiar tekstu nie jest uzyskiwany z Paint, ale z pomiaru samego wywołania TextView measure(0, 0).

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

Plik atrybutów XML:

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

Sprawdź mój github, aby uzyskać najnowszą wersję tej klasy. Mam nadzieję, że może się przydać komuś. Jeśli błąd zostanie znaleziony lub kod wymaga wyjaśnienia, możesz otworzyć problem w Github.

yDelouis
źródło
Działa idealnie na moim Galaxy Nexus, ale mam problemy z urządzeniami z małymi ekranami.
Tony Ceralva,
Jaki masz problem na urządzeniach z małymi ekranami?
yDelouis,
Przepraszamy, problem nie dotyczy małych ekranów, widok nie działa na wszystkich urządzeniach z Androidem 4.0, w tym emulator. Otworzyłem nowy numer w twoim serwisie GitHub
Tony Ceralva,
12

Bardzo dziękuję https://stackoverflow.com/users/234270/speedplane . Świetna odpowiedź!

Oto ulepszona wersja jego odpowiedzi, która również dba o wysokość i zawiera atrybut maxFontSize, aby ograniczyć rozmiar czcionki (był przydatny w moim przypadku, więc chciałem go udostępnić):

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

Odpowiedni plik /res/values/attr.xml:

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

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

</resources>

Przykład:

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

Aby użyć nowego maxFontSizeatrybutu, nie zapomnij dodać xmlns:res-auto="http://schemas.android.com/apk/res-auto"jako pokazu w przykładzie.

Pascal
źródło
Przepraszamy, ale nadal nie działa we wszystkich przypadkach. Myślę też, że nie obsługuje poprawnie wielu linii.
programista Androida
Och ... Czy możesz dać mi skrzynkę, dla której to nie działa? (Multi-line nie jest obsługiwany, masz rację)
Pascal
Wiele przypadków Stworzyłem przypadkowy tester, aby udowodnić, że jest poprawny. Oto jedna próbka: szerokość widoku: 317 pikseli, wysokość: 137 pikseli, tekst: „q7Lr”. Widzę to: tinypic.com/view.php?pic=2dv5yf9&s=6 . oto przykładowy projekt, który wykonałem: mega.co.nz/… . Myślę, że taki widok powinien obsługiwać wiele linii, obsługiwać dowolny rozmiar czcionek, obsługiwać wysokość, a nie tylko szerokość ... Niestety, żadna próbka, którą znalazłem, nie działała dobrze.
programista Androida
ok, zdecydowanie nie jest to kompletne rozwiązanie. Przepraszam. Na razie nie ma czasu, aby go ulepszyć. Jeśli możesz to poprawić, nie wahaj się opublikować swojego rozwiązania.
Pascal
1
Utworzyłem nowy wątek, który pokazuje moje testy, mając nadzieję, że ktoś będzie w stanie zrobić dobre rozwiązanie: stackoverflow.com/questions/16017165/…
programista Androida
7

Miałem ten sam problem i napisałem zajęcia, które wydają się dla mnie skuteczne. Zasadniczo użyłem układu statycznego, aby narysować tekst na osobnym obszarze roboczym i dokonać ponownej oceny, aż znajdę odpowiedni rozmiar czcionki. Możesz zobaczyć zajęcia opublikowane w poniższym temacie. Mam nadzieję, że to pomoże.

Automatyczne skalowanie tekstu Wyświetlanie tekstu w celu dopasowania w granicach

Pościg
źródło
6

Możesz to teraz zrobić bez biblioteki lub widżetu innej firmy. Jest wbudowany w TextView na poziomie API 26. Dodaj android:autoSizeTextType="uniform"do swojego TextViewi ustaw wysokość. To wszystko.

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

Możesz także użyć TextViewCompatdo kompatybilności.

nie zgadzam się
źródło
5

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

I wyszukiwanie binarne w 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
źródło
3
Zamiast wyszukiwania binarnego proste skalowanie działa całkiem dobrze: trySize *= availableWidth / measured_width(następnie zaciśnięte na minTextSize).
Ted Hopp
5

Znalazłem następujące, które działają dla mnie ładnie. Nie zapętla się i uwzględnia zarówno wysokość, jak i szerokość. Zauważ, że ważne jest, aby określić jednostkę PX podczas wywoływania setTextSize w widoku. Dzięki wskazówkom w poprzednim poście do tego!

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

Oto procedura, której używam, przekazując getPaint () z widoku. 10-znakowy ciąg znaków z „szerokim” znakiem służy do oszacowania szerokości niezależnie od rzeczywistego ciągu.

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
źródło
Jak włączyć to do xml układu?
AlikElzin-kilaka
Ten kod jest przydatny do umieszczania tekstu w istniejącym widoku w ograniczonym obszarze wielkości lub możesz utworzyć własną klasę pochodną z TextView i zastąpić onMeasure, jak pokazano w innych postach. Sam w sobie nie może być użyty w układzie.
Glenn
Upewnij się, że sprawdziłeś wymiary widoku po jego narysowaniu. Wykonanie tego podczas działania funkcji onCreate () jest zbyt wcześnie. Użyłem ViewTreeObserver, aby upewnić się, że pomiary zostały wykonane we właściwym czasie.
Scott Biggs,
4

Działa z modyfikacjami

Musisz ustawić taki rozmiar widoku tekstu, ponieważ inaczej setTextSize zakłada, że ​​wartość jest w jednostkach SP:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

I musiałeś jawnie dodać ten kod.

@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
źródło
4

Służy app:autoSizeTextType="uniform"do kompatybilności wstecznej, ponieważ android:autoSizeTextType="uniform"działa tylko na poziomie API 26 i wyższym.

Suraj Vaishnav
źródło
2

Użyłem powyższej odmiany rozwiązania Dunni, ale ten konkretny kod nie działał dla mnie. W szczególności, gdy próbujesz użyć zestawu obiektów Paint, aby mieć cechy obiektu Paint widoku, a następnie wywołujemy pomiarText (), nie zwraca tej samej wartości, co bezpośrednie wywoływanie obiektu Paint widoku. Być może istnieją pewne różnice w sposobie konfiguracji moich poglądów, które sprawiają, że zachowanie się różni.

Moim rozwiązaniem było bezpośrednie użycie Paint w widoku, nawet jeśli mogą wystąpić pewne ograniczenia wydajności przy wielokrotnym zmienianiu rozmiaru czcionki dla widoku.

ThomasW
źródło
2

Pracowałem nad ulepszeniem doskonałego rozwiązania z Speedplane i wymyśliłem to. Zarządza wysokością, w tym ustawia margines w taki sposób, aby tekst był poprawnie wyśrodkowany pionowo.

Używa tej samej funkcji do uzyskania szerokości, ponieważ wydaje się, że działa najlepiej, ale używa innej funkcji do uzyskania wysokości, ponieważ wysokość nigdzie nie jest podana. Jest kilka korekt, które należy wprowadzić, ale wymyśliłem sposób na zrobienie tego, wyglądając przyjemnie dla oka.

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
źródło
1

Tak długo cierpiałem w moich projektach, dopóki nie znalazłem tej biblioteki:

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

Musisz tylko dodać xml według swoich potrzeb i gotowe. Na przykład:

<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
źródło
0

Zainspirowany poprzednimi plakatami chciałem podzielić się moim rozwiązaniem. Działa ze współczynnikiem skali, który jest stosowany do poprzedniego rozmiaru czcionki, aby dopasować ją do dostępnej przestrzeni. Oprócz zapobiegania nieoczekiwanemu zachowaniu metody TextViews onDraw, po prostu rysuje tekst samodzielnie.

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
źródło
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
źródło
1
Po prostu czytając kod, nie sądzę, że to zadziała. Pamiętaj, że pytanie dotyczyło automatycznego dostosowania rozmiaru czcionki widoku tekstowego, aby pasował do widoku. Ustawiono stały rozmiar czcionki.
sulai
0

To powinno być proste rozwiązanie:

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
źródło
0

Rozszerz TextView i zastąp overDraw za pomocą poniższego kodu. Zachowa proporcje tekstu, ale dostosuje rozmiar, aby wypełnić przestrzeń. W razie potrzeby możesz łatwo zmodyfikować kod, aby go rozciągał.

  @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
źródło
0

Napisałem krótką klasę pomocnika, która dopasowuje widok tekstu do określonej szerokości i dodaje na końcu elipsę „...”, jeśli nie można osiągnąć minimalnej wielkości tekstu.

Pamiętaj, że zmniejsza tylko tekst, dopóki nie będzie pasował lub nie zostanie osiągnięty minimalny rozmiar tekstu. Aby przeprowadzić test z dużymi rozmiarami, przed wywołaniem metody pomocy ustaw rozmiar tekstu na dużą liczbę.

Zajmuje piksele, więc jeśli używasz wartości z dimen, możesz to nazwać tak:


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

Z tej klasy korzystam:


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
źródło
Czy obsługuje to wiele wierszy tekstu?
Roymunson,
0

Jeśli ustawiona jest transformacja taka jak allCaps, podejście Speedplane jest błędne. Naprawiłem go, powodując następujący kod (przepraszam, moja reputacja nie pozwala mi dodać tego jako komentarza do rozwiązania 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
źródło
0

Nie wiem, czy to jest poprawny sposób, czy też nie działa ... weź swój widok i sprawdź OnGlobalLayoutListener () i uzyskaj wartość linii tekstowej viewview, a następnie ustaw textSize.

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

Jest to bardzo prosty kod linii.

Mathan Chinna
źródło