Cara menyesuaikan ukuran font teks agar sesuai dengan tampilan teks

178

Apakah ada cara di Android untuk menyesuaikan ukuran teks dalam tampilan teks agar sesuai dengan ruang yang ditempati?

Misalnya saya menggunakan a TableLayoutdan menambahkan beberapa TextViews pada setiap baris. Karena saya tidak menginginkannyaTextView huruf s untuk membungkus teks saya lebih suka melihat itu menurunkan ukuran font konten.

Ada ide?

Saya sudah mencoba measureText, tetapi karena saya tidak tahu ukuran kolomnya, sepertinya sulit untuk digunakan. Ini adalah kode tempat saya ingin mengubah ukuran font menjadi sesuatu yang cocok

TableRow row = new TableRow(this);   
for (int i=0; i < ColumnNames.length; i++) {    
    TextView textColumn = new TextView(this);      
    textColumn.setText(ColumnNames[i]);
    textColumn.setPadding(0, 0, 1, 0);
    textColumn.setTextColor(getResources().getColor(R.drawable.text_default));          
    row.addView(textColumn, new TableRow.LayoutParams()); 
} 
table.addView(row, new TableLayout.LayoutParams());  
rudas
sumber
Periksa solusi saya berdasarkan kode dunni di sini stackoverflow.com/questions/5033012/... Catatan: Saya tidak mengimplementasikannya dengan sebuah loop. PS: terima kasih dunni
vsm

Jawaban:

129

Solusi di bawah ini menggabungkan semua saran di sini. Dimulai dengan apa yang semula diposting oleh Dunni. Ini menggunakan pencarian biner seperti gjpc, tetapi sedikit lebih mudah dibaca. Ini juga termasuk perbaikan bug gregm dan perbaikan bug saya sendiri.

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

public class FontFitTextView extends TextView {

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

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

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

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

        mTestPaint.set(this.getPaint());

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

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

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

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

    //Attributes
    private Paint mTestPaint;
}
speedplane
sumber
9
Terima kasih telah menggabungkan semua umpan balik. Saya melihat bahwa solusinya hanya mempertimbangkan lebar . Masalah saya adalah bahwa fond melebihi ketinggian.
AlikElzin-kilaka
3
Oh, tampaknya sebagian berfungsi saat runtime. Pada perangkat emulator, teks dipotong setengah jalan. Pada editor layout Eclipse terlihat baik-baik saja. Ada ide?
AlikElzin-kilaka
1
posting ini sepertinya akan melakukan trik (menambahkan atribut xml khusus untuk hi / lo): stackoverflow.com/a/8090772/156611
Ed Sinek
7
Ini adalah solusi hebat! Jika ada orang lain yang baru dalam pengembangan android dan tidak cukup tahu bagaimana menerapkan tampilan yang diperluas dalam XML, sepertinya ini: <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
Kerja bagus! Mengerjakannya juga dengan tombol. Untuk menjaga ketinggian tampilan teks, aturfloat hi = this.getHeight() - this.getPaddingBottom() - this.getPaddingTop();
AlexGuti
28

Saya telah menulis sebuah kelas yang memperluas TextView dan melakukan ini. Itu hanya menggunakan sizeText seperti yang Anda sarankan. Pada dasarnya ia memiliki ukuran teks maksimum dan ukuran teks minimum (yang dapat diubah) dan hanya berjalan melalui ukuran di antara mereka dalam pengurangan 1 sampai menemukan yang terbesar yang sesuai. Tidak terlalu elegan, tapi saya tidak tahu cara lain.

Ini kodenya:

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
sumber
11
Pencarian biner umumnya akan lebih cepat daripada pencarian linear.
Ted Hopp
saya pikir akan lebih masuk akal untuk melakukan refitText di "onLayout" daripada "onSizeChanged"
Jacky
Bisakah Anda melihat masalah saya stackoverflow.com/questions/36265448/…
Muhammad
17

Ini adalah speedplane'sFontFitTextView , tetapi hanya mengurangi ukuran font jika diperlukan untuk membuat teks sesuai, dan mempertahankan ukuran fontnya. Itu tidak menambah ukuran font agar sesuai dengan ketinggian.

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

}

Berikut adalah contoh bagaimana ini dapat digunakan dalam 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" />

Ini akan menjaga ukuran font hingga 60sp selama teks sesuai lebar. Jika teks lebih panjang, itu akan mengurangi ukuran font. Dalam hal ini, TextViewtinggi s juga akan berubah karena height=wrap_content.

Jika Anda menemukan bug, silakan diedit.

sulai
sumber
Bagaimana Anda menggunakannya? Saya telah menambahkan contoh bagaimana saya menggunakannya untuk jawaban saya. Saya telah mengujinya dengan berbagai versi android, emulator dan ADT Graphical Layout Editor.
sulai
Pada dasarnya, saya tidak menentukan ketinggian tertentu. OnMeasure Anda memungkinkan tampilan untuk mengambil alih jika diinginkan. Saya berhasil menemukan solusi, mengubah baris terakhir dalam rutin kethis.setMeasuredDimension(parentWidth, height);
PearsonArtPhoto
1
Masukan yang bagus, saya memperbaiki kode :) Sekarang berfungsi dengan match_parentdan wrap_content.
sulai
Mengapa Anda menguji untuk menulis string kosong "". Equals (s) bukan hanya s.isEmpty () ?? Atau s.length () == 0? Tidak mengerti mengapa saya terkadang dapat melihat tes kesetaraan ini.
Zordid
1
@PratikButani terima kasih telah menunjukkan ini. setara dengan text.isEmpty()akan "".equals(text).
sulai
14

Inilah solusi saya yang bekerja pada emulator dan ponsel tetapi tidak terlalu baik pada editor layout Eclipse. Ini terinspirasi dari kode kilaka tetapi ukuran teks tidak diperoleh dari Paint tetapi dari pengukuran pemanggilan TextView sendiri measure(0, 0).

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

File atribut XML:

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

Periksa github saya untuk versi terbaru dari kelas ini. Semoga bermanfaat bagi seseorang. Jika bug ditemukan atau jika kode perlu dijelaskan, jangan ragu untuk membuka masalah di Github.

yDelouis
sumber
Ini berfungsi sebagai prefek pada Galaxy Nexus saya, tetapi saya mengalami masalah pada perangkat dengan layar kecil.
Tony Ceralva
Apa masalah Anda pada perangkat dengan layar kecil?
yDelouis
Maaf, masalahnya bukan pada layar kecil, tampilan Anda tidak berfungsi di semua perangkat dengan Android 4.0, termasuk emulator. Saya membuka masalah baru di GitHub Anda
Tony Ceralva
12

Terima kasih banyak untuk https://stackoverflow.com/users/234270/speedplane . Jawaban bagus!

Ini adalah versi perbaikan dari tanggapannya yang juga menjaga ketinggian dan dilengkapi dengan atribut maxFontSize untuk membatasi ukuran font (berguna dalam kasus saya, jadi saya ingin membagikannya):

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

File /res/values/attr.xml yang sesuai:

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

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

</resources>

Contoh:

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

Untuk menggunakan maxFontSizeatribut baru , jangan lupa untuk menambahkan xmlns:res-auto="http://schemas.android.com/apk/res-auto"seperti yang ditunjukkan dalam contoh.

Pascal
sumber
Maaf, tetapi masih tidak berfungsi pada semua kasus. Saya juga berpikir itu tidak menangani multi jalur dengan benar.
pengembang android
Oh .. Bisakah Anda memberi saya kasing yang tidak berfungsi? (Multi line tidak didukung, Anda benar)
Pascal
Banyak kasus. Saya telah membuat penguji acak hanya untuk membuktikan bahwa itu benar. Berikut satu contoh: lebar tampilan: 317px, tinggi: 137px, teks: "q7Lr". Apa yang saya lihat adalah ini: tinypic.com/view.php?pic=2dv5yf9&s=6 . inilah contoh proyek yang telah saya buat: mega.co.nz/… . Saya pikir pandangan seperti itu harus menangani multi-baris, mendukung berbagai ukuran font, menangani tinggi dan tidak hanya lebar, ... Sayangnya, tidak ada sampel yang saya temukan telah bekerja dengan baik.
pengembang android
ok, jelas bukan solusi lengkap. Maaf. Tidak ada waktu untuk memperbaikinya untuk saat ini .. Jika Anda dapat memperbaikinya, jangan ragu untuk mengirimkan solusi Anda.
Pascal
1
Saya telah membuat utas baru yang menunjukkan pengujian saya, berharap seseorang dapat membuat solusi yang baik: stackoverflow.com/questions/16017165/…
pengembang android
7

Saya memiliki masalah yang sama dan menulis sebuah kelas yang sepertinya cocok untuk saya. Pada dasarnya, saya menggunakan tata letak statis untuk menggambar teks dalam kanvas dan pengukuran ulang yang terpisah sampai saya menemukan ukuran font yang cocok. Anda dapat melihat kelas diposting dalam topik di bawah ini. Saya harap ini membantu.

Skala Otomatis TextView Teks agar Sesuai dengan Batas

Mengejar
sumber
6

Anda sekarang dapat melakukan ini tanpa perpustakaan pihak ketiga atau widget. Ini dibangun ke dalam TextView di API level 26. Tambahkan android:autoSizeTextType="uniform"ke Anda TextViewdan atur ketinggiannya. Itu saja.

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

Anda juga dapat menggunakan TextViewCompatuntuk kompatibilitas.

arsen
sumber
5

Modifikasi sedikit ke 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);
}

Dan pencarian biner pada 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
sumber
3
Alih-alih pencarian biner, penskalaan sederhana berfungsi dengan cukup baik: trySize *= availableWidth / measured_width(lalu dijepit ke minTextSize).
Ted Hopp
5

Saya menemukan yang berikut ini berfungsi dengan baik untuk saya. Itu tidak berulang dan menyumbang tinggi dan lebar. Perhatikan bahwa penting untuk menentukan unit PX saat memanggil setTextSize pada tampilan. Berkat tip di posting sebelumnya untuk ini!

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

Berikut ini adalah rutin yang saya gunakan, melewati getPaint () dari tampilan. String 10 karakter dengan karakter 'lebar' digunakan untuk memperkirakan lebar independen dari string sebenarnya.

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
sumber
Bagaimana Anda memasukkannya dalam tata letak xml?
AlikElzin-kilaka
Kode ini berguna untuk menempatkan teks dalam tampilan yang ada ke area ukuran terbatas atau Anda dapat membuat kelas turunan Anda sendiri dari TextView dan menimpa onMeasure seperti yang ditunjukkan pada posting lain. Dengan sendirinya itu tidak dapat digunakan dalam tata letak.
Glenn
Pastikan Anda memeriksa dimensi tampilan setelah gambar dibuat. Melakukan ini selama onCreate () terlalu dini. Saya menggunakan ViewTreeObserver untuk memastikan pengukuran dilakukan pada waktu yang tepat.
Scott Biggs
4

Bekerja dengan modifikasi

Anda perlu mengatur ukuran tampilan teks seperti ini karena jika tidak setTextSize mengasumsikan nilainya dalam unit SP:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

Dan Anda perlu menambahkan kode ini secara eksplisit.

@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
sumber
4

Gunakan app:autoSizeTextType="uniform"untuk kompatibilitas mundur karena android:autoSizeTextType="uniform"hanya berfungsi di API Level 26 dan lebih tinggi.

Suraj Vaishnav
sumber
2

Saya menggunakan variasi solusi Dunni di atas, tetapi kode khusus itu tidak berfungsi untuk saya. Secara khusus, ketika mencoba menggunakan objek Paint yang diatur untuk memiliki sifat-sifat objek Paint view, dan kemudian memanggil gaugeText (), itu tidak mengembalikan nilai yang sama dengan langsung memanggil objek Paint view. Mungkin ada beberapa perbedaan dalam cara pandangan saya diatur yang membuat perilaku berbeda.

Solusi saya adalah langsung menggunakan Paint view, meskipun mungkin ada beberapa penalti kinerja dalam mengubah ukuran font untuk tampilan beberapa kali.

ThomasW
sumber
2

Saya telah bekerja untuk meningkatkan solusi luar biasa dari speedplane, dan muncul dengan ini. Ia mengatur ketinggian, termasuk mengatur margin sehingga teks harus dipusatkan dengan benar secara vertikal.

Ini menggunakan fungsi yang sama untuk mendapatkan lebar, karena tampaknya berfungsi dengan baik, tetapi menggunakan fungsi yang berbeda untuk mendapatkan ketinggian, karena ketinggian tidak disediakan di mana pun. Ada beberapa koreksi yang perlu dilakukan, tetapi saya menemukan cara untuk melakukan itu, sambil terlihat enak dipandang.

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

Saya memiliki rasa sakit ini dalam proyek saya untuk sangaaaat lama sampai saya menemukan perpustakaan ini:

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

Anda hanya perlu menambahkan xml dengan kebutuhan Anda dan selesai. Sebagai contoh:

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

Terinspirasi oleh poster sebelumnya, saya ingin membagikan solusi saya. Ini bekerja dengan faktor skala yang diterapkan pada ukuran font sebelumnya agar sesuai dengan ruang yang tersedia. Selain untuk mencegah perilaku tak terduga dari metode TextViews onDraw, itu hanya menggambar teks sendiri.

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
sumber
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
sumber
1
Dengan hanya membaca kode Anda, saya tidak berpikir ini akan berhasil. Ingat, pertanyaannya adalah tentang menyesuaikan ukuran font tampilan teks secara otomatis agar sesuai dengan tampilan. Anda menetapkan ukuran font yang tetap.
sulai
0

Ini harus menjadi solusi sederhana:

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

Perluas TextView dan timpa onDraw dengan kode di bawah ini. Ini akan menjaga rasio aspek teks tetapi ukurannya untuk mengisi ruang. Anda dapat dengan mudah memodifikasi kode untuk meregang jika perlu.

  @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
sumber
0

Saya menulis kelas pembantu pendek yang membuat tampilan teks pas dengan lebar tertentu dan menambahkan ellipsize "..." pada akhirnya jika ukuran teks minimum tidak dapat dicapai.

Perlu diingat bahwa itu hanya membuat teks lebih kecil sampai cocok atau sampai ukuran teks minimum tercapai. Untuk menguji dengan ukuran besar, atur ukuran teks ke angka besar sebelum memanggil metode bantuan.

Dibutuhkan Pixel, jadi jika Anda menggunakan nilai dari dimen, Anda dapat menyebutnya seperti ini:


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

Ini adalah kelas yang saya gunakan:


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
sumber
Apakah ini mendukung banyak baris teks?
Roymunson
0

Jika tranformasi seperti allCaps diatur, pendekatan speedplane buggy. Saya memperbaikinya, menghasilkan kode berikut (maaf, reputasi saya tidak memungkinkan saya untuk menambahkan ini sebagai komentar untuk solusi 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
sumber
0

Saya tidak tahu ini cara yang benar atau tidak, tetapi berfungsi ... ambil tampilan Anda dan periksa OnGlobalLayoutListener () dan dapatkan textview linecount kemudian atur textSize.

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

Beberapa kode barisnya sangat sederhana ..

Mathan Chinna
sumber