ปรับสเกลข้อความอัตโนมัติให้พอดีภายในขอบเขต


850

ฉันกำลังมองหาวิธีที่ดีที่สุดในการปรับขนาดการตัดข้อความTextViewเพื่อให้พอดีกับ getHeight และ getWidth ฉันไม่เพียงแค่มองหาวิธีการตัดข้อความ - ฉันต้องการให้แน่ใจว่าทั้งสองห่อและมีขนาดเล็กพอที่จะพอดีกับหน้าจอทั้งหมด

ฉันเคยเห็นบางกรณีเกี่ยวกับ StackOverflow ที่ต้องการการปรับขนาดอัตโนมัติ แต่เป็นกรณีพิเศษมากที่มีโซลูชันแฮ็คไม่มีวิธีแก้ปัญหาหรือเกี่ยวข้องกับการวาดTextViewซ้ำซ้ำจนกระทั่งมันมีขนาดเล็กพอ (ซึ่งเป็นหน่วยความจำที่รุนแรงและบังคับให้ ผู้ใช้สามารถดูข้อความย่อทีละขั้นตอนกับการเรียกซ้ำทุกครั้ง)

แต่ฉันแน่ใจว่ามีใครบางคนที่นั่นพบทางออกที่ดีที่ไม่เกี่ยวข้องกับสิ่งที่ฉันกำลังทำอยู่: เขียนกิจวัตรหนัก ๆ หลายอย่างที่แยกวิเคราะห์และวัดข้อความปรับขนาดตัวอักษรและทำซ้ำจนกว่าจะพบขนาดที่เหมาะสม

รูทีนใดที่TextViewใช้ห่อข้อความ ไม่สามารถนำมาใช้ในการทำนายว่าข้อความจะเล็กพอหรือไม่?

tl; dr : มีวิธีปฏิบัติที่ดีที่สุดในการปรับขนาด a TextViewให้พอดีห่อใน getHeight และ getWidth


ฉันยังลองใช้ getEllipsisCount ใน StaticLayout เพื่อตรวจจับเมื่อข้อความออกนอกขอบเขต แต่นั่นก็ไม่ได้ผลสำหรับฉันฉันก็ถามถึงเรื่องนั้นเช่นกัน: stackoverflow.com/questions/5084647/…
Nathan Fig

ทำไมคุณไม่วาด textview ของปะแก้เก้าตัว มันจะปรับตามขอบเขตโดยอัตโนมัติ ฉันได้รับคุณพี่ชายใช่ไหม?
AZ_

เก้าข้อความในโปรแกรมปะแก้? ฉันไม่คุ้นเคยกับเก้าปะ แต่ดูเหมือนว่าจะเป็นรูปแบบรูปภาพ แต่ ... ฉันกำลังมองหาเอกสารว่าจะใช้กับ TextView ได้อย่างไร รู้ว่าฉันจะได้รับข้อมูลเพิ่มเติมที่ไหน?
นาธานรูปที่

การอ่านเพิ่มเติมเล็กน้อยฉันได้รับความประทับใจว่าเก้าแพทช์เป็นเพียงการปรับขนาดสิ่งที่มีรูปแบบที่กำหนดไว้แล้ว - แต่ฉันต้องการสิ่งที่จะใช้สตริงและหาขนาดและรูปแบบที่เหมาะสมภายในขอบเขตที่แน่นอน
นาธานรูปที่

สำเนาซ้ำที่เป็นไปได้ของ: stackoverflow.com/a/7875656/435605
AlikElzin-kilaka

คำตอบ:


160

ตั้งแต่เดือนมิถุนายน 2018 Android เริ่มสนับสนุนคุณสมบัตินี้อย่างเป็นทางการสำหรับAndroid 4.0 (ระดับ API 14) และสูงกว่า
ตรวจสอบออกที่: ปรับขนาด TextViews

ด้วย Android 8.0 (API ระดับ 26) และสูงกว่า :

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:autoSizeTextType="uniform"
    android:autoSizeMinTextSize="12sp"
    android:autoSizeMaxTextSize="100sp"
    android:autoSizeStepGranularity="2sp" />

โปรแกรม:

setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, 
        int autoSizeStepGranularity, int unit)

textView.setAutoSizeTextTypeUniformWithConfiguration(
                1, 17, 1, TypedValue.COMPLEX_UNIT_DIP);


รุ่น Android ก่อนหน้า Android 8.0 (ระดับ API 26) :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <TextView
      android:layout_width="match_parent"
      android:layout_height="200dp"
      app:autoSizeTextType="uniform"
      app:autoSizeMinTextSize="12sp"
      app:autoSizeMaxTextSize="100sp"
      app:autoSizeStepGranularity="2sp" />

</LinearLayout>

โปรแกรม:

TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
TextView textView, int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) 

TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(textView, 1, 17, 1,
TypedValue.COMPLEX_UNIT_DIP);

ข้อควรสนใจ: TextViewต้องมี layout_width = " match_parent " หรือขนาดที่แน่นอน!


2
ผู้อ่านในอนาคตอย่าลืมเพิ่มimplementation 'com.android.support:support-compat:28.0.0'ในapp/build.gradleสำหรับapp: ...คุณลักษณะการทำงาน
daka

คุณเขียน: "ตั้งแต่เดือนมิถุนายน 2018 Android เริ่มสนับสนุนคุณสมบัตินี้อย่างเป็นทางการสำหรับ Android 4.0 (ระดับ API 14) และสูงกว่า" ?? .. แอตทริบิวต์ autoSizeTextType จะใช้เฉพาะใน API ระดับ 26 ขึ้นไป
nnyerges

4
@nnyerges คุณสามารถใช้รองรับแอพรองรับ API รุ่นเก่า (<26), ใช้งาน: แอพ: autoSizeTextType = "ชุด"
คิดรหัสสองครั้งเมื่อ

สิ่งที่ฉันสะดุดคือคุณควรทดสอบด้วยขนาดตัวอักษรที่แตกต่างกันคุณสามารถตั้งค่าในการตั้งค่า Android มิฉะนั้นautoSizeMaxTextSizeอาจต่ำเกินไปและข้อความถูกตัดออก
L3n95

1105

ในฐานะนักพัฒนาอุปกรณ์พกพาฉันเสียใจที่ไม่พบสิ่งใดที่รองรับการปรับขนาดอัตโนมัติ การค้นหาของฉันไม่ได้เปิดใช้งานสิ่งใดที่เหมาะกับฉันและในที่สุดฉันใช้เวลาครึ่งวันหยุดที่ดีขึ้นและสร้างมุมมองข้อความปรับขนาดอัตโนมัติของฉันเอง ฉันจะโพสต์รหัสที่นี่และหวังว่ามันจะเป็นประโยชน์สำหรับคนอื่น

คลาสนี้ใช้โครงร่างแบบสแตติกพร้อมสีตัวอักษรของมุมมองข้อความต้นฉบับเพื่อวัดความสูง จากตรงนั้นฉันก็ลดขนาดตัวอักษรลงไป 2 พิกเซลและบันทึกไว้จนกว่าฉันจะมีขนาดที่พอดี ในตอนท้ายถ้าข้อความยังไม่พอดีฉันผนวกจุดไข่ปลา ฉันมีข้อกำหนดที่จะทำให้ข้อความและมุมมองใช้งานซ้ำและดูเหมือนว่าจะทำงานได้ดีบนอุปกรณ์ที่ฉันมีและดูเหมือนว่าจะทำงานได้เร็วพอสำหรับฉัน

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT YOU WANT TO.
 */

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

/**
 * Text view that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 20;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = 0;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if (mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }

    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {

        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        if (getTransformationMethod() != null) {
            text = getTransformationMethod().getTransformation(text, this);
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();
        // If there is a max text size set, use the lesser of that and the default text size
        float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;

        // Get the required text height
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
        while (textHeight > height && targetTextSize > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        }

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paint = new TextPaint(textPaint);
            // Draw using a static layout
            StaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if (layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if (lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = textPaint.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while (width < lineWidth + ellipseWidth) {
                        lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if (mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paintCopy = new TextPaint(paint);
        // Update the text paint object
        paintCopy.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

คำเตือน. มีข้อผิดพลาดคงที่ที่สำคัญที่มีผลต่อ Android 3.1 - 4.04 ทำให้เครื่องมือ AutoResizingTextView ทั้งหมดไม่ทำงาน โปรดอ่าน: https://stackoverflow.com/a/21851157/2075875


7
นี่เป็นทางออกที่ดีที่สุดที่ฉันเคยเห็นทำได้ดีมาก มันไม่เหมาะกับความต้องการของฉันเพราะวิธีที่ Android ชอบแบ่งคำเมื่อห่อดังนั้นฉันจะติดกับโซลูชันที่กำหนดเองของฉัน - แต่ชั้นเรียนของคุณดีกว่าสำหรับการใช้งานทั่วไป
นาธานรูปที่

246
ด้วยจิตวิญญาณของ Android ทุกอย่างเป็นของคุณ! และหวังว่าเมื่อคุณสร้างบางสิ่งที่อาจเป็นประโยชน์คุณสามารถแชร์สิ่งนั้นได้ :)
Chase

7
ตกลงดังนั้นฉันได้ทำการปรับปรุงรหัส การลบ 1 จากดัชนีบรรทัดเป็นเจตนาและฉันเพิ่มความคิดเห็น ข้อผิดพลาดที่คุณได้รับอาจมาจากเมื่อมีความสูงไม่เพียงพอที่จะแสดงข้อความแม้แต่บรรทัดเดียว ฉันเพิ่มการตรวจสอบและลบข้อความในกรณีนั้น (ผมทำงานใน Shibuya ดังนั้นฉันจะขึ้นสำหรับเบียร์ตลอดเวลา :)
เชส

7
คุณลองเวอร์ชั่นที่แก้ไขล่าสุดแล้วหรือยัง ฉันทำการเปลี่ยนแปลงเล็กน้อยสำหรับ Jelly Bean ประมาณหนึ่งสัปดาห์ที่ผ่านมา นี่น่าจะเป็น github
ไล่

7
ไม่ทำงานกับ TextView ภายใน ListView Items รวมถึงข้างในรวมเค้าโครงอื่น ๆ
Vladyslav Matviienko

145

ปรับปรุง:รหัสต่อไปนี้ยังตอบสนองความต้องการของ AutoScaleTextView ในอุดมคติตามที่อธิบายไว้ที่นี่: ปรับพอดี TextView สำหรับ Androidและทำเครื่องหมายเป็นผู้ชนะ

UPDATE 2:รองรับการเพิ่ม maxlines ตอนนี้ทำงานได้ดีก่อน API ระดับ 16

การปรับปรุงที่ 3:การสนับสนุนสำหรับandroid:drawableLeft, android:drawableRight, android:drawableTopและandroid:drawableBottomแท็กเพิ่มขอบคุณแก้ไขง่าย MartinH ของที่นี่


ความต้องการของฉันแตกต่างกันเล็กน้อย ฉันต้องการวิธีที่มีประสิทธิภาพในการปรับขนาดเพราะฉันเคลื่อนไหวเป็นจำนวนเต็มตั้งแต่ 0 ถึง ~ 4000 TextViewใน 2 วินาทีและฉันต้องการปรับขนาดตามนั้น โซลูชันของฉันทำงานแตกต่างกันเล็กน้อย นี่คือผลลัพธ์สุดท้ายที่มีลักษณะ:

ป้อนคำอธิบายรูปภาพที่นี่

และรหัสที่ผลิต:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp" >

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:ellipsize="none"
        android:maxLines="2"
        android:text="Auto Resized Text, max 2 lines"
        android:textSize="100sp" /> <!-- maximum size -->

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:ellipsize="none"
        android:gravity="center"
        android:maxLines="1"
        android:text="Auto Resized Text, max 1 line"
        android:textSize="100sp" /> <!-- maximum size -->

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Auto Resized Text"
        android:textSize="500sp" /> <!-- maximum size -->

</LinearLayout>

และสุดท้ายรหัส java:

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Build;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;

public class AutoResizeTextView extends TextView {
private interface SizeTester {
    /**
     * 
     * @param suggestedSize
     *            Size of text to be tested
     * @param availableSpace
     *            available space in which text must fit
     * @return an integer < 0 if after applying {@code suggestedSize} to
     *         text, it takes less space than {@code availableSpace}, > 0
     *         otherwise
     */
    public int onTestSize(int suggestedSize, RectF availableSpace);
}

private RectF mTextRect = new RectF();

private RectF mAvailableSpaceRect;

private SparseIntArray mTextCachedSizes;

private TextPaint mPaint;

private float mMaxTextSize;

private float mSpacingMult = 1.0f;

private float mSpacingAdd = 0.0f;

private float mMinTextSize = 20;

private int mWidthLimit;

private static final int NO_LINE_LIMIT = -1;
private int mMaxLines;

private boolean mEnableSizeCache = true;
private boolean mInitiallized;

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

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

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

private void initialize() {
    mPaint = new TextPaint(getPaint());
    mMaxTextSize = getTextSize();
    mAvailableSpaceRect = new RectF();
    mTextCachedSizes = new SparseIntArray();
    if (mMaxLines == 0) {
        // no value was assigned during construction
        mMaxLines = NO_LINE_LIMIT;
    }
    mInitiallized = true;
}

@Override
public void setText(final CharSequence text, BufferType type) {
    super.setText(text, type);
    adjustTextSize(text.toString());
}

@Override
public void setTextSize(float size) {
    mMaxTextSize = size;
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

@Override
public void setMaxLines(int maxlines) {
    super.setMaxLines(maxlines);
    mMaxLines = maxlines;
    reAdjust();
}

public int getMaxLines() {
    return mMaxLines;
}

@Override
public void setSingleLine() {
    super.setSingleLine();
    mMaxLines = 1;
    reAdjust();
}

@Override
public void setSingleLine(boolean singleLine) {
    super.setSingleLine(singleLine);
    if (singleLine) {
        mMaxLines = 1;
    } else {
        mMaxLines = NO_LINE_LIMIT;
    }
    reAdjust();
}

@Override
public void setLines(int lines) {
    super.setLines(lines);
    mMaxLines = lines;
    reAdjust();
}

@Override
public void setTextSize(int unit, float size) {
    Context c = getContext();
    Resources r;

    if (c == null)
        r = Resources.getSystem();
    else
        r = c.getResources();
    mMaxTextSize = TypedValue.applyDimension(unit, size,
            r.getDisplayMetrics());
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

@Override
public void setLineSpacing(float add, float mult) {
    super.setLineSpacing(add, mult);
    mSpacingMult = mult;
    mSpacingAdd = add;
}

/**
 * Set the lower text size limit and invalidate the view
 * 
 * @param minTextSize
 */
public void setMinTextSize(float minTextSize) {
    mMinTextSize = minTextSize;
    reAdjust();
}

private void reAdjust() {
    adjustTextSize(getText().toString());
}

private void adjustTextSize(String string) {
    if (!mInitiallized) {
        return;
    }
    int startSize = (int) mMinTextSize;
    int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
        - getCompoundPaddingTop();
    mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
        - getCompoundPaddingRight();
    mAvailableSpaceRect.right = mWidthLimit;
    mAvailableSpaceRect.bottom = heightLimit;
    super.setTextSize(
            TypedValue.COMPLEX_UNIT_PX,
            efficientTextSizeSearch(startSize, (int) mMaxTextSize,
                    mSizeTester, mAvailableSpaceRect));
}

private final SizeTester mSizeTester = new SizeTester() {
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public int onTestSize(int suggestedSize, RectF availableSPace) {
        mPaint.setTextSize(suggestedSize);
        String text = getText().toString();
        boolean singleline = getMaxLines() == 1;
        if (singleline) {
            mTextRect.bottom = mPaint.getFontSpacing();
            mTextRect.right = mPaint.measureText(text);
        } else {
            StaticLayout layout = new StaticLayout(text, mPaint,
                    mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
                    mSpacingAdd, true);
            // return early if we have more lines
            if (getMaxLines() != NO_LINE_LIMIT
                    && layout.getLineCount() > getMaxLines()) {
                return 1;
            }
            mTextRect.bottom = layout.getHeight();
            int maxWidth = -1;
            for (int i = 0; i < layout.getLineCount(); i++) {
                if (maxWidth < layout.getLineWidth(i)) {
                    maxWidth = (int) layout.getLineWidth(i);
                }
            }
            mTextRect.right = maxWidth;
        }

        mTextRect.offsetTo(0, 0);
        if (availableSPace.contains(mTextRect)) {
            // may be too small, don't worry we will find the best match
            return -1;
        } else {
            // too big
            return 1;
        }
    }
};

/**
 * Enables or disables size caching, enabling it will improve performance
 * where you are animating a value inside TextView. This stores the font
 * size against getText().length() Be careful though while enabling it as 0
 * takes more space than 1 on some fonts and so on.
 * 
 * @param enable
 *            enable font size caching
 */
public void enableSizeCache(boolean enable) {
    mEnableSizeCache = enable;
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

private int efficientTextSizeSearch(int start, int end,
        SizeTester sizeTester, RectF availableSpace) {
    if (!mEnableSizeCache) {
        return binarySearch(start, end, sizeTester, availableSpace);
    }
    String text = getText().toString();
    int key = text == null ? 0 : text.length();
    int size = mTextCachedSizes.get(key);
    if (size != 0) {
        return size;
    }
    size = binarySearch(start, end, sizeTester, availableSpace);
    mTextCachedSizes.put(key, size);
    return size;
}

private static int binarySearch(int start, int end, SizeTester sizeTester,
        RectF availableSpace) {
    int lastBest = start;
    int lo = start;
    int hi = end - 1;
    int mid = 0;
    while (lo <= hi) {
        mid = (lo + hi) >>> 1;
        int midValCmp = sizeTester.onTestSize(mid, availableSpace);
        if (midValCmp < 0) {
            lastBest = lo;
            lo = mid + 1;
        } else if (midValCmp > 0) {
            hi = mid - 1;
            lastBest = hi;
        } else {
            return mid;
        }
    }
    // make sure to return last best
    // this is what should always be returned
    return lastBest;

}

@Override
protected void onTextChanged(final CharSequence text, final int start,
        final int before, final int after) {
    super.onTextChanged(text, start, before, after);
    reAdjust();
}

@Override
protected void onSizeChanged(int width, int height, int oldwidth,
        int oldheight) {
    mTextCachedSizes.clear();
    super.onSizeChanged(width, height, oldwidth, oldheight);
    if (width != oldwidth || height != oldheight) {
        reAdjust();
    }
}
}

ฉันได้ตรวจสอบปัญหาของคุณแล้ว คุณไม่ต้องการ AutoResizeTextView นี้ปัญหาของคุณเป็นอย่างอื่น ฉันได้แสดงความคิดเห็นกับคำถามของคุณ ปัญหานี้ไม่มีอะไรเกี่ยวข้องกับขนาด
M-WaJeEh

ฉันสังเกตเห็นว่ามี @TargetApi (Build.VERSION_CODES.JELLY_BEAN) สามารถทำงานกับ 2.3 ได้หรือไม่
Nick Jian

10
ดีใจที่คุณชอบใช่ฉันมีใบอนุญาตตรวจสอบที่นี่ docs.google.co.th/document/d/…
M-WaJeEh

8
ดูเหมือนว่าสิ่งนี้จะไม่ทำงานกับ Typefaces ที่กำหนดเองเนื่องจากสิ่งต่างๆถูกตัดออกในแนวตั้ง คุณจะจัดการกับเรื่องนั้นอย่างไร? มีใครลองแบบอักษรที่กำหนดเองบ้างไหม
RealCasually

1
@RobinHood บางครั้งไม่เคารพStaticLayout widthเพียงแทนที่บรรทัดที่ส่งคืนล่าสุดonTestSize()ด้วย 'ถ้า (availableSpace.contain (mTextRect)) {// อาจมีขนาดเล็กเกินไปไม่ต้องกังวลเราจะพบการจับคู่ที่ดีที่สุด -1 } else {if (mTextRect.bottom <availableSpace.bottom && mTextRect.right> availableSpace.right) {// hack: O คืน -1; } // ผลตอบแทนที่มากเกินไป 1; } '
M-WaJeEh

41

จริงๆแล้วโซลูชันอยู่ในคลาสDialogTitleของ Google แม้ว่าจะไม่ได้ผลเท่าที่ยอมรับ แต่ก็ง่ายกว่ามากและปรับได้ง่าย

public class SingleLineTextView extends TextView {

  public SingleLineTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

  public SingleLineTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

  public SingleLineTextView(Context context) {
    super(context);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

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

    final Layout layout = getLayout();
    if (layout != null) {
      final int lineCount = layout.getLineCount();
      if (lineCount > 0) {
        final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
        if (ellipsisCount > 0) {

          final float textSize = getTextSize();

          // textSize is already expressed in pixels
          setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));

          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
      }
    }
  }

}

2
สงสัยว่าทำไมนี่ไม่ได้ +1 มากกว่านี้ แน่นอนแฮ็คโดยธรรมชาติ แต่เป็นวิธีที่ง่ายมาก สำหรับแอพที่วางเค้าโครง SimpleLineTextViews ที่ไม่เลื่อนไม่กี่นี่เป็นวิธีที่ง่ายที่สุดในการไป เยี่ยมมาก!
Tom

การแก้ปัญหานี้ไม่ได้ทำงานจนกว่าคุณจะเรียกในrequestLayout onTextChangedแม้จะมีการแก้ไขนี้การแก้ปัญหาที่นี่ไม่สามารถใช้ได้ในหลายกรณีเนื่องจากการลดขนาดข้อความโดย 1 ดูเหมือนจะไม่สามารถใช้ได้ในทุกกรณี: เราอาจต้องลดขนาดข้อความให้มากขึ้น
Zhi Wang

10
@SergioCarneiro ดีหา แต่super.onMeasure(widthMeasureSpec, heightMeasureSpec);ไม่ก่อให้เกิดการเรียกซ้ำ measure(widthMeasureSpec, heightMeasureSpec);แต่ ดังนั้นฉันจึงเปลี่ยนเป็น 'measure (widthMeasureSpec, heightMeasureSpec); "และใช้งานได้ฉันเพิ่งทดสอบบน Android 4.4 (Nexus 5) และ Android 4.0.4 (Samsung SII-LTE) และทำงานได้ดีกับทั้งคู่ (ฉันยังใช้ แบบอักษร OTF แบบกำหนดเองของญี่ปุ่น) คว้าตัวแก้ไขของฉันที่นี่: gist.github.com/mrleolink/0dfeef749da1b854a44b
Leo

2
คำตอบที่ดีที่สุด เมื่อใช้เวอร์ชั่นของ LeoLink มันทำงานได้ดีกับตัวเลียนแบบ 2.3 และ 4.0 และอุปกรณ์จริงของฉัน ถ้าใช้ใน listview อย่าลืมเรียก requestLayout () หลังจากตั้งค่าข้อความ
adbie

1
ฉันได้ง่ามรุ่น @LeoLink ก็ใช้maxLinesและrequestLayout()บนsetTextเช่น @adbie กล่าวว่า FixedLineTextView -> gist.github.com/Kevinrob/09742d9069e4e4e4ab66
Kevin Robatel

36

ฉันเริ่มต้นด้วยโซลูชันของ Chase แต่ต้องปรับสองสิ่งก่อนที่มันจะทำงานได้ตามที่คาดไว้บนอุปกรณ์ของฉัน (Galaxy Nexus, Android 4.1):

  1. การใช้สำเนาของ TextPaint สำหรับการวัดเค้าโครงเอกสารสำหรับTextView.getPaint ()ระบุว่าควรใช้แบบอ่านอย่างเดียวดังนั้นฉันจึงทำสำเนาทั้งสองแห่งที่เราใช้วัตถุสีสำหรับการวัด:

    // 1. in resizeText()
    if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
      // Draw using a static layout
      // modified: use a copy of TextPaint for measuring
      TextPaint paint = new TextPaint(textPaint);
    
    // 2. in getTextHeight()
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
      // modified: make a copy of the original TextPaint object for measuring
      // (apparently the object gets modified while measuring, see also the
      // docs for TextView.getPaint() (which states to access it read-only)
      TextPaint paint = new TextPaint(originalPaint);
      // Update the text paint object
      paint.setTextSize(textSize);
      ...
  2. เพิ่มหน่วยเพื่อตั้งค่าขนาดตัวอักษร

    // modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
    setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
    setLineSpacing(mSpacingAdd, mSpacingMult);

ด้วยการปรับเปลี่ยนทั้งสองนี้โซลูชันทำงานได้อย่างสมบูรณ์แบบสำหรับฉันขอบคุณ Chase! ฉันไม่ทราบว่าเป็นเพราะ Android 4.x หรือไม่ว่าโซลูชันดั้งเดิมไม่ทำงาน ในกรณีที่คุณต้องการเห็นมันทำงานหรือทดสอบว่ามันใช้งานได้จริงบนอุปกรณ์ของคุณคุณสามารถดูที่แอพแฟลชการ์ดของฉันFlashcards ToGoที่ฉันใช้โซลูชันนี้เพื่อปรับขนาดข้อความของแฟลชการ์ด ข้อความอาจมีความยาวตามอำเภอใจและแฟลชการ์ดจะแสดงในกิจกรรมต่าง ๆ บางครั้งก็เล็กกว่าบางครั้งใหญ่กว่ารวมถึงในแนวนอน + แนวตั้งและฉันไม่พบเคสมุมใด ๆ ที่โซลูชันไม่ทำงานอย่างถูกต้อง ...


23

ฉันเริ่มด้วยคลาส AutoResizeTextView ของ Chase และทำการเปลี่ยนแปลงเล็กน้อยดังนั้นมันจะพอดีกับทั้งแนวตั้งและแนวนอน

ฉันยังค้นพบข้อผิดพลาดที่ทำให้ Null Pointer Exception ใน Layout Editor (ใน Eclipse) ภายใต้เงื่อนไขที่ค่อนข้างคลุมเครือ

เปลี่ยน 1: ใส่ข้อความทั้งแนวตั้งและแนวนอน

เวอร์ชันดั้งเดิมของ Chase จะลดขนาดข้อความจนกว่าจะพอดีในแนวตั้ง แต่อนุญาตให้ข้อความกว้างกว่าเป้าหมาย ในกรณีของฉันฉันต้องการข้อความให้พอดีกับความกว้างที่ระบุ

การเปลี่ยนแปลงนี้ทำให้การปรับขนาดจนกว่าข้อความจะพอดีทั้งในแนวตั้งและแนวนอน

ในresizeText(int ,int)เปลี่ยนแปลงจาก:

// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(textHeight > height && targetTextSize > mMinTextSize) {
    targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
    textHeight = getTextHeight(text, textPaint, width, targetTextSize);
    }

ถึง:

// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
int textWidth  = getTextWidth(text, textPaint, width, targetTextSize);

// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(((textHeight >= height) || (textWidth >= width) ) && targetTextSize > mMinTextSize) {
    targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
    textHeight = getTextHeight(text, textPaint, width, targetTextSize);
    textWidth  = getTextWidth(text, textPaint, width, targetTextSize);
    }

จากนั้นในตอนท้ายของไฟล์ให้ผนวกgetTextWidth()รูทีน getTextHeight()มันเป็นเพียงการแก้ไขเล็กน้อย มันอาจจะมีประสิทธิภาพมากกว่าในการรวมเข้ากับรูทีนหนึ่งซึ่งจะส่งกลับทั้งความสูงและความกว้าง

// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextWidth(CharSequence source, TextPaint paint, int width, float textSize) {
    // Update the text paint object
    paint.setTextSize(textSize);
    // Draw using a static layout
    StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
    layout.draw(sTextResizeCanvas);
    return layout.getWidth();
}  




เปลี่ยน 2: แก้ไข EmptyStackException ใน Eclipse Android Layout Editor

ภายใต้เงื่อนไขที่ค่อนข้างคลุมเครือและแม่นยำมากตัวแก้ไขเลย์เอาท์จะล้มเหลวในการแสดงการแสดงผลกราฟิกของเลย์เอาต์ มันจะโยนข้อยกเว้น "EmptyStackException: null" ใน com.android.ide.eclipse.adt

เงื่อนไขที่ต้องการคือ:
- สร้างวิดเจ็ต AutoResizeTextView
- สร้างสไตล์สำหรับวิดเจ็ตนั้น
- ระบุรายการข้อความในสไตล์; ไม่ได้อยู่ในคำจำกัดความของวิดเจ็ต

ในขณะที่:

ความละเอียด / รูปแบบ / main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <com.ajw.DemoCrashInADT.AutoResizeTextView
        android:id="@+id/resizingText"
        style="@style/myTextStyle" />

</LinearLayout>

ความละเอียด / ค่า / myStyles.xml:

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

    <style name="myTextStyle" parent="@android:style/Widget.TextView">
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_width">fill_parent</item>
        <item name="android:text">some message</item>
    </style>

</resources>

ด้วยไฟล์เหล่านี้การเลือกแท็บGraphical Layoutเมื่อแก้ไขmain.xmlจะปรากฏขึ้น:

ข้อผิดพลาด!
EmptyStackException: null
รายละเอียดข้อยกเว้นถูกบันทึกไว้ในหน้าต่าง> แสดงมุมมอง> บันทึกข้อผิดพลาด

แทนมุมมองกราฟิกของเค้าโครง

เพื่อให้เรื่องที่สั้นเกินไปยาวเกินไปฉันได้ติดตามเรื่องนี้ในบรรทัดต่อไปนี้ (อีกครั้งในresizeText):

// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;

ปัญหาคือว่าภายใต้เงื่อนไขที่เฉพาะเจาะจง mTextSize จะไม่เริ่มต้น; มันมีค่า 0

ด้วยด้านบนtargetTextSizeตั้งเป็นศูนย์ (เป็นผลมาจาก Math.min)

ศูนย์นั้นถูกส่งผ่านไปยังgetTextHeight()(และgetTextWidth()) เป็นtextSizeอาร์กิวเมนต์ เมื่อมันมาถึง
layout.draw(sTextResizeCanvas);
เราจะได้รับการยกเว้น

การทดสอบว่ามีประสิทธิภาพมากกว่า(mTextSize == 0)ในตอนต้นหรือresizeText()ไม่มากกว่าการทดสอบgetTextHeight()และgetTextWidth(); การทดสอบก่อนหน้านี้บันทึกงานที่แทรกแซงทั้งหมด

ด้วยการอัปเดตเหล่านี้ไฟล์ (เช่นเดียวกับในแอพทดสอบความผิดพลาดของฉัน) คือ:

//
// from:  http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds
//
//

package com.ajw.DemoCrashInADT;

import android.content.Context;
import android.graphics.Canvas;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
 * Text view that auto adjusts text size to fit within the view. If the text
 * size equals the minimum text size and still does not fit, append with an
 * ellipsis.
 *
 * 2011-10-29 changes by Alan Jay Weiner
 *              * change to fit both vertically and horizontally  
 *              * test mTextSize for 0 in resizeText() to fix exception in Layout Editor
 *
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 20;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Off screen canvas for text size rendering
    private static final Canvas sTextResizeCanvas = new Canvas();

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for
    // resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = 0;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;


    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }


    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }


    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }


    /**
     * When text changes, set the force resize flag to true and reset the text
     * size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }


    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }


    /**
     * Register listener to receive resize notifications
     *
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }


    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }


    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }


    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }


    /**
     * Set the upper text size limit and invalidate the view
     *
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }


    /**
     * Return upper text size limit
     *
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }


    /**
     * Set the lower text size limit and invalidate the view
     *
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }


    /**
     * Return lower text size limit
     *
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }


    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     *
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }


    /**
     * Return flag to add ellipsis to text that overflows at the smallest text
     * size
     *
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }


    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
        mMaxTextSize = mTextSize;
    }


    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft()
                    - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom()
                    - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }


    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }


    /**
     * Resize the text size with specified width and height
     *
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no
        // text
        // or if mTextSize has not been initialized
        if (text == null || text.length() == 0 || height <= 0 || width <= 0
                || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();

        // If there is a max text size set, use the lesser of that and the
        // default text size
        float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize)
                : mTextSize;

        // Get the required text height
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        int textWidth = getTextWidth(text, textPaint, width, targetTextSize);

        // Until we either fit within our text view or we had reached our min
        // text size, incrementally try smaller sizes
        while (((textHeight > height) || (textWidth > width))
                && targetTextSize > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            textWidth = getTextWidth(text, textPaint, width, targetTextSize);
        }

        // If we had reached our minimum text size and still don't fit, append
        // an ellipsis
        if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            StaticLayout layout = new StaticLayout(text, textPaint, width,
                    Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            layout.draw(sTextResizeCanvas);
            int lastLine = layout.getLineForVertical(height) - 1;
            int start = layout.getLineStart(lastLine);
            int end = layout.getLineEnd(lastLine);
            float lineWidth = layout.getLineWidth(lastLine);
            float ellipseWidth = textPaint.measureText(mEllipsis);

            // Trim characters off until we have enough room to draw the
            // ellipsis
            while (width < lineWidth + ellipseWidth) {
                lineWidth = textPaint.measureText(text.subSequence(start, --end + 1)
                        .toString());
            }
            setText(text.subSequence(0, end) + mEllipsis);

        }

        // Some devices try to auto adjust line spacing, so force default line
        // spacing
        // and invalidate the layout as a side effect
        textPaint.setTextSize(targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if (mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }


    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint paint, int width,
            float textSize) {
        // Update the text paint object
        paint.setTextSize(textSize);
        // Draw using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        layout.draw(sTextResizeCanvas);
        return layout.getHeight();
    }


    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextWidth(CharSequence source, TextPaint paint, int width,
            float textSize) {
        // Update the text paint object
        paint.setTextSize(textSize);
        // Draw using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        layout.draw(sTextResizeCanvas);
        return layout.getWidth();
    }

}



ขอบคุณมากสำหรับ Chase ที่โพสต์โค้ดเริ่มต้น ฉันสนุกกับการอ่านผ่านเพื่อดูว่ามันทำงานอย่างไรและฉันยินดีที่จะเพิ่มเข้าไป


3
BUG DETECTED: getTextWidth()ไม่ทำงานเลยเมื่อคุณผ่านความกว้างตามที่ต้องการในStaticLayoutConstructor เดาว่าความกว้างจะกลับมาในกรณีนั้นจากgetWidth()วิธีการ?
se.solovyev

หมายเหตุสำหรับคนในอนาคตเช่นฉันที่สะดุดข้ามสิ่งนี้: วัตถุ 'ระบายสี' มีวิธีการวัดข้อความที่สามารถเรียกใช้เพื่อให้ได้ความกว้างของข้อความ
Xono

2
สวย. รหัสนี้ใช้ได้กับฉันใน Android 4.1 บน Galaxy Nexus ของฉันในแอป KeepScore ของฉัน ( github.com/nolanlawson/KeepScore ) ในขณะที่เวอร์ชันของ Chase ไม่ได้ใช้ คำขอคุณสมบัติ: โปรดใส่รหัสนี้ใน GitHub พวก! StackOverflow ไม่ใช่สถานที่สำหรับการตรวจสอบแพตช์และโค้ด :)
nlawson

1
ดังกล่าวข้างต้น layout.getWidth () เพียงแค่ส่งกลับความกว้างมันถูกสร้างขึ้นด้วย สิ่งที่ฉันต้องทำเพื่อทำให้งานนี้คือการสร้างเลย์เอาต์ที่มีความกว้าง 4096 แล้วเรียกใช้ getLineWidth () บนทุกบรรทัดแล้วคืนค่าสูงสุด
Edward Falk

1
22 upvote สำหรับรหัสที่ไม่ทำงาน / ทำไม?! ใช้หลายบรรทัดและไม่พอดีกับพื้นที่

21

AppcompatTextView รองรับการปรับขนาดอัตโนมัติเริ่มตั้งแต่ Support Library 26.0 TextView ใน Android O ก็ทำงานเช่นเดียวกัน ข้อมูลเพิ่มเติมสามารถดูได้ที่นี่ การตรวจสอบการสาธิตง่ายสามารถพบได้ที่นี่

<LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:autoSizeTextType="uniform"
        app:autoSizeMinTextSize="12sp"
        app:autoSizeMaxTextSize="100sp"
        app:autoSizeStepGranularity="2sp"
      />

</LinearLayout>

1
ฉันต้องตั้งค่าandroid:lines="1"ใน TextView เพื่อปรับขนาดข้อความ หากไม่มีแอตทริบิวต์นี้ข้อความจะถูกรวมเป็นสองบรรทัด
rewgoes

การนำเข้าเพื่อให้ทำงานนี้คืออะไร ฉันได้รับข้อผิดพลาดในการสร้างเมื่อฉันลอง 26.0.0-beta-1
Psest328

1
@ Psest328, คุณใช้ maven {url " maven.google.com "} หรือไม่
makata

17

วิธีแก้ปัญหาสำหรับ Android 4.x:

ฉันพบ AutoResizeTextView และใช้งานได้ดีกับ Android 2.1 emulator ฉันรักมันมาก แต่น่าเสียดายที่มันล้มเหลวในมือถือของฉันเอง 4.0.4 และตัวเลียนแบบ 4.1 หลังจากลองแล้วฉันพบว่ามันสามารถแก้ไขได้ง่าย ๆ โดยการเพิ่มคุณสมบัติต่อไปนี้ในคลาส AutoResizeTextView ใน xml:

หุ่นยนต์: ellipsize = "ไม่มี"

หุ่นยนต์: singleLine = "true"

ด้วย 2 บรรทัดข้างต้นตอนนี้ AutoResizeTextView ทำงานได้อย่างสมบูรณ์บนตัวเลียนแบบ 2.1 และ 4.1 และโทรศัพท์มือถือของฉันเอง 4.0.4 ในขณะนี้

หวังว่านี่จะช่วยคุณได้ :-)


2
ฉันแนะนำวิธีแก้ปัญหาที่เสนอโดย @onoelle มากขึ้นอย่างแน่นอน มันทำงานได้อย่างสมบูรณ์แบบในทุกกรณี เช่นกรณีของฉันไม่ใช่มุมมองข้อความบรรทัดเดียว
Boris Strandjev

16

คำเตือนข้อผิดพลาดใน Android Honeycomb และ Ice Cream Sandwich

เวอร์ชัน Androids: 3.1 - 4.04 มีข้อผิดพลาดที่ setTextSize () ด้านในของ TextView ใช้งานได้เฉพาะครั้งที่ 1 (การเรียกใช้ครั้งที่ 1)

มีการอธิบายข้อบกพร่องที่นี่: http://code.google.com/p/android/issues/detail?id=22493 http://code.google.com/p/android/issues/detail?id=17343#c9

วิธีแก้ปัญหาคือการเพิ่มอักขระบรรทัดใหม่ลงในข้อความที่กำหนดให้ TextView ก่อนเปลี่ยนขนาด:

final String DOUBLE_BYTE_SPACE = "\u3000";
textView.append(DOUBLE_BYTE_SPACE);

ฉันใช้มันในรหัสของฉันดังต่อไปนี้:

final String DOUBLE_BYTE_SPACE = "\u3000";
AutoResizeTextView textView = (AutoResizeTextView) view.findViewById(R.id.aTextView);
String fixString = "";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1
   && android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {  
    fixString = DOUBLE_BYTE_SPACE;
}
textView.setText(fixString + "The text" + fixString);

ฉันเพิ่มอักขระ "\ u3000" นี้ทางซ้ายและขวาของข้อความเพื่อให้อยู่กึ่งกลาง หากคุณจัดแนวให้ชิดซ้ายแล้วให้เพิ่มไปทางขวาเท่านั้น แน่นอนว่ามันสามารถฝังตัวได้ด้วยวิดเจ็ต AutoResizeTextView แต่ฉันต้องการเก็บรหัสแก้ไขไว้ด้านนอก


เพิ่ม⁠ "\ u2060" เพื่อให้ข้อความอยู่กึ่งกลางในแนวตั้ง
igla

13

ความต้องการของฉันคือการปรับขนาดข้อความเพื่อให้พอดีกับขอบเขตการดูอย่างสมบูรณ์แบบ โซลูชันของ Chase ลดขนาดตัวอักษรเพียงอย่างเดียวอันนี้จะขยายข้อความหากมีพื้นที่เพียงพอ

ในการทำให้ทุกอย่างรวดเร็วและแม่นยำฉันใช้วิธีการแบ่งครึ่งแทนที่จะทำซ้ำในขณะที่ตามที่คุณเห็นในresizeText()วิธีการ นั่นเป็นเหตุผลที่คุณยังมีMAX_TEXT_SIZEตัวเลือกด้วย ฉันยังรวมถึงเคล็ดลับของ onoelle

ทดสอบบน Android 4.4

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT YOU WANT TO.
 */

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

/**
 * Text view that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 *
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 26;

    // Maximum text size for this text view
    public static final float MAX_TEXT_SIZE = 128;

    private static final int BISECTION_LOOP_WATCH_DOG = 30;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = MAX_TEXT_SIZE;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if(mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            //mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if(changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }


    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();

        // Bisection method: fast & precise
        float lower = mMinTextSize;
        float upper = mMaxTextSize;
        int loop_counter=1;
        float targetTextSize = (lower+upper)/2;
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        while(loop_counter < BISECTION_LOOP_WATCH_DOG && upper - lower > 1) {
            targetTextSize = (lower+upper)/2;
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            if(textHeight > height)
                upper = targetTextSize;
            else
                lower = targetTextSize;
            loop_counter++;
        }

        targetTextSize = lower;
        textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paintCopy = new TextPaint(textPaint);
            paintCopy.setTextSize(targetTextSize);
            StaticLayout layout = new StaticLayout(text, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if(layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if(lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = paintCopy.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while(width < lineWidth + ellipseWidth) {
                        lineWidth = paintCopy.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if(mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paint = new TextPaint(originalPaint);
        // Update the text paint object
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

อันนี้ใช้งานได้! ยังแก้ปัญหาบางอย่างจากรหัสเดิม
Nick Jian

13

ในการประชุม google IO ในปี 2560 google ได้เปิดตัวคุณสมบัติ autoSize ของ TextView

https://youtu.be/fjUdJ2aVqE4

<android.support.v7.widget.AppCompatTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/my_text"
        app:autoSizeTextType="uniform"
        app:autoSizeMaxTextSize="10sp"
        app:autoSizeMinTextSize="6sp"
        app:autoSizeStepGranularity="1sp"/>

android.support.v7.widget.AppCompatTextView เป็นสิ่งสำคัญ มันไม่ทำงานเหมือนแอพ: แอตทริบิวต์ autoSizeTextType ใน TextView มาตรฐาน ขอแสดงความยินดี ...
Sinan Ergin

ทำงานได้สำหรับฉัน! ต้องเพิ่มแอพ: autoSizeTextType = "uniform" line
avisper

11

เนื่องจากฉันได้ค้นหาสิ่งนี้ตลอดไปและฉันพบวิธีแก้ปัญหาเมื่อไม่นานมานี้ซึ่งหายไปที่นี่ฉันจะเขียนที่นี่เพื่อใช้อ้างอิงในอนาคต

หมายเหตุ: รหัสนี้ถ่ายโดยตรงจาก Google Android Lollipop dialer ในขณะที่กลับมาฉันจำไม่ได้ว่าหากมีการเปลี่ยนแปลงในเวลานั้น นอกจากนี้ฉันไม่ทราบว่าใบอนุญาตนี้คืออะไรภายใต้ แต่ฉันมีเหตุผลที่จะคิดว่ามันคือApache 2.0แต่ฉันมีเหตุผลที่จะคิดว่ามันเป็น

คลาสResizeTextViewจริงView

public class ResizeTextView extends TextView {

private final int mOriginalTextSize;
private final int mMinTextSize;
private final static int sMinSize = 20;
public ResizeTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    mOriginalTextSize = (int) getTextSize();
    mMinTextSize = (int) sMinSize;
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
    super.onTextChanged(text, start, lengthBefore, lengthAfter);
    ViewUtil.resizeText(this, mOriginalTextSize, mMinTextSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    ViewUtil.resizeText(this, mOriginalTextSize, mMinTextSize);
}

นี้ ResizeTextViewชั้นสามารถขยาย TextView และลูก ๆ ทั้งหมดของมันในขณะที่ฉัน undestand ดังนั้น EditText เช่นกัน

ชั้นเรียนViewUtilด้วยวิธีการresizeText(...)

/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import android.graphics.Paint;
import android.util.TypedValue;
import android.widget.TextView;

public class ViewUtil {

    private ViewUtil() {}

    public static void resizeText(TextView textView, int originalTextSize, int minTextSize) {
        final Paint paint = textView.getPaint();
        final int width = textView.getWidth();
        if (width == 0) return;
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalTextSize);
        float ratio = width / paint.measureText(textView.getText().toString());
        if (ratio <= 1.0f) {
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                    Math.max(minTextSize, originalTextSize * ratio));
        }
    }
}

คุณควรตั้งค่ามุมมองของคุณเป็น

<yourpackage.yourapp.ResizeTextView
            android:layout_width="match_parent"
            android:layout_height="64dp"
            android:gravity="center"
            android:maxLines="1"/>

หวังว่ามันจะช่วย!


รับความกว้างอยู่เสมอ 0
ดร. aNdRO

มันไม่ได้มีไว้เพื่อให้คุณมีความกว้างที่ถูกต้องเพราะมันควรจะปรับขนาดตัวเองโดยอัตโนมัติ คุณยังคงต้องเรียกใช้ ViewTreeObserver หากคุณต้องการรับความกว้างจริงของมุมมอง
Matteo

1
โซลูชันของคุณไม่ได้ปรับขนาดอย่างเหมาะสม มีพื้นที่สีขาวมากเกินไปใน TextView
CoolMind

8

ฉันหวังว่านี่จะช่วยคุณได้

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

/* Based on 
 * from http://stackoverflow.com/questions/2617266/how-to-adjust-text-font-size-to-fit-textview
 */
public class FontFitTextView extends TextView {

private static float MAX_TEXT_SIZE = 20;

public FontFitTextView(Context context) {
    this(context, null);
}

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

    float size = this.getTextSize();
    if (size > MAX_TEXT_SIZE)
        setTextSize(MAX_TEXT_SIZE);
}

private void refitText(String text, int textWidth) {
    if (textWidth > 0) {
        float availableWidth = textWidth - this.getPaddingLeft()
                - this.getPaddingRight();

        TextPaint tp = getPaint();
        Rect rect = new Rect();
        tp.getTextBounds(text, 0, text.length(), rect);
        float size = rect.width();

        if (size > availableWidth)
            setTextScaleX(availableWidth / size);
    }
}

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

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

หมายเหตุ: ฉันใช้ MAX_TEXT_SIZE ในกรณีที่ขนาดข้อความใหญ่กว่า 20 เพราะฉันไม่ต้องการอนุญาตให้ใช้ฟอนต์ขนาดใหญ่กับมุมมองของฉันหากนี่ไม่ใช่กรณีของคุณคุณสามารถลบออกได้ง่ายๆ


2
อืม - การใช้ "setTextScaleX" ดูเหมือนว่าจะทำให้ข้อความได้รับการบีบอัดในแนวนอนแทนที่จะปรับขนาดข้อความเป็นรูปแบบที่เล็กลง (แต่อ่านได้) นอกจากนี้ดูเหมือนว่าการห่อข้อความไม่เป็นมิตร
นาธานรูปที่

การใช้ผลข้างเคียงที่น่าสนใจของ setTextScaleX ฉันสงสัยว่ามันแตกต่างจากการเปลี่ยนขนาดตัวอักษรหรือไม่ อาจเป็นไปได้ว่ารหัสนั้นเหมือนกัน แต่เนื่องจากเป็นแบบดั้งเดิมฉันจึงไม่เห็นแหล่งที่มา
Scott Biggs

และความคิดเห็นอื่นทำไมต้องทดสอบscale > availableWidth? สิ่งนี้จะทำให้การปรับขนาดเฉพาะในกรณีที่ขนาดที่มีอยู่มีขนาดเล็กกว่าเดิม; การยืด (หรือทำให้ข้อความใหญ่ขึ้น) จะไม่เกิดขึ้น
Scott Biggs

8

นี่เป็นวิธีง่ายๆที่ใช้ TextView ด้วย TextChangedListened เพิ่มเข้าไป:

expressionView = (TextView) findViewById(R.id.expressionView);
expressionView.addTextChangedListener(textAutoResizeWatcher(expressionView, 25, 55));

private TextWatcher textAutoResizeWatcher(final TextView view, final int MIN_SP, final int MAX_SP) {
    return new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

        @Override
        public void afterTextChanged(Editable editable) {

            final int widthLimitPixels = view.getWidth() - view.getPaddingRight() - view.getPaddingLeft();
            Paint paint = new Paint();
            float fontSizeSP = pixelsToSp(view.getTextSize());
            paint.setTextSize(spToPixels(fontSizeSP));

            String viewText = view.getText().toString();

            float widthPixels = paint.measureText(viewText);

            // Increase font size if necessary.
            if (widthPixels < widthLimitPixels){
                while (widthPixels < widthLimitPixels && fontSizeSP <= MAX_SP){
                    ++fontSizeSP;
                    paint.setTextSize(spToPixels(fontSizeSP));
                    widthPixels = paint.measureText(viewText);
                }
                --fontSizeSP;
            }
            // Decrease font size if necessary.
            else {
                while (widthPixels > widthLimitPixels || fontSizeSP > MAX_SP) {
                    if (fontSizeSP < MIN_SP) {
                        fontSizeSP = MIN_SP;
                        break;
                    }
                    --fontSizeSP;
                    paint.setTextSize(spToPixels(fontSizeSP));
                    widthPixels = paint.measureText(viewText);
                }
            }

            view.setTextSize(fontSizeSP);
        }
    };
}

private float pixelsToSp(float px) {
    float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
    return px/scaledDensity;
}

private float spToPixels(float sp) {
    float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
    return sp * scaledDensity;
}

วิธีนี้จะเพิ่มหรือลดขนาดตัวอักษรตามที่จำเป็นเพื่อให้พอดีกับข้อความโดยคำนึงถึงขอบเขตที่ได้รับ MIN_SP และ MAX_SP เป็นพารามิเตอร์


สำหรับฉันนี่คือคำตอบที่ดีที่สุดและดี ฉันใช้ฟอนต์ที่กำหนดเองดังนั้นฉันต้องเพิ่ม paint.setTypeface (typeFace); ทำงานเหมือน magic..thanks
Abhijit

7

ฉันเขียนโพสต์บล็อกเกี่ยวกับเรื่องนี้

ฉันสร้างส่วนประกอบที่เรียกว่าResizableButtonอิงตามโพสต์บล็อกของ Kirill Grouchnikov เกี่ยวกับส่วนประกอบที่กำหนดเองที่ใช้ในแอพ Android Market ตัวใหม่ ฉันวางรหัสที่ src ที่นี่

ในทางตรงกันข้ามmosabuaอ่านโพสต์ของฉันและบอกฉันว่าเขากำลังจะเปิดแหล่งที่มาการดำเนินงานของเขาซึ่งเร็วกว่าของฉัน ฉันหวังว่าเขาจะปล่อยมันเร็วพอ :)


ใช้งานได้กับข้อความบรรทัดเดียว แต่ไม่สามารถใช้สำหรับตัดข้อความ ถ้าฉันไม่ได้รับอะไร measure () ดูเหมือนว่าจะวัดความกว้างของข้อสมมติว่าข้อความทั้งหมดจะอยู่ในบรรทัดเดียวกัน
นาธานรูปที่

ฉันสังเกตเห็นว่าการใช้งานของคุณไม่ได้อยู่ตรงกลางข้อความโดยค่าเริ่มต้น มันลอยไปทางซ้าย คิด?
Donn Felker

สังเกตว่าฉันมีปัญหาเดียวกันกับข้อความที่ไม่ได้อยู่กึ่งกลางฉันเพิ่มบรรทัด android: singleLine = "true" และอยู่กึ่งกลางหลังจากนั้น
Clayton33

คุณสามารถโพสต์รหัสชิ้นนี้บน GitHub มันค่อนข้างมีประโยชน์ ฉันเกลียดระบบการจัดวางของ Android ที่ไม่ให้คุณปรับขนาดฟอนต์ในองค์ประกอบโดยอัตโนมัติ
Lachezar

5

การใช้งานของฉันมีความซับซ้อนมากขึ้นเล็กน้อย แต่มาพร้อมกับสารพัดต่อไปนี้:

  • คำนึงถึงความกว้างและความสูงที่มีอยู่ในบัญชี
  • ทำงานร่วมกับป้ายกำกับบรรทัดเดียวและหลายบรรทัด
  • ใช้จุดไข่ปลาในกรณีที่ขนาดตัวอักษรขั้นต่ำถูกตี
  • เนื่องจากการแสดงข้อความภายในมีการเปลี่ยนแปลงให้จดจำข้อความที่ตั้งไว้เดิมในตัวแปรแยกต่างหาก
  • ตรวจสอบให้แน่ใจว่าผืนผ้าใบมีขนาดใหญ่เสมอเท่าที่จำเป็นในขณะที่ใช้ความสูงที่มีอยู่ทั้งหมดของพาเรนต์
/**
 * Text view that auto adjusts text size to fit within the view. If the text
 * size equals the minimum text size and still does not fit, append with an
 * ellipsis.
 * 
 * Based on the original work from Chase Colburn
 * &lt;http://stackoverflow.com/a/5535672/305532>
 *
 * @author Thomas Keller &lt;me@thomaskeller.biz>
 */
public class AutoResizeTextView extends TextView {

    // in dip
    private static final int MIN_TEXT_SIZE = 20;

    private static final boolean SHRINK_TEXT_SIZE = true;

    private static final char ELLIPSIS = '\u2026';

    private static final float LINE_SPACING_MULTIPLIER_MULTILINE = 0.8f;

    private static final float LINE_SPACING_MULTIPLIER_SINGLELINE = 1f;

    private static final float LINE_SPACING_EXTRA = 0.0f;

    private CharSequence mOriginalText;

    // temporary upper bounds on the starting text size
    private float mMaxTextSize;

    // lower bounds for text size
    private float mMinTextSize;

    // determines whether we're currently in the process of measuring ourselves,
    // so we do not enter onMeasure recursively
    private boolean mInMeasure = false;

    // if the text size should be shrinked or if the text size should be kept
    // constant and only characters should be removed to hit the boundaries
    private boolean mShrinkTextSize;

    public AutoResizeTextView(Context context) {
        this(context, null);
        init(context, null);
    }

    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        init(context, attrs);
    }

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

    private void init(Context context, AttributeSet attrs) {
        // the current text size is used as maximum text size we can apply to
        // our widget
        mMaxTextSize = getTextSize();
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoResizeTextView);
            mMinTextSize = a.getFloat(R.styleable.AutoResizeTextView_minFontSize, MIN_TEXT_SIZE);
            mShrinkTextSize = a.getBoolean(R.styleable.AutoResizeTextView_shrinkTextSize, SHRINK_TEXT_SIZE);
            a.recycle();
        }
    }

    @Override
    public void setTextSize(float size) {
        mMaxTextSize = size;
        super.setTextSize(size);
    }

    /**
     * Returns the original, unmodified text of this widget
     * 
     * @return
     */
    public CharSequence getOriginalText() {
        // text has not been resized yet
        if (mOriginalText == null) {
            return getText();
        }
        return mOriginalText;
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        if (!mInMeasure) {
            mOriginalText = text.toString();
        }
        super.setText(text, type);
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mInMeasure = true;
        try {
            int availableWidth = MeasureSpec.getSize(widthMeasureSpec) - getCompoundPaddingLeft()
                    - getCompoundPaddingRight();
            int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - getCompoundPaddingTop()
                    - getCompoundPaddingBottom();

            // Do not resize if the view does not have dimensions or there is no
            // text
            if (mOriginalText == null || mOriginalText.length() == 0 || availableWidth <= 0) {
                return;
            }

            TextPaint textPaint = getPaint();

            // start with the recorded max text size
            float targetTextSize = mMaxTextSize;
            String originalText = mOriginalText.toString();
            String finalText = originalText;

            Rect textSize = getTextSize(originalText, textPaint, targetTextSize);
            boolean textExceedsBounds = textSize.height() > availableHeight || textSize.width() > availableWidth;
            if (mShrinkTextSize && textExceedsBounds) {
                // check whether all lines can be rendered in the available
                // width / height without violating the bounds of the parent and
                // without using a text size that is smaller than the minimum
                // text size
                float heightMultiplier = availableHeight / (float) textSize.height();
                float widthMultiplier = availableWidth / (float) textSize.width();
                float multiplier = Math.min(heightMultiplier, widthMultiplier);
                targetTextSize = Math.max(targetTextSize * multiplier, mMinTextSize);

                // measure again
                textSize = getTextSize(finalText, textPaint, targetTextSize);
            }

            // we cannot shrink the height further when we hit the available
            // height, but we can shrink the width by applying an ellipsis on
            // each line
            if (textSize.width() > availableWidth) {
                StringBuilder modifiedText = new StringBuilder();
                String lines[] = originalText.split(System.getProperty("line.separator"));
                for (int i = 0; i < lines.length; i++) {
                    modifiedText.append(resizeLine(textPaint, lines[i], availableWidth));
                    // add the separator back to all but the last processed line
                    if (i != lines.length - 1) {
                        modifiedText.append(System.getProperty("line.separator"));
                    }
                }
                finalText = modifiedText.toString();

                // measure again
                textSize = getTextSize(finalText, textPaint, targetTextSize);
            }

            textPaint.setTextSize(targetTextSize);
            boolean isMultiline = finalText.indexOf('\n') > -1;
            // do not include extra font padding (for accents, ...) for
            // multiline texts, this will prevent proper placement with
            // Gravity.CENTER_VERTICAL
            if (isMultiline) {
                setLineSpacing(LINE_SPACING_EXTRA, LINE_SPACING_MULTIPLIER_MULTILINE);
                setIncludeFontPadding(false);
            } else {
                setLineSpacing(LINE_SPACING_EXTRA, LINE_SPACING_MULTIPLIER_SINGLELINE);
                setIncludeFontPadding(true);
            }

            // according to
            // <http://code.google.com/p/android/issues/detail?id=22493>
            // we have to add a unicode character to trigger the text centering
            // in ICS. this particular character is known as "zero-width" and
            // does no harm.
            setText(finalText + "\u200B");

            int measuredWidth = textSize.width() + getCompoundPaddingLeft() + getCompoundPaddingRight();
            int measuredHeight = textSize.height() + getCompoundPaddingTop() + getCompoundPaddingBottom();

            // expand the view to the parent's height in case it is smaller or
            // to the minimum height that has been set
            // FIXME: honor the vertical measure mode (EXACTLY vs AT_MOST) here
            // somehow
            measuredHeight = Math.max(measuredHeight, MeasureSpec.getSize(heightMeasureSpec));
            setMeasuredDimension(measuredWidth, measuredHeight);
        } finally {
            mInMeasure = false;
        }
    }

    private Rect getTextSize(String text, TextPaint textPaint, float textSize) {
        textPaint.setTextSize(textSize);
        // StaticLayout depends on a given width in which it should lay out the
        // text (and optionally also split into separate lines).
        // Therefor we calculate the current text width manually and start with
        // a fake (read: maxmimum) width for the height calculation.
        // We do _not_ use layout.getLineWidth() here since this returns
        // slightly smaller numbers and therefor would lead to exceeded text box
        // drawing.
        StaticLayout layout = new StaticLayout(text, textPaint, Integer.MAX_VALUE, Alignment.ALIGN_NORMAL, 1f, 0f, true);
        int textWidth = 0;
        String lines[] = text.split(System.getProperty("line.separator"));
        for (int i = 0; i < lines.length; ++i) {
            textWidth = Math.max(textWidth, measureTextWidth(textPaint, lines[i]));
        }
        return new Rect(0, 0, textWidth, layout.getHeight());
    }

    private String resizeLine(TextPaint textPaint, String line, int availableWidth) {
        checkArgument(line != null && line.length() > 0, "expected non-empty string");
        int textWidth = measureTextWidth(textPaint, line);
        int lastDeletePos = -1;
        StringBuilder builder = new StringBuilder(line);
        while (textWidth > availableWidth && builder.length() > 0) {
            lastDeletePos = builder.length() / 2;
            builder.deleteCharAt(builder.length() / 2);
            // don't forget to measure the ellipsis character as well; it
            // doesn't matter where it is located in the line, it just has to be
            // there, since there are no (known) ligatures that use this glyph
            String textToMeasure = builder.toString() + ELLIPSIS;
            textWidth = measureTextWidth(textPaint, textToMeasure);
        }
        if (lastDeletePos > -1) {
            builder.insert(lastDeletePos, ELLIPSIS);
        }
        return builder.toString();
    }

    // there are several methods in Android to determine the text width, namely
    // getBounds() and measureText().
    // The latter works for us the best as it gives us the best / nearest
    // results without that our text canvas needs to wrap its text later on
    // again.
    private int measureTextWidth(TextPaint textPaint, String line) {
        return Math.round(textPaint.measureText(line));
    }
}

[แก้ไขวันที่ 2012-11-21]

  • แก้ไขตำแหน่งของจุดไข่ปลา (ข้อผิดพลาดจากหนึ่ง)
  • การคำนวณขนาดตัวอักษรที่ทำใหม่ ตอนนี้จะวัดข้อความแบบเต็มรวมถึงตัวแบ่งบรรทัดเสมอเพื่อแก้ไขปัญหาเมื่อการเพิ่มความสูงของสองบรรทัดที่วัดเพียงเส้นเดียวนั้นไม่ได้นำไปสู่ผลลัพธ์เช่นเดียวกับการวัดความสูงของข้อความโดยรวม
  • แทนที่จะวนลูปเพื่อหาขนาดตัวอักษรที่เล็กที่สุดเพียงแค่คำนวณหลังการวัดครั้งแรก

1
StyleableTextView คืออะไร
Tushar

คลาสภายในขออภัยฉันจะลบที่นี่
โทมัสเคลเลอร์

2
เต็มไปด้วยข้อผิดพลาดและยังมีการอ้างอิงถึง StylableTextView
LairdPleng

4

ฉันพบว่าสิ่งต่อไปนี้ทำงานได้ดีสำหรับฉัน มันไม่วนซ้ำและมีทั้งความสูงและความกว้าง โปรดทราบว่าเป็นสิ่งสำคัญที่จะต้องระบุหน่วย PX เมื่อเรียกใช้ setTextSize บนมุมมอง

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

นี่คือกิจวัตรที่ฉันใช้ผ่านใน getPaint () จากมุมมอง สตริงอักขระ 10 ตัวที่มีอักขระ 'wide' ใช้เพื่อประเมินความกว้างโดยไม่ขึ้นกับสตริงจริง

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

3

นี่คือการระบุสิ่งที่ฉันได้พบสำหรับทุกคนที่ยังคงค้นหา:

1) ต่อไปนี้เป็นวิธีแก้ปัญหาที่แสดงมุมมองข้อความซ้ำซ้ำจนกว่าจะเหมาะสม นี่หมายถึงการดูข้อความของคุณหดตัวอย่างชัดเจน แต่อย่างน้อยมันก็พอดีเมื่อเสร็จแล้ว รหัสจะต้องมีการปรับแต่งเพื่อนำไปใช้ แต่ส่วนใหญ่จะอยู่ที่นั่น

2) คุณสามารถลองแฮ็คโซลูชันแบบกำหนดเองเช่นนี้หรือคลาสของ dunni ในสิ่งนี้ซึ่งเป็นสิ่งที่ฉันทำโดยใช้ getPaint () measureText (str) เพื่อหาขนาดที่เหมาะสม มันห่อเฉพาะในช่องว่าง ...

3) คุณสามารถค้นหาต่อไปได้ - ฉันได้ลองตัวเลือกอื่นมากกว่าที่ฉันสามารถนับได้ คำแนะนำของ Ted เกี่ยวกับ StaticLayout ไม่ได้จ่ายเงินให้ฉัน แต่อาจมีบางอย่างที่นั่น ฉันพยายามใช้ StaticLayout.getEllipsis (บรรทัด) เพื่อตรวจสอบว่าข้อความกำลังออกจากหน้าจอหรือไม่ ดูของฉัน (ปัจจุบันยกเลิกการตอบ) โพสต์เกี่ยวกับที่นี่


2

ฉันต้องการโซลูชันที่เฉพาะเจาะจง ฉันมีข้อความที่ตัดต่อและข้อความในเค้าโครงของฉัน มุมมองข้อความคือความสูงและความกว้างคงที่ เมื่อผู้ใช้เริ่มพิมพ์ข้อความ edittext ข้อความควรปรากฏในมุมมองข้อความทันที ข้อความในฟิลด์ข้อความควรปรับขนาดให้พอดีกับมุมมองข้อความโดยอัตโนมัติ ดังนั้นฉันจึงปรับปรุงโซลูชันของ Chase เพื่อทำงานให้ฉัน ดังนั้นเมื่อข้อความเปลี่ยนแปลงในมุมมองข้อความการปรับขนาดเริ่มต้น ความแตกต่างระหว่างฉันและ soluton เชส: ปรับขนาดจะทำแม้ว่าผู้ใช้จะลบตัวอักษรบาง ฉันหวังว่ามันจะช่วยให้ใครบางคน

public class TextFitTextView extends TextView {

// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 10;

// Maximum text size for this text view - if it is 0, then the text acts
// like match_parent
public static final float MAX_TEXT_SIZE = 0;

// Our ellipse string
private static final String mEllipsis = "...";

// Text size that is set from code. This acts as a starting point for
// resizing
private float mTextSize;

// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;

// Max bounds for text size
private float mMaxTextSize = MAX_TEXT_SIZE;

// Text view line spacing multiplier
private float mSpacingMult = 1.0f;

// Text view additional line spacing
private float mSpacingAdd = 0.0f;

// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;

// Add ellipsis to text that overflows at the smallest text size
private int heightLimit;
private int widthLimit;

// Default constructor override
public TextFitTextView(Context context) {
    this(context, null);
}

// Default constructor when inflating from XML file
public TextFitTextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

// Default constructor override
public TextFitTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    mTextSize = getTextSize();
}

/**
 * When text changes resize the text size.
 */
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
    // if we are adding new chars to text
    if (before <= after && after != 1) {
        resizeText(true);
        // now we are deleting chars
    } else {
        resizeText(false);
    }
}

/**
 * Override the set text size to update our internal reference values
 */
@Override
public void setTextSize(float size) {
    super.setTextSize(size);
    mTextSize = getTextSize();
}

/**
 * Override the set text size to update our internal reference values
 */
@Override
public void setTextSize(int unit, float size) {
    super.setTextSize(unit, size);
    mTextSize = getTextSize();
}

/**
 * Override the set line spacing to update our internal reference values
 */
@Override
public void setLineSpacing(float add, float mult) {
    super.setLineSpacing(add, mult);
    mSpacingMult = mult;
    mSpacingAdd = add;
}

/**
 * Set the lower text size limit and invalidate the view
 * 
 * @param minTextSize
 */
public void setMinTextSize(float minTextSize) {
    mMinTextSize = minTextSize;
    requestLayout();
    invalidate();
}

/**
 * Return lower text size limit
 * 
 * @return
 */
public float getMinTextSize() {
    return mMinTextSize;
}

/**
 * Set flag to add ellipsis to text that overflows at the smallest text size
 * 
 * @param addEllipsis
 */
public void setAddEllipsis(boolean addEllipsis) {
    mAddEllipsis = addEllipsis;
}

/**
 * Return flag to add ellipsis to text that overflows at the smallest text
 * size
 * 
 * @return
 */
public boolean getAddEllipsis() {
    return mAddEllipsis;
}

/**
 * Get width and height limits
 */
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    if (widthLimit == 0 && heightLimit == 0) {
        widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
        heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
    }
    super.onLayout(changed, left, top, right, bottom);
}

/**
 * Resize the text size with specified width and height
 * 
 * @param width
 * @param height
 */
public void resizeText(boolean increase) {
    CharSequence text = getText();
    // Do not resize if the view does not have dimensions or there is no
    // text
    if (text == null || text.length() == 0 || heightLimit <= 0 || widthLimit <= 0 || mTextSize == 0) {
        return;
    }

    // Get the text view's paint object
    TextPaint textPaint = getPaint();

    // Get the required text height
    int textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);


    // If the text length is increased 
    // Until we either fit within our text view or we had reached our min
    // text size, incrementally try smaller sizes
    if (increase) {
        while (textHeight > heightLimit && mTextSize > mMinTextSize) {
            mTextSize = Math.max(mTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
        }
    } 
//      text length has been decreased
    else {
//          if max test size is set then add it to while condition
        if (mMaxTextSize != 0) {
            while (textHeight < heightLimit && mTextSize <= mMaxTextSize) {
                mTextSize = mTextSize + 2;
                textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
            }
        } else {
            while (textHeight < heightLimit) {
                mTextSize = mTextSize + 2;
                textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
            }
        }
        mTextSize = textHeight > heightLimit ? mTextSize - 2 : mTextSize;
    }

    // If we had reached our minimum text size and still don't fit, append
    // an ellipsis
    if (mAddEllipsis && mTextSize == mMinTextSize && textHeight > heightLimit) {
        // Draw using a static layout
        TextPaint paint = new TextPaint(textPaint);
        StaticLayout layout = new StaticLayout(text, paint, widthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
                mSpacingAdd, false);
        // Check that we have a least one line of rendered text
        if (layout.getLineCount() > 0) {
            // Since the line at the specific vertical position would be cut
            // off,
            // we must trim up to the previous line
            int lastLine = layout.getLineForVertical(heightLimit) - 1;
            // If the text would not even fit on a single line, clear it
            if (lastLine < 0) {
                setText("");
            }
            // Otherwise, trim to the previous line and add an ellipsis
            else {
                int start = layout.getLineStart(lastLine);
                int end = layout.getLineEnd(lastLine);
                float lineWidth = layout.getLineWidth(lastLine);
                float ellipseWidth = paint.measureText(mEllipsis);

                // Trim characters off until we have enough room to draw the
                // ellipsis
                while (widthLimit < lineWidth + ellipseWidth) {
                    lineWidth = paint.measureText(text.subSequence(start, --end + 1).toString());
                }
                setText(text.subSequence(0, end) + mEllipsis);
            }
        }
    }

    // Some devices try to auto adjust line spacing, so force default line
    // spacing
    // and invalidate the layout as a side effect
    setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
    setLineSpacing(mSpacingAdd, mSpacingMult);

}

// Set the text size of the text paint object and use a static layout to
// render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
    // Update the text paint object
    TextPaint paint = new TextPaint(originalPaint);
    paint.setTextSize(textSize);
    // Measure using a static layout
    StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd,
            true);
    return layout.getHeight();
}

}

2

ให้คำตอบที่เขียนไว้บนC #สำหรับผู้ที่รหัสบน Xamarin.Android ทำงานให้ฉันได้ดี

 /**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT YOU WANT TO.
 */


using System;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Util;
using Android.Widget;
using Java.Lang;

namespace App.GuestGuide.Droid.Controls
{
    public class OnTextResizeEventArgs : EventArgs
    {
        public TextView TextView { get; set; }
        public float OldSize { get; set; }
        public float NewSize { get; set; }
    }

    /// <inheritdoc />
    /// <summary>
    /// Text view that auto adjusts text size to fit within the view.
    /// If the text size equals the minimum text size and still does not
    /// fit, append with an ellipsis.
    /// </summary>
    public class AutoResizeTextView : TextView
    {
        /// <summary>
        /// Minimum text size for this text view
        /// </summary>
        public static float MIN_TEXT_SIZE = 10;

        /// <summary>
        /// Our ellipse string
        /// </summary>
        private const string Ellipsis = "...";


        private float _mMaxTextSize;

        private float _mMinTextSize = MIN_TEXT_SIZE;

        /// <summary>
        /// Register subscriber to receive resize notifications
        /// </summary>
        public event EventHandler<OnTextResizeEventArgs> OnTextResize;

        /// <summary>
        /// Flag for text and/or size changes to force a resize
        /// </summary>
        private bool _needsResize;

        /// <summary>
        /// Text size that is set from code. This acts as a starting point for resizing
        /// </summary>
        private float _textSize;

        /// <summary>
        /// Text view line spacing multiplier
        /// </summary>
        private float _spacingMult = 1.0f;

        /// <summary>
        /// Text view additional line spacing
        /// </summary>
        private float _spacingAdd;

        /// <summary>
        /// Add ellipsis to text that overflows at the smallest text size
        /// </summary>
        public bool ShouldAddEllipsis { get; set; }

        /// <inheritdoc />
        /// <summary>
        /// Override the set text size to update our internal reference values
        /// </summary>
        public override float TextSize
        {
            get => base.TextSize;
            set
            {
                base.TextSize = value;
                _textSize = TextSize;
            }
        }

        /// <summary>
        /// Temporary upper bounds on the starting text size
        /// </summary>
        public float MaxTextSize
        {
            get => _mMaxTextSize;
            // Set the upper text size limit and invalidate the view
            set
            {
                _mMaxTextSize = value;
                RequestLayout();
                Invalidate();
            }
        }

        /// <summary>
        /// Lower bounds for text size
        /// </summary>
        public float MinTextSize
        {
            get => _mMinTextSize;
            //Set the lower text size limit and invalidate the view
            set
            {
                _mMinTextSize = value;
                RequestLayout();
                Invalidate();
            }
        }

        public AutoResizeTextView(Context context) : this(context, null)
        {
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs) : this(context, attrs, 0)
        {
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
        {
            _textSize = TextSize;
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
        {
            _textSize = TextSize;
        }

        protected AutoResizeTextView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
            _textSize = TextSize;
        }

        /// <inheritdoc />
        /// <summary>
        /// When text changes, set the force resize flag to true and reset the text size.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="start"></param>
        /// <param name="lengthBefore"></param>
        /// <param name="lengthAfter"></param>
        protected override void OnTextChanged(ICharSequence text, int start, int lengthBefore, int lengthAfter)
        {
            _needsResize = true;
            // Since this view may be reused, it is good to reset the text size
            ResetTextSize();
        }

        /// <inheritdoc />
        /// <summary>
        /// If the text view size changed, set the force resize flag to true
        /// </summary>
        /// <param name="w"></param>
        /// <param name="h"></param>
        /// <param name="oldw"></param>
        /// <param name="oldh"></param>
        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            if (w != oldw || h != oldh)
            {
                _needsResize = true;
            }
        }

        public override void SetTextSize([GeneratedEnum] ComplexUnitType unit, float size)
        {
            base.SetTextSize(unit, size);
            _textSize = TextSize;
        }

        /// <inheritdoc />
        /// <summary>
        /// Override the set line spacing to update our internal reference values
        /// </summary>
        /// <param name="add"></param>
        /// <param name="mult"></param>
        public override void SetLineSpacing(float add, float mult)
        {
            base.SetLineSpacing(add, mult);
            _spacingMult = mult;
            _spacingAdd = add;
        }

        /// <summary>
        /// Reset the text to the original size
        /// </summary>
        public void ResetTextSize()
        {
            if (_textSize > 0)
            {
                base.SetTextSize(ComplexUnitType.Px, _textSize);
                _mMaxTextSize = _textSize;
            }
        }

        /// <inheritdoc />
        /// <summary>
        /// Resize text after measuring
        /// </summary>
        /// <param name="changed"></param>
        /// <param name="left"></param>
        /// <param name="top"></param>
        /// <param name="right"></param>
        /// <param name="bottom"></param>
        protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
        {
            if (changed || _needsResize)
            {
                var widthLimit = (right - left) - CompoundPaddingLeft - CompoundPaddingRight;
                var heightLimit = (bottom - top) - CompoundPaddingBottom - CompoundPaddingTop;
                ResizeText(widthLimit, heightLimit);
            }

            base.OnLayout(changed, left, top, right, bottom);
        }

        /// <summary>
        /// Resize the text size with default width and height
        /// </summary>
        public void ResizeText()
        {
            var heightLimit = Height - PaddingBottom - PaddingTop;
            var widthLimit = Width - PaddingLeft - PaddingRight;
            ResizeText(widthLimit, heightLimit);
        }

        /// <summary>
        /// Resize the text size with specified width and height
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void ResizeText(int width, int height)
        {
            ICharSequence text = null;

            if (!string.IsNullOrEmpty(Text))
            {
                text = new Java.Lang.String(Text);
            }

            // Do not resize if the view does not have dimensions or there is no text
            if (text == null || text.Length() == 0 || height <= 0 || width <= 0 || _textSize == 0)
            {
                return;
            }

            if (TransformationMethod != null)
            {
                text = TransformationMethod.GetTransformationFormatted(text, this);
            }

            // Get the text view's paint object
            var textPaint = Paint;
            // Store the current text size
            var oldTextSize = textPaint.TextSize;
            // If there is a max text size set, use the lesser of that and the default text size
            var targetTextSize = _mMaxTextSize > 0 ? System.Math.Min(_textSize, _mMaxTextSize) : _textSize;

            // Get the required text height
            var textHeight = GetTextHeight(text, textPaint, width, targetTextSize);

            // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
            while (textHeight > height && targetTextSize > _mMinTextSize)
            {
                targetTextSize = System.Math.Max(targetTextSize - 2, _mMinTextSize);
                textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
            }

            // If we had reached our minimum text size and still don't fit, append an ellipsis
            if (ShouldAddEllipsis && targetTextSize == _mMinTextSize && textHeight > height)
            {
                // Draw using a static layout
                // modified: use a copy of TextPaint for measuring
                var paint = new TextPaint(textPaint);
                // Draw using a static layout
                var layout = new StaticLayout(text, paint, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, false);

                // Check that we have a least one line of rendered text
                if (layout.LineCount > 0)
                {
                    // Since the line at the specific vertical position would be cut off,
                    // we must trim up to the previous line
                    var lastLine = layout.GetLineForVertical(height) - 1;
                    // If the text would not even fit on a single line, clear it
                    if (lastLine < 0)
                    {
                        Text = string.Empty;
                    }
                    // Otherwise, trim to the previous line and add an ellipsis
                    else
                    {
                        var start = layout.GetLineStart(lastLine);
                        var end = layout.GetLineEnd(lastLine);
                        var lineWidth = layout.GetLineWidth(lastLine);
                        var ellipseWidth = textPaint.MeasureText(Ellipsis);

                        // Trim characters off until we have enough room to draw the ellipsis
                        while (width < lineWidth + ellipseWidth)
                        {
                            lineWidth = textPaint.MeasureText(text.SubSequence(start, --end + 1));
                        }

                        Text = text.SubSequence(0, end) + Ellipsis;
                    }
                }
            }

            // Some devices try to auto adjust line spacing, so force default line spacing
            // and invalidate the layout as a side effect
            SetTextSize(ComplexUnitType.Px, targetTextSize);
            SetLineSpacing(_spacingAdd, _spacingMult);

            var notifyArgs = new OnTextResizeEventArgs
            {
                TextView = this,
                NewSize = targetTextSize,
                OldSize = oldTextSize
            };

            // Notify the listener if registered
            OnTextResize?.Invoke(this, notifyArgs);

            // Reset force resize flag
            _needsResize = false;
        }

        /// <summary>
        /// Set the text size of the text paint object and use a static layout to render text off screen before measuring
        /// </summary>
        /// <param name="source"></param>
        /// <param name="paint"></param>
        /// <param name="width"></param>
        /// <param name="textSize"></param>
        /// <returns></returns>
        private int GetTextHeight(ICharSequence source, TextPaint paint, int width, float textSize)
        {
            // modified: make a copy of the original TextPaint object for measuring
            // (apparently the object gets modified while measuring, see also the
            // docs for TextView.getPaint() (which states to access it read-only)
            // Update the text paint object
            var paintCopy = new TextPaint(paint)
            {
                TextSize = textSize
            };

            // Measure using a static layout
            var layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, true);

            return layout.Height;
        }
    }
}

ฉันใหม่กับซามารินและไม่สามารถสร้างโครงการใน VS17 ได้ คุณช่วยฉันออกจากที่นี่ได้ไหม Thanx
Shambhu

@Shambhu โปรดตรวจสอบที่นี่youtube.com/watch?v=NGvn-pGZFPA
Oleg Kosuakiv

@OlegKosuakiv คุณมีแอปแนวคิดที่ใช้สิ่งนี้หรือไม่? อยากรู้อยากเห็นลอง!
envyM6

@ envyM6, yo หมายถึง autoisizing textview หรือ Xamarin Android หรือไม่
Oleg Kosuakiv

ใช่ครับนั่นคือ Xamarin Android แน่นอน
envyM6

1

คุณสามารถใช้android.text.StaticLayoutคลาสนี้ได้ นั่นคือสิ่งที่TextViewใช้ภายใน


4
คุณสามารถทำอย่างละเอียด? ฉันเห็นเอกสาร dev เกี่ยวกับ StaticLayout มันมีวิธีการที่น่าสนใจ แต่ฉันไม่แน่ใจว่าจะเห็นได้อย่างไรว่าจะใช้สิ่งนี้เพื่อสร้างสิ่งที่ฉันขอได้อย่างไร
นาธานรูปที่

1

ฉันเพิ่งสร้างวิธีการต่อไปนี้ (ตามแนวคิดของ Chase) ซึ่งอาจช่วยคุณได้หากคุณต้องการวาดข้อความลงในภาพวาดใด ๆ

private static void drawText(Canvas canvas, int xStart, int yStart,
        int xWidth, int yHeigth, String textToDisplay,
        TextPaint paintToUse, float startTextSizeInPixels,
        float stepSizeForTextSizeSteps) {

    // Text view line spacing multiplier
    float mSpacingMult = 1.0f;
    // Text view additional line spacing
    float mSpacingAdd = 0.0f;
    StaticLayout l = null;
    do {
        paintToUse.setTextSize(startTextSizeInPixels);
        startTextSizeInPixels -= stepSizeForTextSizeSteps;
        l = new StaticLayout(textToDisplay, paintToUse, xWidth,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
    } while (l.getHeight() > yHeigth);

    int textCenterX = xStart + (xWidth / 2);
    int textCenterY = (yHeigth - l.getHeight()) / 2;

    canvas.save();
    canvas.translate(textCenterX, textCenterY);
    l.draw(canvas);
    canvas.restore();
}

สามารถใช้เช่นในวิธี onDraw () ของมุมมองที่กำหนดเองใด ๆ


การสร้างวัตถุในฟังก์ชั่นการวาดภาพเป็นวิธีปฏิบัติที่ไม่ถูกต้อง
Ofek Ron

1

นี่เป็นอีกทางออกหนึ่งสำหรับการเตะ มันอาจจะไม่ได้มีประสิทธิภาพมากนัก แต่มันรับมือกับทั้งความสูงและความกว้างของข้อความและกับข้อความที่ถูกทำเครื่องหมาย

@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
{
    if ((MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)
            && (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED)) {

        final float desiredWidth = MeasureSpec.getSize(widthMeasureSpec);
        final float desiredHeight = MeasureSpec.getSize(heightMeasureSpec);

        float textSize = getTextSize();
        float lastScale = Float.NEGATIVE_INFINITY;
        while (textSize > MINIMUM_AUTO_TEXT_SIZE_PX) {
            // Measure how big the textview would like to be with the current text size.
            super.onMeasure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

            // Calculate how much we'd need to scale it to fit the desired size, and
            // apply that scaling to the text size as an estimate of what we need.
            final float widthScale = desiredWidth / getMeasuredWidth();
            final float heightScale = desiredHeight / getMeasuredHeight();
            final float scale = Math.min(widthScale, heightScale);

            // If we don't need to shrink the text, or we don't seem to be converging, we're done.
            if ((scale >= 1f) || (scale <= lastScale)) {
                break;
            }

            // Shrink the text size and keep trying.
            textSize = Math.max((float) Math.floor(scale * textSize), MINIMUM_AUTO_TEXT_SIZE_PX);
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            lastScale = scale;
        }
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

ฉันได้ลองคำตอบอีก 6 คำ แต่สิ่งนี้ทำให้ฉันได้ผลลัพธ์ที่ดีที่สุด ยังไม่สมบูรณ์เพราะขนาดตัวอักษรเล็กกว่าที่ควรจะเป็นในบางกรณี แต่ดีกว่าคำตอบอื่น ๆ
ThomasW

1

ฉันรวมคำแนะนำข้างต้นบางข้อเพื่อทำให้ข้อเสนอที่ปรับขนาดขึ้นและลงด้วยวิธีการแบ่งครึ่ง นอกจากนี้ยังปรับขนาดภายในความกว้าง

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT YOU WANT TO.
 */

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

/**
 * Text view that auto adjusts text size to fit within the view. If the text
 * size equals the minimum text size and still does not fit, append with an
 * ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 10;

    // Minimum text size for this text view
    public static final float MAX_TEXT_SIZE = 128;

    private static final int BISECTION_LOOP_WATCH_DOG = 30;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for
    // resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = MAX_TEXT_SIZE;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text
     * size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * 
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * 
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * 
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * 
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * 
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * 
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text
     * size
     * 
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if (mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            // mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        if (changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft()
                    - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom()
                    - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }

    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {

        // Height and width with a padding as a percentage of height
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * 
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();

        // Do not resize if the view does not have dimensions or there is no
        // text
        if (text == null || text.length() == 0 || height <= 0 || width <= 0
                || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();

        // Bisection method: fast & precise
        float lower = mMinTextSize;
        float upper = mMaxTextSize;
        int loop_counter = 1;
        float targetTextSize = (lower + upper) / 2;
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        int textWidth = getTextWidth(text, textPaint, width, targetTextSize);

        while (loop_counter < BISECTION_LOOP_WATCH_DOG && upper - lower > 1) {
            targetTextSize = (lower + upper) / 2;
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            textWidth = getTextWidth(text, textPaint, width, targetTextSize);
            if (textHeight > (height) || textWidth > (width))
                upper = targetTextSize;
            else
                lower = targetTextSize;
            loop_counter++;
        }

        targetTextSize = lower;
        textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // If we had reached our minimum text size and still don't fit, append
        // an ellipsis
        if (mAddEllipsis && targetTextSize == mMinTextSize
                && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paintCopy = new TextPaint(textPaint);
            paintCopy.setTextSize(targetTextSize);
            StaticLayout layout = new StaticLayout(text, paintCopy, width,
                    Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if (layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut
                // off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if (lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = paintCopy.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the
                    // ellipsis
                    while (width < lineWidth + ellipseWidth) {
                        lineWidth = paintCopy.measureText(text.subSequence(
                                start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line
        // spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if (mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint,
            int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paint = new TextPaint(originalPaint);
        // Update the text paint object
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextWidth(CharSequence source, TextPaint originalPaint,
            int width, float textSize) {
        // Update the text paint object
        TextPaint paint = new TextPaint(originalPaint);
        // Draw using a static layout
        paint.setTextSize(textSize);

        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);

        return (int) layout.getLineWidth(0);
    }
}

1

ฉันใช้รหัสจากการไล่ล่าและ M-WaJeEh และฉันพบว่ามีข้อดีและข้อเสียที่นี่

จากการไล่ล่า

ความได้เปรียบ:

  • มันเหมาะสำหรับ TextView 1 บรรทัด

ข้อด้อย:

  • หากมีมากกว่า 1 บรรทัดที่มีแบบอักษรที่กำหนดเองข้อความบางส่วนจะหายไป

  • หากเปิดใช้งานวงรีมันจะไม่เตรียมพื้นที่สำหรับวงรี

  • หากเป็นแบบอักษรที่กำหนดเอง (แบบอักษร) แสดงว่าไม่รองรับ

จาก M-WaJeEh

ความได้เปรียบ:

  • มันสมบูรณ์แบบสำหรับหลายบรรทัด

ข้อด้อย:

  • หากตั้งความสูงเป็นเนื้อหาสรุปโค้ดนี้จะเริ่มจากขนาดต่ำสุดและจะลดลงให้เล็กที่สุดเท่าที่จะทำได้ไม่ใช่จาก setSize และลดด้วยความกว้างที่ จำกัด

  • หากเป็นแบบอักษรที่กำหนดเอง (แบบอักษร) แสดงว่าไม่รองรับ


1
เกิดปัญหาgetTextHeight()เมื่อทำการตั้งค่าข้อความหรือขนาดตัวอักษร Android 4.0.4 อีมูเลเตอร์ java.lang.IllegalArgumentException: Layout: -40 < 0 at android.text.Layout.<init>(Layout.java:140) at android.text.StaticLayout.<init>(StaticLayout.java:104) at android.text.StaticLayout.<init>(StaticLayout.java:90) at android.text.StaticLayout.<init>(StaticLayout.java:68) at android.text.StaticLayout.<init>(StaticLayout.java:48)
TalkLittle

1

วิธีการของฉันคือ:

public void changeTextSize(int initialSize, TextView tv) {

    DisplayMetrics displayMetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    double width = displayMetrics.widthPixels / displayMetrics.xdpi;
    double height = displayMetrics.heightPixels / displayMetrics.ydpi;

    Log.i("LOG", "The width of the tested emulator is: " + width);
    Log.i("LOG", "The height of the tested emulator is: " + height);

    double scale = Math.min(width / 2.25, height / 4.0); //See the logcat >>> width = 2.25 and heigt = 4.0
    tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, (int) (initialSize * scale));

}

ตัวอย่างเช่น:

changeTextSize(16, findViewById(R.id.myTextView));
changeTextSize(12, findViewById(R.id.myEditText));

0

โซลูชั่นนี้ใช้ได้กับเรา:

public class CustomFontButtonTextFit extends CustomFontButton
{
    private final float DECREMENT_FACTOR = .1f;

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

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

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

    private synchronized void refitText(String text, int textWidth) {
        if (textWidth > 0) 
        {
            float availableWidth = textWidth - this.getPaddingLeft()
                    - this.getPaddingRight();

            TextPaint tp = getPaint();
            Rect rect = new Rect();
            tp.getTextBounds(text, 0, text.length(), rect);
            float size = rect.width();

            while(size > availableWidth)
            {
                setTextSize( getTextSize() - DECREMENT_FACTOR );
                tp = getPaint();

                tp.getTextBounds(text, 0, text.length(), rect);
                size = rect.width();
            }
        }
    }

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

        if(parentWidth < getSuggestedMinimumWidth())
            parentWidth = getSuggestedMinimumWidth();

        if(parentHeight < getSuggestedMinimumHeight())
            parentHeight = getSuggestedMinimumHeight();

        this.setMeasuredDimension(parentWidth, parentHeight);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) 
    {
        super.onTextChanged(text, start, before, after);

        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);

        if (w != oldw) 
            refitText(this.getText().toString(), w);
    }
}

2
CustomFontButton คืออะไรที่ขยายมาจากนี้
สลับซับซ้อน

ถ้าฉันขยายCustomFontButtonTextFitจากButtonชั้นเรียนแล้ว UI จะไม่แสดง ฉันจะบอกว่าคลาสนี้เสีย ...
คนอยู่ที่ไหนสักแห่ง

0

ขอบคุณ Chase และ onoelle สำหรับโปรแกรมเมอร์ผู้ขี้เกียจขอให้ฉันโพสต์โค้ดการผสานที่ยอดเยี่ยมของพวกเขาในเวอร์ชันการทำงานซึ่งดัดแปลงโดยใช้ปุ่มเพียงปุ่มเดียวแทนที่จะเป็น TextView

ทดแทนปุ่มทั้งหมดของคุณ (ไม่ใช่ ImageButtons) ด้วย AutoResizeTextButtons และแก้ไขปัญหาการเบื่อเช่นเดียวกันสำหรับพวกเขาเช่นกัน

นี่คือรหัส ฉันเพิ่งลบการนำเข้า

/**
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT THE FUCK YOU WANT TO.
 *  made better by onoelle
 *  adapted for button by beppi
 */

/**
 * Text Button that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextButton extends Button {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 20;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(Button textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = 0;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextButton(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if(mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if(changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }


    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();
        // If there is a max text size set, use the lesser of that and the default text size
        float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;

        // Get the required text height
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
        while(textHeight > height && targetTextSize > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        }

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paint = new TextPaint(textPaint);
            StaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if(layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if(lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = textPaint.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while(width < lineWidth + ellipseWidth) {
                        lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
//      textPaint.setTextSize(targetTextSize);
     // modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if(mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
          // modified: make a copy of the original TextPaint object for measuring
          // (apparently the object gets modified while measuring, see also the
          // docs for TextView.getPaint() (which states to access it read-only)
        // Update the text paint object
          TextPaint paint = new TextPaint(originalPaint);
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

การใช้งาน:

วาง AutoResizeTextButton ไว้ใน xml แทนปุ่มปกติโดยไม่ต้องเปลี่ยนอะไรอื่น ข้างใน onCreate () ใส่ (ตัวอย่าง):

    myButton = (AutoResizeTextButton)getView().findViewById(id.myButton);
    myButton.setMinTextSize(8f);
    myButton.resizeText();

พื้นหลังของปุ่มไม่สามารถมองเห็นได้ คุณต้องรวมสิ่งต่อไปนี้ในมุมมองปุ่มของคุณ xml: style = "? android: attr / buttonBarButtonStyle"
Kristy Welsh

0

นี่คือวิธีที่ฉันใช้ มันง่ายมาก มันใช้การประมาณแบบต่อเนื่องเป็นศูนย์ในแบบอักษรและโดยทั่วไปสามารถหาได้ในการทำซ้ำน้อยกว่า 10 เพียงแค่แทนที่ "activityWidth" ด้วยความกว้างของมุมมองที่คุณใช้เพื่อแสดงข้อความในตัวอย่างของฉันมันถูกตั้งเป็นฟิลด์ส่วนตัวให้เป็นความกว้างของหน้าจอ แบบอักษรเริ่มต้นที่ 198 ถูกตั้งค่าเฉพาะในกรณีที่วิธีการสร้างข้อยกเว้น (ซึ่งไม่ควรเกิดขึ้นจริง):

  private float GetFontSizeForScreenWidth(String text)
  {
    float fontsize = 198;

    try
    {
      Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
      paint.setColor(Color.RED);
      Typeface typeface = Typeface.create("Helvetica", Typeface.BOLD);
      paint.setTypeface(typeface);
      paint.setTextAlign(Align.CENTER);

      int lowVal = 0;
      int highVal = 2000;
      int currentVal = highVal;

      /*
       * Successively approximate the screen size until it is 
       * within 2 pixels of the maximum screen width. Generally
       * this will get you to the closest font size within about 10
       * iterations.
       */

      do
      {
        paint.setTextSize(currentVal);
        float textWidth = paint.measureText(text);

        float diff = activityWidth - textWidth;

        if ((diff >= 0) && (diff <= 2))
        {
          fontsize = paint.getTextSize();
          return fontsize;
        }

        if (textWidth > activityWidth)
          highVal = currentVal;
        else if (textWidth < activityWidth)
          lowVal = currentVal;
        else
        {
          fontsize = paint.getTextSize();
          return fontsize;
        }

        currentVal = (highVal - lowVal) / 2 + lowVal;

      } while (true);      
    }
    catch (Exception ex)
    {
      return fontsize;
    }
  }

0

ขยาย TextView และแทนที่ onDraw ด้วยรหัสด้านล่าง มันจะรักษาอัตราส่วนของข้อความ แต่ปรับขนาดเพื่อเติมช่องว่าง คุณสามารถปรับเปลี่ยนรหัสเพื่อยืดหากจำเป็น

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

ในกรณีที่ไม่rectมาจากไหน?
ThomasW

คุณควรประกาศและสร้างมันนอกของ onDraw ตามที่คุณควรลดจำนวนของวัตถุที่สร้างขึ้นภายใน onDraw จากนั้นค่าจะถูกตั้งค่าโดย getTextBounds
Greg Bacchus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.