ปรับ TextView อัตโนมัติสำหรับ Android


231

พื้นหลัง

หลายครั้งที่เราต้องปรับแบบอักษรของ TextView ให้พอดีกับขอบเขตที่กำหนดให้โดยอัตโนมัติ

ปัญหา

น่าเศร้าที่แม้ว่าจะมีหลายกระทู้และโพสต์ (และแก้ปัญหา) พูดคุยเกี่ยวกับปัญหานี้ (ตัวอย่างเช่นที่นี่ , ที่นี่และที่นี่ ) ไม่มีของพวกเขาจริงทำงานได้ดี

นั่นเป็นเหตุผลที่ฉันตัดสินใจทดสอบแต่ละคนจนกว่าฉันจะพบข้อตกลงที่แท้จริง

ฉันคิดว่าข้อกำหนดจาก textView เช่นนั้นควรเป็น:

  1. ควรอนุญาตให้ใช้แบบอักษรแบบอักษรลักษณะและชุดอักขระใด ๆ

  2. ควรจัดการทั้งความกว้างและความสูง

  3. ไม่มีการตัดยกเว้นว่าข้อความไม่สามารถบรรจุได้เนื่องจากข้อ จำกัด เราได้มอบให้ (ตัวอย่าง: ข้อความยาวเกินไปมีขนาดเล็กเกินไป) อย่างไรก็ตามเราสามารถขอแถบเลื่อนแนวนอน / แนวตั้งได้หากต้องการเพียงสำหรับกรณีเหล่านั้น

  4. ควรอนุญาตให้มีหลายสายหรือสายเดียว ในกรณีที่มีหลายสายอนุญาตให้มีบรรทัดสูงสุด & นาที

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

  6. ในกรณีที่มีหลายบรรทัดควรอนุญาตให้ปรับขนาดหรือใช้เส้นมากขึ้นและ / หรืออนุญาตให้เลือกเส้นตัวเองโดยใช้อักขระ "\ n"

สิ่งที่ฉันได้ลอง

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

ฉันได้ทำโครงการตัวอย่างที่ช่วยให้ฉันเห็นด้วยตาว่า TextView แบบอัตโนมัติถูกต้องหรือไม่

ปัจจุบันโครงการตัวอย่างของฉันสุ่มเฉพาะข้อความ (ตัวอักษรภาษาอังกฤษบวกตัวเลข) และขนาดของ textView และปล่อยให้มันอยู่ในบรรทัดเดียว แต่แม้มันจะไม่ได้ผลกับตัวอย่างใด ๆ ที่ฉันได้ลอง

นี่คือรหัส (มีให้ที่นี่ด้วย ):

ไฟล์ res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" tools:context=".MainActivity">
  <Button android:id="@+id/button1" android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true" android:text="Button" />
  <FrameLayout android:layout_width="match_parent"
    android:layout_height="wrap_content" android:layout_above="@+id/button1"
    android:layout_alignParentLeft="true" android:background="#ffff0000"
    android:layout_alignParentRight="true" android:id="@+id/container"
    android:layout_alignParentTop="true" />

</RelativeLayout>

ไฟล์ src/.../MainActivity.java

public class MainActivity extends Activity
  {
  private final Random        _random            =new Random();
  private static final String ALLOWED_CHARACTERS ="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";

  @Override
  protected void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ViewGroup container=(ViewGroup)findViewById(R.id.container);
    findViewById(R.id.button1).setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(final View v)
          {
          container.removeAllViews();
          final int maxWidth=container.getWidth();
          final int maxHeight=container.getHeight();
          final FontFitTextView fontFitTextView=new FontFitTextView(MainActivity.this);
          final int width=_random.nextInt(maxWidth)+1;
          final int height=_random.nextInt(maxHeight)+1;
          fontFitTextView.setLayoutParams(new LayoutParams(width,height));
          fontFitTextView.setSingleLine();
          fontFitTextView.setBackgroundColor(0xff00ff00);
          final String text=getRandomText();
          fontFitTextView.setText(text);
          container.addView(fontFitTextView);
          Log.d("DEBUG","width:"+width+" height:"+height+" text:"+text);
          }
      });
    }

  private String getRandomText()
    {
    final int textLength=_random.nextInt(20)+1;
    final StringBuilder builder=new StringBuilder();
    for(int i=0;i<textLength;++i)
      builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
    return builder.toString();
    }
  }

คำถาม

ใครรู้วิธีแก้ปัญหาสำหรับปัญหาทั่วไปนี้ที่ใช้งานได้จริง?

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


โครงการ GitHub

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


คุณลองอันนี้หรือไม่? androidviews.net/2012/12/autoscale-textview
— Thrakbad

@Thrakbad เป็นหนึ่งในลิงก์ที่ฉันพูดถึง มันยังไม่ผ่านการทดสอบ
— นักพัฒนา android

อาขอโทษฉันพลาดตัวอย่างสุดท้ายอย่างใด
— Thrakbad

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

1
@ กฎนี่เป็นหนึ่งในโพสต์ที่ฉันได้อ่านไปแล้วและฉันได้ทดสอบตัวอย่างโค้ดทั้งหมดแล้ว ฉันคิดว่าคุณโพสต์สองครั้ง
— นักพัฒนา android

คำตอบ:


147

ขอขอบคุณที่แก้ไขง่าย MartinH ของที่นี่รหัสนี้ยังดูแลandroid:drawableLeft, android:drawableRight, android:drawableTopและandroid:drawableBottomแท็ก


คำตอบของฉันที่นี่ควรทำให้คุณมีความสุขปรับขนาดข้อความอัตโนมัติให้พอดีภายในขอบเขต

ฉันได้แก้ไขกรณีทดสอบของคุณ:

@Override
protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ViewGroup container = (ViewGroup) findViewById(R.id.container);
    findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(final View v) {
            container.removeAllViews();
            final int maxWidth = container.getWidth();
            final int maxHeight = container.getHeight();
            final AutoResizeTextView fontFitTextView = new AutoResizeTextView(MainActivity.this);
            final int width = _random.nextInt(maxWidth) + 1;
            final int height = _random.nextInt(maxHeight) + 1;
            fontFitTextView.setLayoutParams(new FrameLayout.LayoutParams(
                    width, height));
            int maxLines = _random.nextInt(4) + 1;
            fontFitTextView.setMaxLines(maxLines);
            fontFitTextView.setTextSize(500);// max size
            fontFitTextView.enableSizeCache(false);
            fontFitTextView.setBackgroundColor(0xff00ff00);
            final String text = getRandomText();
            fontFitTextView.setText(text);
            container.addView(fontFitTextView);
            Log.d("DEBUG", "width:" + width + " height:" + height
                    + " text:" + text + " maxLines:" + maxLines);
        }
    });
}

ฉันกำลังโพสต์รหัสที่นี่ที่คำขอของนักพัฒนา android :

ผลสุดท้าย:

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

ตัวอย่างไฟล์เลย์เอาต์:

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

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

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

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

    public int getMaxLines() {
        return mMaxLines;
    }

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

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

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

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

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

    private void adjustTextSize() {
        if (!mInitializedDimens) {
            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);
        }
        int key = getText().toString().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 the 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);
        adjustTextSize();
    }

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

คำเตือน:

ระวังข้อผิดพลาดที่แก้ไข นี้ใน Android 3.1 (Honeycomb)


ตกลงฉันได้ทดสอบรหัสและดูเหมือนดี อย่างไรก็ตามคุณใช้ getMaxLines () ซึ่งใช้สำหรับ API 16 แต่คุณสามารถเก็บ maxLines และรับโดยการแทนที่ setMaxLines และเก็บค่าของมันฉันได้เปลี่ยนมันและตอนนี้มันทำงานได้ดี เช่นกันเนื่องจากบางครั้งข้อความอาจยาวเกินไปสำหรับขนาดที่เล็กที่สุดฉันพยายามใช้ setEllipsize และก็ใช้ได้เช่นกัน! ฉันคิดว่าเรามีผู้ชนะ กรุณาโพสต์รหัสของคุณที่นี่เพื่อให้ฉันสามารถทำเครื่องหมายว่าถูกต้อง
— นักพัฒนา Android

คุณไม่ได้ลบ getMaxLines () และใช้ของคุณเอง มันจะยังคงทำงานได้จาก API16 เท่านั้น ... โปรดเปลี่ยนเพื่อให้ทุกคนสามารถใช้งานได้ ฉันขอแนะนำให้แทนที่ setMaxLines เพื่อเก็บพารามิเตอร์ไว้ในเขตข้อมูลจากนั้นเข้าถึงฟิลด์แทนการใช้ getMaxLines
— นักพัฒนา android

ตรวจสอบตอนนี้เพิ่มการสนับสนุนสำหรับสายสูงสุด
— M-WaJeEh

ดูเหมือนดี คุณควรเพิ่ม "@TargetApi (Build.VERSION_CODES.JELLY_BEAN)" ในเมธอด onTestSize () เพื่อให้ผ้าสำลีไม่ให้ข้อผิดพลาด / เตือนเกี่ยวกับมันและเริ่มต้นมันใน CTOR แทน การตั้งค่าการค้นหาแบบไบนารีเป็นแบบคงที่เป็นสิ่งที่ดีและคุณสามารถทำการเริ่มต้นใน CTOR ที่ใหญ่ที่สุดและเรียกมันจาก CTOR อื่น ๆ อย่างไรก็ตามเป็นคำตอบที่ดีที่สุดและคุณควรได้รับ V นี่เป็นคำตอบที่ดีสำหรับคำถามเก่านี้ ดีมาก!
— นักพัฒนา Android

@ M-WaJeEh ตรวจสอบโพสต์ใหม่ในกระทู้นี้ ผู้ใช้ชื่อ "MartinH" กล่าวว่าเขาได้แก้ไขรหัสของคุณแล้ว
— นักพัฒนา android

14

ฉันได้แก้ไขคำตอบของ M-WaJeEh สักเล็กน้อยเพื่อให้คำนึงถึงสารประกอบที่สามารถวาดได้ด้านข้าง

วิธีกลับgetCompoundPaddingXXXX() padding of the view + drawable spaceดูตัวอย่าง: TextView.getCompoundPaddingLeft ()

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


ส่วนที่อัปเดตadjustTextSize(String):

private void adjustTextSize(final String text) {
  if (!mInitialized) {
    return;
  }
  int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
  mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();

  mAvailableSpaceRect.right = mWidthLimit;
  mAvailableSpaceRect.bottom = heightLimit;

  int maxTextSplits = text.split(" ").length;
  AutoResizeTextView.super.setMaxLines(Math.min(maxTextSplits, mMaxLines));

  super.setTextSize(
      TypedValue.COMPLEX_UNIT_PX,
      binarySearch((int) mMinTextSize, (int) mMaxTextSize,
                   mSizeTester, mAvailableSpaceRect));
}

คุณได้อัปเดตแหล่งที่มาของ M-WaJeEh ทำไมคุณถึงคิดว่ามันเป็นคำตอบใหม่? ฉันรู้ว่าคุณมีความปรารถนาดี แต่เป็นสิ่งที่ดีหรือไม่ที่จะใช้เป็นคำตอบ? ฉันไม่แน่ใจว่าเขาสามารถเห็นคำตอบของคุณ ...
— นักพัฒนา android

1
ฉันเพิ่งลงทะเบียนเพื่อให้สามารถอัปเดตได้ การนับชื่อเสียงของฉันเป็นเพียง 1 ซึ่งป้องกันไม่ให้ฉันแสดงความคิดเห็นในโพสต์ที่ไม่ใช่ของฉัน (ขั้นต่ำคือ 15)
— MartinH

ไม่ต้องกังวลคุณอาจต้องการส่งคำตอบของฉันไปที่ M-WajeEh เพราะฉันไม่สามารถติดต่อเขาได้ในขณะนี้: /
— MartinH

คุณกรุณาช่วยอธิบายได้ดีขึ้นว่าคุณทำอะไรไป คุณแก้ไขข้อผิดพลาดหรือไม่? คุณสามารถแสดงภาพหน้าจอเพื่อแสดงข้อผิดพลาดได้หรือไม่?
— นักพัฒนา android

1
สวัสดี @MartinH ยินดีต้อนรับสู่ SO ฉันโกรธจะดูในนี้และจะปรับปรุงคำตอบของฉันโดยกล่าวถึงชื่อของคุณและการโพสต์ลิงก์ไปยังโพสต์ของคุณหลังจากการทดสอบ โปรดให้ฉันสองสามวัน ฉันติดอยู่ที่อื่นทุกวันนี้
— M-WaJeEh

7

ตกลงฉันได้ใช้สัปดาห์ที่ผ่านมาอย่างหนาแน่นเขียนโค้ดของฉันไปอย่างแม่นยำพอดีกับการทดสอบของคุณ ตอนนี้คุณสามารถคัดลอกนี้ 1: 1 และมันจะทำงานได้ทันที - setSingleLine()รวมทั้ง โปรดจำไว้ว่าต้องปรับMIN_TEXT_SIZEและMAX_TEXT_SIZEถ้าคุณจะได้ค่าที่มากที่สุด

อัลกอริทึมการแปลงมีลักษณะดังนี้:

for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

    // Go to the mean value...
    testSize = (upperTextSize + lowerTextSize) / 2;

    // ... inflate the dummy TextView by setting a scaled textSize and the text...
    mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
    mTestView.setText(text);

    // ... call measure to find the current values that the text WANTS to occupy
    mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    int tempHeight = mTestView.getMeasuredHeight();

    // ... decide whether those values are appropriate.
    if (tempHeight >= targetFieldHeight) {
        upperTextSize = testSize; // Font is too big, decrease upperSize
    }
    else {
        lowerTextSize = testSize; // Font is too small, increase lowerSize
    }
}

และทั้งชั้นสามารถพบได้ที่นี่

ผลลัพธ์มีความยืดหยุ่นมากในขณะนี้ มันใช้งานได้เหมือนกันประกาศใน xml ดังนี้:

<com.example.myProject.AutoFitText
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="4"
    android:text="@string/LoremIpsum" />

... เช่นเดียวกับการสร้างโปรแกรมเหมือนในการทดสอบของคุณ

ฉันหวังว่าคุณจะสามารถใช้สิ่งนี้ได้ในตอนนี้ คุณสามารถโทรหาsetText(CharSequence text)เพื่อใช้งานได้ทันที ชั้นเรียนจะดูแลข้อยกเว้นที่หายากอย่างน่าอัศจรรย์และควรเป็นหินที่แข็งแกร่ง สิ่งเดียวที่อัลกอริทึมยังไม่รองรับคือ:

  • โทรไปsetMaxLines(x)ที่ไหนx >= 2

แต่ฉันได้เพิ่มความคิดเห็นมากมายเพื่อช่วยคุณสร้างสิ่งนี้หากคุณต้องการ!


โปรดทราบ:

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

โดยสรุป: คุณควรพิจารณาการเขียนการทดสอบของคุณใหม่เพื่อสนับสนุน chars space เช่น:

final Random _random = new Random();
final String ALLOWED_CHARACTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
final int textLength = _random.nextInt(80) + 20;
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < textLength; ++i) {
    if (i % 7 == 0 && i != 0) {
        builder.append(" ");
    }
    builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
}
((AutoFitText) findViewById(R.id.textViewMessage)).setText(builder.toString());

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

ขอให้โชคดีและขอแสดงความนับถือ


มีบางประเด็น แต่โดยรวมแล้วก็ใช้งานได้ดีมาก ปัญหา: ไม่รองรับหลายบรรทัด (ตามที่คุณเขียน) และจะแบ่งคำในกรณีที่คุณพยายามทำรวมฟังก์ชั่น API16 (addOnGlobalLayoutListener) ซึ่งฉันคิดว่าสามารถหลีกเลี่ยงได้โดยสิ้นเชิง (หรืออย่างน้อยใช้ OnPreDrawListener แทน) ใช้ การค้นหาแบบไบนารีดังนั้นจึงอาจทดสอบขนาดที่ไม่พอดีเลย (และใช้หน่วยความจำเพิ่มเติมโดยไม่มีเหตุผลซึ่งอาจทำให้เกิดข้อยกเว้นหากมีขนาดใหญ่เกินไป) ไม่รองรับการจัดการเมื่อมีการเปลี่ยนแปลงอะไร (เช่นข้อความ)
— นักพัฒนา android

btw คุณไม่ต้องใช้ตัวแปร mTestView และทำการทดสอบโดยตรงในมุมมองปัจจุบันและด้วยวิธีนี้คุณจะไม่ต้องพิจารณาสิ่งพิเศษทั้งหมด นอกจากนี้ฉันคิดว่าแทนที่จะเป็น MAX_TEXT_SIZE คุณสามารถใช้ targetFieldHeight
— นักพัฒนา android

ฉันคิดว่าแทนที่จะเป็นการค้นหาแบบไบนารี่ที่บริสุทธิ์ (โดยเฉพาะอย่างยิ่ง 2 ในนั้น) คุณสามารถคาดเดาได้ดีกว่าด้วยอัตราส่วนของเป้าหมายเทียบกับขนาดที่วัดได้ นอกจากนี้ยังมีวิธีการอื่น (นิวตันและอย่างอื่นที่ฉันจำไม่ได้) แต่ฉันไม่คิดว่ามันจะพอดีกับที่นี่ สิ่งที่ฉันแนะนำตอนนี้ดีกว่าเนื่องจากคุณสามารถเริ่มต้นจากขนาดต่ำสุดแทนการทดสอบขนาดสูงสุดนั่นคือคุณไม่จำเป็นต้องมีขนาดสูงสุดเนื่องจากคุณคาดเดาได้ดีกว่า
— นักพัฒนา android

ฉันยังคิดว่าการค้นหาแบบไบนารี 2 รายการสามารถรวมกันเป็นบรรทัดเดียวเมื่อเงื่อนไขอาจเป็นเพียง: if (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
— นักพัฒนา Android

1
อีกปัญหาที่ฉันพบคือแรงโน้มถ่วงของข้อความ ฉันคิดว่ามันไม่สามารถอยู่กึ่งกลางแนวตั้งได้
— นักพัฒนา android

4

ความต้องการของฉันคือ

  • คลิกที่ ScalableTextView
  • เปิด listActivity และแสดงรายการสตริงความยาวต่างๆ
  • เลือกข้อความจากรายการ
  • ตั้งค่าข้อความกลับเป็น ScalableTextView ในกิจกรรมอื่น

ฉันอ้างถึงลิงก์: Auto Scale TextView Text ให้พอดีภายในขอบเขต (รวมถึงความคิดเห็น) และDialogTitle.java

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

ฉันปรับเปลี่ยน ScalableTextView.java เพื่อปรับขนาดตัวอักษรใหม่ตามความยาวข้อความ นี่คือของฉันScalableTextView.java

public class ScalableTextView extends TextView
{
float defaultTextSize = 0.0f;

public ScalableTextView(Context context, AttributeSet attrs, int defStyle)
{
    super(context, attrs, defStyle);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

public ScalableTextView(Context context, AttributeSet attrs)
{
    super(context, attrs);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

public ScalableTextView(Context context)
{
    super(context);
    setSingleLine();
    setEllipsize(TruncateAt.END);
    defaultTextSize = getTextSize();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

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

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

                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                ellipsisCount = layout.getEllipsisCount(lineCount - 1);
            }
        }
    }
}
}

Happy Coding


คุณไม่ควรใช้การค้นหาแบบไบนารีแทนการลดลงทีละหนึ่งซ้ำสำหรับการทำซ้ำแต่ละครั้ง นอกจากนี้ดูเหมือนว่ารหัสนี้บังคับให้ TextView อยู่ในบรรทัดเดียวของข้อความแทนที่จะอนุญาตให้มีหลายบรรทัด
— นักพัฒนา android

นี่เป็นทางออกเดียวที่ได้ผลสำหรับฉัน ปัญหาของฉันเกี่ยวข้องกับ TExtView ไม่เหมาะกับมุมมองใน 4.1 เท่านั้น
— FOliveira

ดูเหมือนว่าจะไม่ตรงกับหลัก 100% ดูภาพหน้าจอ: s14.postimg.org/93c2xgs75/Sc ทัศน์_2.png
— 25

4

ฉันจะอธิบายวิธีการใช้งานคุณลักษณะนี้ลดรุ่น Android ทีละขั้นตอน:

1- นำเข้า android support library 26.xx ในไฟล์ gradle โครงการของคุณ หากไม่มีไลบรารีสนับสนุนบน IDE พวกเขาจะดาวน์โหลดโดยอัตโนมัติ

dependencies {
    compile 'com.android.support:support-v4:26.1.0'
    compile 'com.android.support:appcompat-v7:26.1.0'
    compile 'com.android.support:support-v13:26.1.0' }

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    } }

2- เปิดไฟล์ XML เลย์เอาต์ของคุณและสร้างซ้ำเช่นแท็ก TextView ของคุณ สถานการณ์นี้คือ: เมื่อเพิ่มขนาดตัวอักษรในระบบให้พอดีกับข้อความที่มีความกว้างที่ใช้ได้ไม่ใช่การตัดคำ

<android.support.v7.widget.AppCompatTextView
            android:id="@+id/textViewAutoSize"
            android:layout_width="match_parent"
            android:layout_height="25dp"
            android:ellipsize="none"
            android:text="Auto size text with compatible lower android versions."
            android:textSize="12sp"
            app:autoSizeMaxTextSize="14sp"
            app:autoSizeMinTextSize="4sp"
            app:autoSizeStepGranularity="0.5sp"
            app:autoSizeTextType="uniform" />

การใช้งาน TextView แบบพอดีอัตโนมัติไม่ได้ผล มีบางครั้งที่แทนการปรับขนาดขนาดตัวอักษรข้อความเพียง wraps ยังบรรทัดถัดไปไม่ดี ... มันก็แสดงให้เห็นแม้ในวิดีโอของพวกเขาราวกับว่ามันเป็นสิ่งที่ดี: youtu.be/fjUdJ2aVqE4?t=14 แจ้งให้ทราบว่า "W" ของ "TextView" ไปยังบรรทัดถัดไป ... อย่างไรก็ตามสร้างคำขอใหม่เกี่ยวกับเรื่องนี้ที่นี่: issuetracker.google.com/issues/68787108 โปรดพิจารณาตัวเอก
— นักพัฒนา android

@androiddeveloper คุณพูดถูก ฉันไม่ได้อธิบายการใช้งานแท็กนี้ใช้งานได้บรรทัดเดียว หากคุณต้องการตัดบรรทัดคุณต้องเปลี่ยนแอตทริบิวต์นี้: android: layout_height = "wrap_content" แต่ android.support.v7.widget.AppCompatTextView และการรับประกันการใช้งานแบบ gradle ใช้งานได้
— Sinan Ergin

ฉันไม่คิดว่าคุณเข้าใจ ฉันไม่ต้องการให้คำบางส่วนตัดคำในบรรทัดถัดไปเว้นแต่จะไม่มีวิธีอื่นในการแก้ไข ในวิดีโอคุณสามารถเห็นแบบอักษรที่อาจมีขนาดเล็กลงแทน นั่นคือปัญหา คุณกำลังบอกว่าคุณรู้วิธีแก้ไขหรือไม่? ความหมายจะไม่มีการตัดคำและการเปลี่ยนแปลงขนาดตัวอักษรเท่านั้น?
— นักพัฒนาซอฟต์แวร์ Android

@androiddeveloper มีหลายสถานการณ์ สถานการณ์ของฉันคือ: ป้อนข้อความอัตโนมัติเพื่อแก้ไขด้วยไม่ใช่การตัดคำ ระหว่างการเพิ่มขนาดตัวอักษรของระบบข้อความจะต้องไม่แสดงการห่อคำในมุมมองข้อความ หากการห่อคำไม่สำคัญสำหรับคุณไม่ต้องตั้งค่าความสูงคงที่ คุณสมบัตินี้ไม่ได้ใช้เพียงแค่การตัดคำเท่านั้นคุณสามารถป้อนข้อความอัตโนมัติได้ด้วยความกว้างคงที่ของ TextView
— Sinan Ergin

จากการทดสอบของฉันขนาดตัวอักษรไม่เปลี่ยนแปลงอย่างถูกต้องและปัญหาการตัดคำอาจเกิดขึ้นได้
— นักพัฒนา android

3

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

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

ข้อผิดพลาดที่อธิบายไว้ในฉบับที่ 22493: TextView ข้อผิดพลาดสูงใน Android 4.0และฉบับที่ 17343: ความสูงของปุ่มและข้อความไม่กลับสู่สภาพเดิมภายหลังการเพิ่มและลดขนาดของข้อความในรังผึ้ง

วิธีแก้ปัญหาคือการเพิ่มอักขระบรรทัดใหม่ให้กับข้อความที่กำหนดให้ 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 แต่ฉันต้องการเก็บรหัสแก้ไขไว้ด้านนอก


คุณช่วยกรุณาแสดงให้เห็นอย่างชัดเจนว่าสายใด (ก่อน / หลังสายใด) และฟังก์ชั่นใด
— นักพัฒนา android

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

รหัสนี้ถูกเรียกจากภายนอกรหัสของมุมมอง คุณรู้วิธีที่ดีในการจัดการสิ่งนี้ภายในโค้ดของมุมมองแทนหรือไม่?
— นักพัฒนา android

คุณสามารถแทนที่ textView.setText () และใส่ในรหัสนี้ของชั้น TextView
— Malachiasz

ไม่ได้หมายความว่า "getText ()" จะส่งคืนข้อความด้วยอักขระพิเศษหรือไม่
— นักพัฒนา android

3

ขณะนี้มีวิธีแก้ปัญหาอย่างเป็นทางการสำหรับปัญหานี้แล้ว การเปลี่ยนขนาดข้อความอัตโนมัติที่แนะนำด้วย Android O นั้นมีอยู่ใน Support Library 26 และสามารถใช้งานร่วมกันได้ย้อนหลังไปจนถึง Android 4.0

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

ฉันไม่แน่ใจว่าทำไมhttps://stackoverflow.com/a/42940171/47680ซึ่งรวมถึงข้อมูลนี้ถูกลบโดยผู้ดูแลระบบ


1
ใช่ฉันเคยได้ยินเกี่ยวกับเรื่องนี้ด้วย แต่มันจะดีแค่ไหน? มีตัวอย่างของการใช้งานหรือไม่ แม้ในวิดีโอของพวกเขาฉันได้สังเกตเห็นข้อผิดพลาด: จดหมายจากหนึ่งคำได้ถึงบรรทัดหลังจาก (ห่อ): youtu.be/1N9KveJ-FU8?t=781 (สังเกตเห็น "W")
— นักพัฒนา Android

3

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

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

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

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

ตรวจสอบรายละเอียดของฉันคำตอบ


1

แปลงมุมมองข้อความเป็นภาพและปรับขนาดภาพภายในขอบเขต

นี่คือตัวอย่างในการแปลงมุมมองไปยังภาพ: แปลงมุมมองในการ Bitmap โดยไม่ต้องแสดงไว้ใน Android?

ปัญหาคือข้อความของคุณจะไม่สามารถเลือกได้ แต่ควรทำเคล็ดลับ ฉันไม่ได้ลองดังนั้นฉันไม่แน่ใจว่ามันจะมีลักษณะอย่างไร (เนื่องจากการปรับขนาด)


เป็นความคิดที่ดี แต่สิ่งนี้จะทำให้ข้อความเป็นพิกเซลและลบคุณลักษณะใด ๆ ที่ฉันสามารถใช้ได้กับ textView
— นักพัฒนา android

0

ด้านล่างนี้เป็น avalancha TextView พร้อมฟังก์ชันเพิ่มเติมสำหรับแบบอักษรที่กำหนดเอง

การใช้งาน:

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

                <de.meinprospekt.androidhd.view.AutoFitText
                android:layout_width="wrap_content"
                android:layout_height="10dp"
                android:text="Small Text"
                android:textColor="#FFFFFF"
                android:textSize="100sp"
                foo:customFont="fonts/Roboto-Light.ttf" />

</FrameLayout>

อย่าลืมเพิ่ม: xmlns: foo = "http://schemas.android.com/apk/res-auto" แบบอักษรควรอยู่ในเนื้อหาของไดเรกทอรี

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.TextView;
import de.meinprospekt.androidhd.R;
import de.meinprospekt.androidhd.adapter.BrochuresHorizontalAdapter;
import de.meinprospekt.androidhd.util.LOG;

/**
 * https://stackoverflow.com/a/16174468/2075875 This class builds a new android Widget named AutoFitText which can be used instead of a TextView to
 * have the text font size in it automatically fit to match the screen width. Credits go largely to Dunni, gjpc, gregm and speedplane from
 * Stackoverflow, method has been (style-) optimized and rewritten to match android coding standards and our MBC. This version upgrades the original
 * "AutoFitTextView" to now also be adaptable to height and to accept the different TextView types (Button, TextClock etc.)
 * 
 * @author pheuschk
 * @createDate: 18.04.2013
 * 
 * combined with: https://stackoverflow.com/a/7197867/2075875
 */
@SuppressWarnings("unused")
public class AutoFitText extends TextView {

    private static final String TAG = AutoFitText.class.getSimpleName();

    /** Global min and max for text size. Remember: values are in pixels! */
    private final int MIN_TEXT_SIZE = 10;
    private final int MAX_TEXT_SIZE = 400;

    /** Flag for singleLine */
    private boolean mSingleLine = false;

    /**
     * A dummy {@link TextView} to test the text size without actually showing anything to the user
     */
    private TextView mTestView;

    /**
     * A dummy {@link Paint} to test the text size without actually showing anything to the user
     */
    private Paint mTestPaint;

    /**
     * Scaling factor for fonts. It's a method of calculating independently (!) from the actual density of the screen that is used so users have the
     * same experience on different devices. We will use DisplayMetrics in the Constructor to get the value of the factor and then calculate SP from
     * pixel values
     */
    private float mScaledDensityFactor;

    /**
     * Defines how close we want to be to the factual size of the Text-field. Lower values mean higher precision but also exponentially higher
     * computing cost (more loop runs)
     */
    private final float mThreshold = 0.5f;

    /**
     * Constructor for call without attributes --> invoke constructor with AttributeSet null
     * 
     * @param context
     */
    public AutoFitText(Context context) {
        this(context, null);
    }

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

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

    private void init(Context context, AttributeSet attrs) {
        //TextViewPlus part https://stackoverflow.com/a/7197867/2075875
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoFitText);
        String customFont = a.getString(R.styleable.AutoFitText_customFont);
        setCustomFont(context, customFont);
        a.recycle();

        // AutoFitText part
        mScaledDensityFactor = context.getResources().getDisplayMetrics().scaledDensity;
        mTestView = new TextView(context);

        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());

        this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

            @Override
            public void onGlobalLayout() {
                // make an initial call to onSizeChanged to make sure that refitText is triggered
                onSizeChanged(AutoFitText.this.getWidth(), AutoFitText.this.getHeight(), 0, 0);
                // Remove the LayoutListener immediately so we don't run into an infinite loop
                //AutoFitText.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                removeOnGlobalLayoutListener(AutoFitText.this, this);
            }
        });
    }

    public boolean setCustomFont(Context ctx, String asset) {
        Typeface tf = null;
        try {
        tf = Typeface.createFromAsset(ctx.getAssets(), asset);  
        } catch (Exception e) {
            LOG.e(TAG, "Could not get typeface: "+e.getMessage());
            return false;
        }

        setTypeface(tf);  
        return true;
    }

    @SuppressLint("NewApi")
    public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener){
        if (Build.VERSION.SDK_INT < 16) {
            v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
        } else {
            v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
        }
    }

    /**
     * Main method of this widget. Resizes the font so the specified text fits in the text box assuming the text box has the specified width. This is
     * done via a dummy text view that is refit until it matches the real target width and height up to a certain threshold factor
     * 
     * @param targetFieldWidth The width that the TextView currently has and wants filled
     * @param targetFieldHeight The width that the TextView currently has and wants filled
     */
    private void refitText(String text, int targetFieldWidth, int targetFieldHeight) {

        // Variables need to be visible outside the loops for later use. Remember size is in pixels
        float lowerTextSize = MIN_TEXT_SIZE;
        float upperTextSize = MAX_TEXT_SIZE;

        // Force the text to wrap. In principle this is not necessary since the dummy TextView
        // already does this for us but in rare cases adding this line can prevent flickering
        this.setMaxWidth(targetFieldWidth);

        // Padding should not be an issue since we never define it programmatically in this app
        // but just to to be sure we cut it off here
        targetFieldWidth = targetFieldWidth - this.getPaddingLeft() - this.getPaddingRight();
        targetFieldHeight = targetFieldHeight - this.getPaddingTop() - this.getPaddingBottom();

        // Initialize the dummy with some params (that are largely ignored anyway, but this is
        // mandatory to not get a NullPointerException)
        mTestView.setLayoutParams(new LayoutParams(targetFieldWidth, targetFieldHeight));

        // maxWidth is crucial! Otherwise the text would never line wrap but blow up the width
        mTestView.setMaxWidth(targetFieldWidth);

        if (mSingleLine) {
            // the user requested a single line. This is very easy to do since we primarily need to
            // respect the width, don't have to break, don't have to measure...

            /*************************** Converging algorithm 1 ***********************************/
            for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                // Go to the mean value...
                testSize = (upperTextSize + lowerTextSize) / 2;

                mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                mTestView.setText(text);
                mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

                if (mTestView.getMeasuredWidth() >= targetFieldWidth) {
                    upperTextSize = testSize; // Font is too big, decrease upperSize
                } else {
                    lowerTextSize = testSize; // Font is too small, increase lowerSize
                }
            }
            /**************************************************************************************/

            // In rare cases with very little letters and width > height we have vertical overlap!
            mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

            if (mTestView.getMeasuredHeight() > targetFieldHeight) {
                upperTextSize = lowerTextSize;
                lowerTextSize = MIN_TEXT_SIZE;

                /*************************** Converging algorithm 1.5 *****************************/
                for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                    // Go to the mean value...
                    testSize = (upperTextSize + lowerTextSize) / 2;

                    mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                    mTestView.setText(text);
                    mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

                    if (mTestView.getMeasuredHeight() >= targetFieldHeight) {
                        upperTextSize = testSize; // Font is too big, decrease upperSize
                    } else {
                        lowerTextSize = testSize; // Font is too small, increase lowerSize
                    }
                }
                /**********************************************************************************/
            }
        } else {

            /*********************** Converging algorithm 2 ***************************************/
            // Upper and lower size converge over time. As soon as they're close enough the loop
            // stops
            // TODO probe the algorithm for cost (ATM possibly O(n^2)) and optimize if possible
            for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

                // Go to the mean value...
                testSize = (upperTextSize + lowerTextSize) / 2;

                // ... inflate the dummy TextView by setting a scaled textSize and the text...
                mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                mTestView.setText(text);

                // ... call measure to find the current values that the text WANTS to occupy
                mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
                int tempHeight = mTestView.getMeasuredHeight();
                // int tempWidth = mTestView.getMeasuredWidth();

                // LOG.debug("Measured: " + tempWidth + "x" + tempHeight);
                // LOG.debug("TextSize: " + testSize / mScaledDensityFactor);

                // ... decide whether those values are appropriate.
                if (tempHeight >= targetFieldHeight) {
                    upperTextSize = testSize; // Font is too big, decrease upperSize
                } else {
                    lowerTextSize = testSize; // Font is too small, increase lowerSize
                }
            }
            /**************************************************************************************/

            // It is possible that a single word is wider than the box. The Android system would
            // wrap this for us. But if you want to decide fo yourself where exactly to break or to
            // add a hyphen or something than you're going to want to implement something like this:
            mTestPaint.setTextSize(lowerTextSize);
            List<String> words = new ArrayList<String>();

            for (String s : text.split(" ")) {
                Log.i("tag", "Word: " + s);
                words.add(s);
            }
            for (String word : words) {
                if (mTestPaint.measureText(word) >= targetFieldWidth) {
                    List<String> pieces = new ArrayList<String>();
                    // pieces = breakWord(word, mTestPaint.measureText(word), targetFieldWidth);

                    // Add code to handle the pieces here...
                }
            }
        }

        /**
         * We are now at most the value of threshold away from the actual size. To rather undershoot than overshoot use the lower value. To match
         * different screens convert to SP first. See {@link http://developer.android.com/guide/topics/resources/more-resources.html#Dimension} for
         * more details
         */
        this.setTextSize(TypedValue.COMPLEX_UNIT_SP, lowerTextSize / mScaledDensityFactor);
        return;
    }

    /**
     * This method receives a call upon a change in text content of the TextView. Unfortunately it is also called - among others - upon text size
     * change which means that we MUST NEVER CALL {@link #refitText(String)} from this method! Doing so would result in an endless loop that would
     * ultimately result in a stack overflow and termination of the application
     * 
     * So for the time being this method does absolutely nothing. If you want to notify the view of a changed text call {@link #setText(CharSequence)}
     */
    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        // Super implementation is also intentionally empty so for now we do absolutely nothing here
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        if (width != oldWidth && height != oldHeight) {
            refitText(this.getText().toString(), width, height);
        }
    }

    /**
     * This method is guaranteed to be called by {@link TextView#setText(CharSequence)} immediately. Therefore we can safely add our modifications
     * here and then have the parent class resume its work. So if text has changed you should always call {@link TextView#setText(CharSequence)} or
     * {@link TextView#setText(CharSequence, BufferType)} if you know whether the {@link BufferType} is normal, editable or spannable. Note: the
     * method will default to {@link BufferType#NORMAL} if you don't pass an argument.
     */
    @Override
    public void setText(CharSequence text, BufferType type) {

        int targetFieldWidth = this.getWidth();
        int targetFieldHeight = this.getHeight();

        if (targetFieldWidth <= 0 || targetFieldHeight <= 0 || text.equals("")) {
            // Log.v("tag", "Some values are empty, AutoFitText was not able to construct properly");
        } else {
            refitText(text.toString(), targetFieldWidth, targetFieldHeight);
        }
        super.setText(text, type);
    }

    /**
     * TODO add sensibility for {@link #setMaxLines(int)} invocations
     */
    @Override
    public void setMaxLines(int maxLines) {
        // TODO Implement support for this. This could be relatively easy. The idea would probably
        // be to manipulate the targetHeight in the refitText-method and then have the algorithm do
        // its job business as usual. Nonetheless, remember the height will have to be lowered
        // dynamically as the font size shrinks so it won't be a walk in the park still
        if (maxLines == 1) {
            this.setSingleLine(true);
        } else {
            throw new UnsupportedOperationException("MaxLines != 1 are not implemented in AutoFitText yet, use TextView instead");
        }
    }

    @Override
    public void setSingleLine(boolean singleLine) {
        // save the requested value in an instance variable to be able to decide later
        mSingleLine = singleLine;
        super.setSingleLine(singleLine);
    }
}

ข้อบกพร่องที่รู้จัก: ไม่ทำงานกับ Android 4.03 - ตัวอักษรจะมองไม่เห็นหรือเล็กมาก (avalancha ดั้งเดิมก็ใช้งานไม่ได้) ด้านล่างนี้เป็นวิธีแก้ปัญหาสำหรับข้อผิดพลาดนั้น: https://stackoverflow.com/a/21851239/2075875


0

ลองสิ่งนี้

TextWatcher changeText = new TextWatcher() {
     @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                tv3.setText(et.getText().toString());
                tv3.post(new Runnable() {           
                    @Override
                    public void run() {
                    while(tv3.getLineCount() >= 3){                     
                            tv3.setTextSize((tv3.getTextSize())-1);                     
                        }
                    }
                });
            }

            @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

            @Override public void afterTextChanged(Editable s) { }
        };

ฉันไม่คิดว่ามันจะใช้งานได้ทุกครั้งที่คุณเปลี่ยนขนาดตัวอักษรมันจะทำให้เกิดการวาดเฉพาะหลังจากที่คุณจบด้วยบล็อก (หมายถึงมันจะถูกเพิ่มลงในคิวเหตุการณ์) ซึ่งหมายความว่าการวนซ้ำจะทำงานเพียงครั้งเดียวในแต่ละครั้งที่มีการเปลี่ยนแปลงข้อความและไม่รับประกันว่าจำนวนบรรทัดจะไม่เกิน 3 บรรทัดของข้อความ
— นักพัฒนา android

0

หากคุณกำลังมองหาบางสิ่งที่ง่ายขึ้น:

 public MyTextView extends TextView{

    public void resize(String text, float textViewWidth, float textViewHeight) {
       Paint p = new Paint();
       Rect bounds = new Rect();
       p.setTextSize(1);
       p.getTextBounds(text, 0, text.length(), bounds);
       float widthDifference = (textViewWidth)/bounds.width();
       float heightDifference = (textViewHeight);
       textSize = Math.min(widthDifference, heightDifference);
       setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}

ดูเหมือนจะสั้นมาก แต่สามารถผ่านการทดสอบที่ฉันเตรียมไว้ได้ไหม นอกจากนี้ฉันไม่ได้รับเมื่อเรียกใช้ฟังก์ชันนี้
— นักพัฒนา android

ไม่มันเป็นคำแนะนำเพิ่มเติม ฉันจะปรับปรุงมันเพื่อให้ผ่านการทดสอบของคุณโดยเร็ว
— Sander

ถ้าเป็นข้อเสนอแนะคุณช่วยกรุณาโพสต์ลงใน Github โดยตรงได้ไหม?
— นักพัฒนา android

0

แก้ไขด่วนสำหรับปัญหาที่อธิบายโดย @Malachiasz

ฉันได้แก้ไขปัญหาแล้วโดยเพิ่มการสนับสนุนที่กำหนดเองสำหรับสิ่งนี้ในคลาสการปรับขนาดอัตโนมัติ:

public void setTextCompat(final CharSequence text) {
    setTextCompat(text, BufferType.NORMAL);
}

public void setTextCompat(final CharSequence text, BufferType type) {
    // Quick fix for Android Honeycomb and Ice Cream Sandwich which sets the text only on the first call
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
        super.setText(DOUBLE_BYTE_WORDJOINER + text + DOUBLE_BYTE_WORDJOINER, type);
    } else {
        super.setText(text, type);
    }
}

@Override
public CharSequence getText() {
    String originalText = super.getText().toString();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
        // We try to remove the word joiners we added using compat method - if none found - this will do nothing.
        return originalText.replaceAll(DOUBLE_BYTE_WORDJOINER, "");
    } else {
        return originalText;
    }
}

แค่โทรมาyourView.setTextCompat(newTextValue)แทนyourView.setText(newTextValue)


เหตุใดจึงสร้างฟังก์ชันใหม่ของ setTextCompat แทนที่จะแทนที่ setText นอกจากนี้ปัญหาใด
— นักพัฒนา android

setText () เป็นวิธีสุดท้ายของ TextView ดังนั้นคุณจะไม่สามารถแทนที่มันได้ ตัวเลือกอื่นจะทำเช่นนี้นอก TextView ที่กำหนดเอง แต่วิธีนี้มีไว้สำหรับใช้ใน TextView
— Ionut Negru

ไม่แน่นอน บางส่วนของ "setText" เป็นส่วนตัวบางคนเป็นสาธารณะและบางคนไม่ได้สุดท้าย ดูเหมือนว่าส่วนใหญ่สามารถจัดการได้ด้วยการแทนที่ public void setText (ข้อความ CharSequence ประเภท BufferType) มีฟังก์ชั่นสุดท้ายที่ฉันไม่ทราบว่าวัตถุประสงค์ของมันคืออะไร: เป็นโมฆะสาธารณะสุดท้าย setText (ถ่าน [] ข้อความเริ่มต้น int, int len) อาจมองลึกลงไปที่รหัส
— นักพัฒนา android

ตัวแปรทั้งหมดของ setText () จริง ๆ แล้วจะเรียกเมธอด setText (CharSequence text) ในท้ายที่สุด หากคุณต้องการให้แน่ใจว่าพฤติกรรมเดียวกันคุณจะต้องแทนที่วิธีการนั้นมิฉะนั้นจะเป็นการดีกว่าที่จะเพิ่มเมธอด setText () ที่กำหนดเองของคุณเอง
— Ionut Negru

ใช่ แต่บางทีสำหรับกรณีส่วนใหญ่มันก็โอเค
— นักพัฒนา android

0

ลองเพิ่มLayoutParamsและMaxWidthและไปMaxHeight TextViewมันจะบังคับให้รูปแบบที่จะเคารพภาชนะแม่และไม่ล้น

textview.setLayoutParams(new LayoutParams(LinearLayout.MATCH_PARENT,LinearLayout.WRAP_CONTENT));

int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);` 

ฉันไม่แน่ใจว่าคุณกำลังพูดถึงอะไร คุณแนะนำให้เพิ่มสิ่งนี้ลงในตัวอย่าง textView เพื่อที่จะไม่มีปัญหาเล็ก ๆ ที่ฉันเห็นในบางครั้ง ถ้าเป็นเช่นนั้นทำไมคุณไม่โพสต์เกี่ยวกับเว็บไซต์ GitHub?
— นักพัฒนา android

0

ตั้งแต่ Android O เป็นไปได้ที่จะปรับขนาดข้อความโดยอัตโนมัติใน xml:

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

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

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

Support Library 26.0 Beta ให้การสนับสนุนอย่างเต็มที่กับฟีเจอร์การดูข้อความอัตโนมัติบนอุปกรณ์ที่ใช้ Android เวอร์ชันก่อนหน้า Android O ห้องสมุดให้การสนับสนุน Android 4.0 (ระดับ API 14) และสูงกว่า แพ็คเกจ android.support.v4.widget มีคลาส TextViewCompat เพื่อเข้าถึงฟีเจอร์ในรูปแบบที่เข้ากันได้แบบย้อนหลัง


น่าเศร้าที่จากการทดสอบของฉันและแม้แต่ในวิดีโอของ Google IO ฉันพบว่ามันมีปัญหาเช่นการตัดคำที่ไม่ถูกต้องแทนที่จะปรับขนาดตัวอักษร ฉันได้รายงานเกี่ยวกับสิ่งนี้ที่นี่: issuetracker.google.com/issues/38468964และนี่คือเหตุผลที่ฉันยังไม่ได้ใช้
— นักพัฒนา android

0

หลังจากที่ฉันลองใช้TextView Autosizingอย่างเป็นทางการของ Android ฉันพบว่าเวอร์ชัน Android ของคุณก่อนหน้า Android 8.0 (ระดับ API 26) คุณต้องใช้android.support.v7.widget.AppCompatTextViewและตรวจสอบให้แน่ใจว่าเวอร์ชันไลบรารีสนับสนุนของคุณนั้นสูงกว่า 26.0.0 ตัวอย่าง:

<android.support.v7.widget.AppCompatTextView
    android:layout_width="130dp"
    android:layout_height="32dp"
    android:maxLines="1"
    app:autoSizeMaxTextSize="22sp"
    app:autoSizeMinTextSize="12sp"
    app:autoSizeStepGranularity="2sp"
    app:autoSizeTextType="uniform" />

อัปเดต :

ตามการตอบกลับของ @ android-developers ของผู้พัฒนาฉันตรวจสอบAppCompatActivityซอร์สโค้ดและพบสองบรรทัดนี้onCreate

final AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory();

และในAppCompatDelegateImpl'screateView

    if (mAppCompatViewInflater == null) {
        mAppCompatViewInflater = new AppCompatViewInflater();
    }

มันใช้AppCompatViewInflaterมุมมอง inflater เมื่อAppCompatViewInflatercreateView มันจะใช้ AppCompatTextView สำหรับ "TextView"

public final View createView(){
    ...
    View view = null;
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        case "ImageView":
            view = new AppCompatImageView(context, attrs);
            break;
        case "Button":
            view = new AppCompatButton(context, attrs);
            break;
    ...
}

ในโครงการของฉันฉันไม่ได้ใช้AppCompatActivityดังนั้นฉันต้องใช้<android.support.v7.widget.AppCompatTextView>ใน xml


1
ในกรณีส่วนใหญ่คนสูบลมใช้ AppCompatTextView เมื่อคุณเขียนแค่ "TextView" ดังนั้นคุณไม่จำเป็นต้องทำ
— นักพัฒนา android

@androiddeveloper แต่เมื่อฉันใช้ TextView การทำให้ขนาดอัตโนมัติไม่ทำงาน
— weei.zh

@androiddeveloper AppCompatActivityขอบคุณเหตุผลที่ฉันไม่ได้ใช้งาน AppCompatActivity ใช้เค้าโครง AppCompatViewInflater
— weei.zh

ฉันไม่เข้าใจ
— นักพัฒนาหุ่นยนต์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.