ปรับ 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.