Tự động điều chỉnh TextView cho Android


231

Lý lịch

Nhiều lần chúng ta cần tự động điều chỉnh phông chữ của TextView theo các ranh giới được cung cấp cho nó.

Vấn đề

Đáng buồn thay, mặc dù có nhiều chủ đề và bài viết (và giải pháp được đề xuất) nói về vấn đề này (ví dụ ở đây , ở đâyở đây ), không ai trong số họ thực sự hoạt động tốt.

Đó là lý do tại sao, tôi đã quyết định thử nghiệm từng người trong số họ cho đến khi tôi tìm được thỏa thuận thực sự.

Tôi nghĩ rằng các yêu cầu từ một TextView như vậy phải là:

  1. Nên cho phép sử dụng bất kỳ phông chữ, kiểu chữ, kiểu và bộ ký tự.

  2. Nên xử lý cả chiều rộng và chiều cao

  3. Không cắt bớt trừ khi văn bản không thể phù hợp vì giới hạn, chúng tôi đã cung cấp cho nó (ví dụ: văn bản quá dài, kích thước có sẵn quá nhỏ). Tuy nhiên, chúng tôi có thể yêu cầu thanh cuộn ngang / dọc nếu muốn, chỉ cho những trường hợp đó.

  4. Nên cho phép nhiều dòng hoặc một dòng. Trong trường hợp đa dòng, cho phép dòng tối đa & tối thiểu.

  5. Không nên chậm trong tính toán. Sử dụng một vòng lặp để tìm kích thước tốt nhất? Ít nhất là tối ưu hóa nó và không tăng mẫu của bạn lên 1 lần.

  6. Trong trường hợp nhiều dòng, nên cho phép thay đổi kích thước hoặc sử dụng nhiều dòng hơn và / hoặc cho phép tự chọn các dòng bằng cách sử dụng ký tự "\ n".

Những gì tôi đã thử

Tôi đã thử rất nhiều mẫu (bao gồm cả các liên kết, tôi đã viết về) và tôi cũng đã cố gắng sửa đổi chúng để xử lý các trường hợp, tôi đã nói về, nhưng không có gì thực sự hiệu quả.

Tôi đã thực hiện một dự án mẫu cho phép tôi xem trực quan xem TextView có tự động khớp chính xác không.

Hiện tại, dự án mẫu của tôi chỉ ngẫu nhiên hóa văn bản (bảng chữ cái tiếng Anh cộng với chữ số) và kích thước của textView và để nó ở cùng một dòng, nhưng ngay cả điều này không hoạt động tốt trên bất kỳ mẫu nào tôi đã thử.

Đây là mã (cũng có sẵn ở đây ):

Tập tin 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>

Tập tin 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();
    }
  }

Câu hỏi

Có ai biết một giải pháp cho vấn đề phổ biến này thực sự hoạt động không?

Ngay cả một giải pháp có ít tính năng hơn những gì tôi đã viết, ví dụ như một giải pháp chỉ có số lượng dòng văn bản không đổi và điều chỉnh phông chữ theo kích thước của nó, nhưng không bao giờ có sự cố lạ và văn bản cũng bị lỗi lớn / nhỏ so với không gian có sẵn của nó.


Dự án GitHub

Vì đây là một TextView quan trọng như vậy, tôi đã quyết định xuất bản một thư viện, để mọi người có thể dễ dàng sử dụng và đóng góp cho nó ở đây .


Bạn đã cố gắng chưa? androidview.net/2012/12/autoscale-textview
Thrakbad

@Thrakbad đó là một trong những liên kết tôi đã đề cập. Nó cũng không vượt qua bài kiểm tra.
nhà phát triển Android

À xin lỗi, tôi đã bỏ lỡ ví dụ cuối cùng bằng cách nào đó
Thrakbad

Vâng, xin hãy tin tôi, tôi đã thử rất nhiều mẫu và tôi cũng đã thử sửa đổi chúng để khắc phục các vấn đề tôi tìm thấy, nhưng chưa bao giờ thành công. Nếu bạn tìm thấy một cái gì đó mà bạn nghĩ rằng có thể làm việc, xin vui lòng kiểm tra nó. Tôi đã đăng một mã mẫu chỉ cho việc này.
nhà phát triển Android

1
@rule đây là một trong những bài viết tôi đã đọc và tôi đã kiểm tra tất cả các mẫu mã. Ngoài ra tôi nghĩ rằng bạn đã đăng hai lần.
nhà phát triển Android

Câu trả lời:


147

Nhờ sửa chữa đơn giản MartinH của ở đây , mã này cũng sẽ chăm sóc của android:drawableLeft, android:drawableRight, android:drawableTopandroid:drawableBottomthẻ.


Câu trả lời của tôi ở đây sẽ khiến bạn hài lòng Tự động chia tỷ lệ văn bản TextView để vừa trong giới hạn

Tôi đã sửa đổi trường hợp thử nghiệm của bạn:

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

Tôi đang đăng mã ở đây theo yêu cầu của nhà phát triển Android :

Hiệu quả cuối cùng:

Nhập mô tả hình ảnh ở đây

Tệp bố cục mẫu:

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

Và mã 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();
        }
    }
}

Cảnh báo:

Cảnh giác với lỗi đã giải quyết này trong Android 3.1 (Honeycomb).


ok, tôi đã kiểm tra mã, và nó có vẻ tốt. tuy nhiên, bạn đã sử dụng getMaxLines (), dành cho API 16, nhưng bạn có thể lưu trữ maxLines và lấy nó bằng cách ghi đè setMaxLines và lưu trữ giá trị của nó. Tôi đã thay đổi và giờ nó hoạt động tốt. Ngoài ra, vì đôi khi văn bản có thể quá dài đối với kích thước nhỏ nhất, tôi đã thử sử dụng setEllipsize và điều đó cũng hiệu quả! Tôi nghĩ rằng chúng tôi có một người chiến thắng. xin vui lòng gửi mã của bạn ở đây để tôi có thể đánh dấu nó là đúng.
nhà phát triển Android

bạn đã không xóa getMaxLines () và sử dụng riêng của bạn. nó vẫn sẽ chỉ hoạt động từ API16 ... vui lòng thay đổi nó để mọi người có thể sử dụng nó. tôi đề nghị ghi đè setMaxLines để lưu tham số vào một trường và sau đó truy cập vào trường thay vì sử dụng getMaxLines.
nhà phát triển Android

kiểm tra ngay, thêm hỗ trợ cho các dòng tối đa.
M-WaJeEh

trông có vẻ ổn. bạn nên thêm "@TargetApi (Build.VERSION_CODES.JELLY_BESE)" vào phương thức onTestSize () để Lint sẽ không đưa ra lỗi / cảnh báo về nó và thay vào đó khởi tạo nó trong CTOR. thiết lập tìm kiếm nhị phân thành tĩnh cũng là một điều tốt và bạn có thể thực hiện khởi tạo trên CTOR lớn nhất và gọi nó từ các CTOR khác. tuy nhiên, đó thực sự là câu trả lời tốt nhất và bạn xứng đáng được điểm V. đây cuối cùng là một câu trả lời tốt cho câu hỏi cũ này làm tốt lắm!
nhà phát triển Android

@ M-WaJeEh kiểm tra bài mới trong chủ đề này. một người dùng được gọi là "MartinH" nói rằng anh ta đã sửa lỗi mã của bạn.
nhà phát triển Android

14

Tôi đã sửa đổi câu trả lời của M-WaJeEh một chút để tính đến các hợp đồng có thể rút được ở hai bên.

Các getCompoundPaddingXXXX()phương thức trở lại padding of the view + drawable space. Xem ví dụ: TextView.getCompoundPaddingLeft ()

Vấn đề: Điều này sửa lỗi đo chiều rộng và chiều cao của không gian TextView có sẵn cho văn bản. Nếu chúng ta không tính đến kích thước có thể vẽ được, nó sẽ bị bỏ qua và văn bản sẽ bị chồng chéo lên nhau.


Phân đoạn cập nhật 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));
}

bạn đã cập nhật nguồn của M-WaJeEh. Tại sao bạn lại đặt nó như một câu trả lời mới? Tôi biết bạn có thiện chí, nhưng nó có phải là một điều tốt để đặt nó như một câu trả lời? Tôi không chắc anh ấy có thể thấy thông báo câu trả lời của bạn ...
nhà phát triển Android

1
Tôi chỉ cần đăng ký để có thể cung cấp bản cập nhật đó. Số lượng danh tiếng của tôi chỉ là 1 khiến tôi không thể nhận xét về các bài đăng không thuộc về tôi (tối thiểu là 15).
MartinH

Đừng lo lắng, có lẽ bạn muốn chuyển câu trả lời của tôi cho M-WajeEh vì tôi thậm chí không thể liên lạc với anh ấy vào lúc này: /
MartinH

Bạn có thể cố gắng để mô tả tốt hơn những gì bạn đã làm? bạn đã sửa một lỗi chưa? bạn có thể hiển thị một ảnh chụp màn hình để chứng minh lỗi không?
nhà phát triển Android

1
Xin chào @MartinH, Chào mừng bạn đến với SO. Tôi chắc chắn sẽ xem xét điều này và sẽ cập nhật câu trả lời của tôi bằng cách đề cập đến tên của bạn và đăng liên kết đến bài đăng của bạn sau khi thử nghiệm . Chỉ cần cho tôi vài ngày. Tôi bị mắc kẹt ở một nơi khác ngày nay.
M-WaJeEh

7

Ok Tôi đã sử dụng các tuần trước để ồ ạt viết lại mã của tôi để chính xác phù hợp với thử nghiệm của bạn. Bây giờ bạn có thể sao chép 1: 1 này và nó sẽ ngay lập tức hoạt động - bao gồm setSingleLine(). Hãy nhớ điều chỉnh MIN_TEXT_SIZEMAX_TEXT_SIZEnếu bạn đang tìm kiếm các giá trị cực đoan.

Thuật toán hội tụ trông như thế này:

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

Và cả lớp có thể được tìm thấy ở đây.

Kết quả bây giờ rất linh hoạt. Điều này hoạt động tương tự khai báo trong xml như vậy:

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

... cũng như được xây dựng theo chương trình như trong bài kiểm tra của bạn.

Tôi thực sự hy vọng bạn có thể sử dụng điều này bây giờ. Bạn có thể gọi setText(CharSequence text)ngay bây giờ để sử dụng nó bằng cách này. Lớp học quan tâm đến các trường hợp ngoại lệ hiếm hoi và nên vững chắc. Điều duy nhất mà thuật toán chưa hỗ trợ là:

  • Gọi đến setMaxLines(x)đâux >= 2

Nhưng tôi đã thêm ý kiến ​​mở rộng để giúp bạn xây dựng điều này nếu bạn muốn!


Xin lưu ý:

Nếu bạn chỉ sử dụng điều này một cách bình thường mà không giới hạn nó trong một dòng thì có thể có từ ngắt như bạn đã đề cập trước đó. Đây là một tính năng của Android , không phải lỗi của AutoFitText. Android sẽ luôn ngắt các từ quá dài đối với TextView và nó thực sự khá tiện lợi. Nếu bạn muốn can thiệp vào đây hơn xin vui lòng xem các nhận xét và mã của tôi bắt đầu từ dòng 203. Tôi đã viết một phân chia đầy đủ và công nhận cho bạn, tất cả những gì bạn cần làm là từ đó là chia các từ và sau đó sửa đổi theo ý muốn .

Tóm lại: Bạn nên cân nhắc kỹ việc viết lại bài kiểm tra của mình để hỗ trợ các ký tự không gian, như vậy:

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

Điều này sẽ tạo ra kết quả rất đẹp (và thực tế hơn).
Bạn sẽ tìm thấy bình luận để giúp bạn bắt đầu trong vấn đề này là tốt.

Chúc may mắn và trân trọng


Có một số vấn đề, nhưng nhìn chung nó hoạt động rất tốt. các vấn đề: không hỗ trợ nhiều dòng (như bạn đã viết) và sẽ phá vỡ từ này trong trường hợp bạn cố gắng thực hiện, bao gồm chức năng API16 (addOnGlobalLayoutListener) mà tôi nghĩ có thể tránh được hoàn toàn (hoặc ít nhất là sử dụng OnPreDrawListener) tìm kiếm nhị phân để nó có thể kiểm tra kích thước mà nó hoàn toàn không phù hợp (và sử dụng nhiều bộ nhớ hơn mà không có lý do, điều này có thể gây ra ngoại lệ nếu quá lớn), không hỗ trợ xử lý khi có bất cứ điều gì thay đổi (như chính văn bản).
nhà phát triển Android

Tuy nhiên, bạn không phải sử dụng biến mTestView và thực hiện kiểm tra trực tiếp trên chế độ xem hiện tại và theo cách này, bạn sẽ không phải xem xét tất cả những điều đặc biệt. Ngoài ra, tôi nghĩ rằng thay vì MAX_TEXT_SIZE bạn có thể sử dụng targetFieldHeight.
nhà phát triển Android

Tôi nghĩ rằng thay vì tìm kiếm nhị phân thuần túy (đặc biệt là 2 trong số đó), bạn có thể đoán chính xác hơn bằng tỷ lệ của mục tiêu so với kích thước đo được. cũng có những phương pháp khác (newton và những thứ khác mà tôi không nhớ) nhưng tôi không nghĩ chúng phù hợp ở đây. cái tôi đề nghị bây giờ tốt hơn vì bạn có thể bắt đầu từ kích thước tối thiểu thay vì kiểm tra kích thước tối đa. Chính xác là bạn sẽ không cần kích thước tối đa vì bạn tiếp tục đoán tốt hơn.
nhà phát triển Android

Tôi cũng nghĩ rằng 2 tìm kiếm nhị phân có thể được hợp nhất thành một dòng khi điều kiện có thể đơn giản là: if (getMeasuredWidth ()> = targetFieldWidth || getMeasuredHeight ()> = targetFieldHeight)
nhà phát triển Android

1
một vấn đề khác tôi đã tìm thấy là trọng lực văn bản. Tôi nghĩ rằng nó không thể được tập trung theo chiều dọc.
nhà phát triển Android

4

Yêu cầu của tôi là

  • Nhấp vào ScalableTextView
  • Mở listActivity và hiển thị các mục chuỗi có độ dài khác nhau.
  • Chọn một văn bản từ danh sách.
  • Đặt văn bản trở lại ScalableTextView trong một hoạt động khác.

Tôi đã giới thiệu liên kết: Tự động chia tỷ lệ Văn bản Xem văn bản cho phù hợp trong giới hạn (bao gồm cả nhận xét) và DialogTitle.java

Tôi thấy rằng giải pháp được cung cấp là tốt và đơn giản nhưng nó không tự động thay đổi kích thước của hộp văn bản. Nó hoạt động tuyệt vời khi độ dài văn bản được chọn từ chế độ xem danh sách có kích thước lớn hơn chiều dài văn bản hiện có trong ScalableTextView . Khi chọn văn bản có độ dài nhỏ hơn văn bản hiện có ScalableTextView, nó không làm tăng kích thước của văn bản, hiển thị văn bản ở kích thước nhỏ hơn.

Tôi đã sửa đổi ScalableTextView.java để điều chỉnh kích thước văn bản dựa trên độ dài văn bản. Đây là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);
            }
        }
    }
}
}

Mã hóa hạnh phúc ....


Bạn không nên làm điều đó bằng cách sử dụng tìm kiếm nhị phân thay vì giảm từng cái cho mỗi lần lặp? Ngoài ra, dường như mã này buộc TextView phải có một dòng văn bản, thay vì cho phép nó có nhiều dòng.
nhà phát triển Android

Đây là giải pháp duy nhất hiệu quả với tôi. Vấn đề của tôi liên quan đến TExtView không phù hợp với chế độ xem chỉ trong 4.1
FOliveira

có vẻ như nó không khớp với cha mẹ 100%. xem ảnh chụp màn hình: s14.postimg.org/93c2xgs75/Sc Muff_2.png
user25

4

Tôi sẽ giải thích cách hoạt động của thuộc tính này các phiên bản Android thấp hơn từng bước:

1- Nhập thư viện hỗ trợ android 26.xx vào tệp lớp dự án của bạn. Nếu không có thư viện hỗ trợ trên IDE, họ sẽ tự động tải xuống.

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- Mở tệp XML bố cục của bạn và cấu trúc lại như thẻ TextView của bạn. Kịch bản này là: khi kích thước phông chữ được khắc trên hệ thống, điều chỉnh văn bản phù hợp với chiều rộng có sẵn, không phải từ bao.

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

Việc triển khai TextView tự động phù hợp của họ không hoạt động tốt mặc dù. Đôi khi, thay vì thay đổi kích thước kích thước phông chữ, văn bản chỉ chuyển sang dòng tiếp theo, thật tệ ... Nó thậm chí còn được hiển thị trên video của họ, như thể đó là một điều tốt: youtu.be/fjUdJ2aVqE4?t=14 . Lưu ý cách "w" của "TextView" chuyển đến dòng tiếp theo ... Dù sao, đã tạo một yêu cầu mới về nó ở đây: suetracker.google.com/issues/68787108 . Hãy xem xét đánh dấu sao.
nhà phát triển Android

@androiddeveloper Bạn nói đúng. Tôi đã không giải thích việc thực hiện thẻ này hoạt động một dòng. Nếu bạn cần các dòng bọc, bạn phải thay đổi thuộc tính này: android: layout_height = "quấn_content". Nhưng android.support.v7.widget.AppCompatTextView và bảo đảm thực hiện gradle hoạt động thuộc tính này.
Sinan Ergin

Tôi không nghĩ bạn hiểu. Tôi không muốn các từ bán phần được gói sang dòng tiếp theo, trừ khi không có cách nào khác để giải quyết nó. Trong video, bạn có thể dễ dàng thấy các phông chữ có thể đã nhỏ hơn thay vào đó. Đó là vấn đề. Bạn đang nói rằng bạn biết làm thế nào để sửa chữa nó? Có nghĩa là sẽ không có kết thúc từ và chỉ thay đổi kích thước phông chữ?
nhà phát triển Android

@androiddeveloper Có nhiều kịch bản. Kịch bản của tôi là: văn bản tự động sửa thành cố định và không bao bọc từ. Trong quá trình kích thước phông chữ của hệ thống, văn bản phải hiển thị không bao bọc từ trong textview. Nếu gói từ không quan trọng với bạn, đừng bận tâm đặt chiều cao cố định. Thuộc tính này không chỉ sử dụng gói từ, bạn có thể tự động xác định văn bản có chiều rộng cố định TextView.
Sinan Ergin

Theo thử nghiệm của tôi, kích thước phông chữ không thay đổi chính xác và vấn đề bao bọc từ có thể xảy ra.
nhà phát triển Android

3

Cảnh báo, lỗi trong Android 3 (Honeycomb) và Android 4.0 (Ice Cream Sandwich)

Các phiên bản Android: 3.1 - 4.04 có một lỗi, đó là setTextSize () bên trong TextView chỉ hoạt động lần đầu tiên (lần gọi đầu tiên).

Lỗi được mô tả trong Sự cố 22493: Lỗi chiều cao TextView trong Android 4.0Sự cố 17343: Chiều cao và văn bản của nút không trở về trạng thái ban đầu sau khi tăng và giảm kích thước văn bản trên HoneyComb .

Cách giải quyết là thêm một ký tự dòng mới vào văn bản được gán cho TextView trước khi thay đổi kích thước:

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

Tôi sử dụng nó trong mã của tôi như sau:

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

Tôi thêm ký tự "\ u3000" này ở bên trái và bên phải văn bản của mình để giữ nó ở giữa. Nếu bạn có nó được căn chỉnh sang trái thì chỉ thêm vào bên phải. Tất nhiên nó cũng có thể được nhúng với tiện ích AutoResizeTextView, nhưng tôi muốn giữ mã sửa lỗi bên ngoài.


bạn có thể vui lòng hiển thị chính xác trên dòng nào (trước / sau dòng nào) và trong chức năng nào không?
nhà phát triển Android

được rồi cảm ơn. Tôi hy vọng điều này sẽ sửa chữa mọi thứ, và bất cứ ai đọc nó sẽ thấy nó hữu ích. Hiện tại tôi không thể kiểm tra nó. có lẽ lần sau tôi sẽ sử dụng nó
nhà phát triển Android

Mã này được gọi từ bên ngoài mã của khung nhìn. Bạn có biết có lẽ là một cách thay thế tốt để xử lý việc này bên trong mã của khung nhìn không?
nhà phát triển Android

bạn có thể ghi đè textView.setText () và đặt mã này vào bên trong lớp TextView
Malachiasz

Điều đó cũng không có nghĩa là "getText ()" sẽ trả về văn bản có thêm các ký tự?
nhà phát triển Android

3

Bây giờ có một giải pháp chính thức cho vấn đề này. Tự động hiển thị TextViews được giới thiệu với Android O có sẵn trong Thư viện hỗ trợ 26 và tương thích ngược hoàn toàn với Android 4.0.

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

Tôi không chắc tại sao https://stackoverflow.com/a/42940171/47680 cũng bao gồm thông tin này đã bị xóa bởi một quản trị viên.


1
Vâng, tôi cũng đã nghe về nó, nhưng nó tốt như thế nào? Có mẫu nào sử dụng không? Ngay cả trong video của họ, tôi đã nhận thấy một lỗi: một lá thư từ một từ được gửi đến dòng sau (được bao bọc): youtu.be/1N9KveJ-FU8?t=781 (chú ý "W")
nhà phát triển Android

3

Từ tháng 6 năm 2018, Android chính thức hỗ trợ tính năng này cho Android 4.0 (API cấp 14) trở lên.
Với Android 8.0 (API cấp 26) trở lên:

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

Các phiên bản Android trước Android 8.0 (API cấp 26):

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

Kiểm tra câu trả lời chi tiết của tôi .


1

Chuyển đổi chế độ xem văn bản thành hình ảnh và chia tỷ lệ hình ảnh trong ranh giới.

Dưới đây là một ví dụ về cách chuyển đổi chế độ xem thành Hình ảnh: Chuyển đổi chế độ xem sang Bitmap mà không hiển thị trong Android?

Vấn đề là, văn bản của bạn sẽ không thể chọn được, nhưng nó nên thực hiện thủ thuật. Tôi đã không thử nó, vì vậy tôi không chắc nó sẽ trông như thế nào (vì tỷ lệ).


Đó là một ý tưởng hay, nhưng điều này cũng sẽ làm cho văn bản được pixel hóa và loại bỏ bất kỳ tính năng nào tôi có thể sử dụng với textView.
nhà phát triển Android

0

Dưới đây là TextView avalancha với chức năng bổ sung cho Phông chữ tùy chỉnh.

Sử dụng:

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

Đừng quên thêm: xmlns: foo = "http://schemas.android.com/apk/res-auto". Phông chữ nên có trong tài sản cứu hỏa

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

lỗi đã biết: Không hoạt động với Android 4.03 - phông chữ là vô hình hoặc rất nhỏ (avalancha ban đầu cũng không hoạt động) bên dưới là cách khắc phục cho lỗi đó: https://stackoverflow.com/a/21851239/2075875


0

Thử cái này

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

Tôi không nghĩ rằng nó sẽ hoạt động, vì mỗi lần bạn thay đổi kích thước văn bản, nó sẽ khiến bản vẽ chỉ sau khi bạn kết thúc với khối (có nghĩa là nó sẽ được thêm vào hàng đợi sự kiện). Điều này có nghĩa là vòng lặp sẽ chỉ hoạt động một lần nhiều nhất mỗi lần văn bản thay đổi và không đảm bảo rằng số lượng dòng sẽ không vượt quá 3 dòng văn bản.
nhà phát triển Android

0

Nếu bạn đang tìm kiếm một cái gì đó dễ dàng hơn:

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

Nó có vẻ rất ngắn, nhưng nó có thể vượt qua bài kiểm tra mà tôi đã chuẩn bị không? Ngoài ra, tôi không nhận được khi chức năng này được gọi.
nhà phát triển Android

Không, đó là một gợi ý. Tôi sẽ cải thiện nó để nó có thể vượt qua bài kiểm tra của bạn càng sớm càng tốt.
Sander

Chà, nếu đó là một gợi ý, bạn có thể vui lòng đăng nó trực tiếp lên Github không?
nhà phát triển Android

0

Khắc phục nhanh sự cố được mô tả bởi @Malachiasz

Tôi đã khắc phục sự cố bằng cách thêm hỗ trợ tùy chỉnh cho vấn đề này trong lớp tự động thay đổi kích thước:

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

Chỉ cần gọi yourView.setTextCompat(newTextValue)thay vìyourView.setText(newTextValue)


Tại sao tạo một chức năng mới của setTextCompat, thay vì ghi đè setText? Ngoài ra, vấn đề nào?
nhà phát triển Android

setText () là một phương thức cuối cùng của TextView, do đó bạn sẽ không thể ghi đè lên nó. Một tùy chọn khác là làm điều này bên ngoài TextView tùy chỉnh, nhưng giải pháp này là để sử dụng nó bên trong TextView.
Irika Negru

Không chính xác. Một số "setText" là riêng tư, một số là công khai và một số công khai không phải là cuối cùng. Dường như hầu hết có thể được xử lý bằng cách ghi đè void void setText (văn bản CharSequence, loại BufferType). Có một chức năng cuối cùng mà tôi không biết mục đích của nó là: công khai void void setText (char [] text, int start, int len). Có lẽ có cái nhìn sâu hơn về mã.
nhà phát triển Android

Tất cả các biến thể của setText () thực sự sẽ gọi phương thức setText (văn bản CharSequence) ở cuối. Nếu bạn muốn đảm bảo hành vi tương tự, bạn sẽ cần ghi đè phương thức đó, nếu không, tốt hơn hết là chỉ cần thêm phương thức setText () tùy chỉnh của riêng bạn.
Irika Negru

Có, nhưng có lẽ đối với hầu hết các trường hợp, nó ổn.
nhà phát triển Android

0

Thử thêm LayoutParamsMaxWidthMaxHeightđến TextView. Nó sẽ buộc bố cục phải tôn trọng container cha mẹ và không tràn.

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

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

Tôi không chắc bạn đang nói về cái gì Bạn có đề nghị thêm phần này vào mẫu textView để nó không gặp phải vấn đề nhỏ mà tôi thường thấy không? Nếu vậy, tại sao bạn không đăng nó trên trang web github?
nhà phát triển Android

0

Kể từ Android O, có thể tự động thay đổi kích thước văn bản trong 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 cho phép bạn hướng dẫn TextView để kích thước văn bản tự động mở rộng hoặc co lại để lấp đầy bố cục dựa trên các đặc điểm và ranh giới của TextView. Cài đặt này giúp dễ dàng tối ưu hóa kích thước văn bản trên các màn hình khác nhau với nội dung động.

Thư viện hỗ trợ 26.0 Beta cung cấp hỗ trợ đầy đủ cho tính năng TextView tự động hóa trên các thiết bị chạy phiên bản Android trước Android O. Thư viện cung cấp hỗ trợ cho Android 4.0 (API cấp 14) trở lên. Gói android.support.v4.widget chứa lớp TextViewCompat để truy cập các tính năng theo kiểu tương thích ngược.


Đáng buồn thay, theo các thử nghiệm của tôi và ngay cả trong video của Google IO, tôi đã nhận thấy nó có vấn đề, chẳng hạn như gói sai các từ, thay vì thay đổi kích thước phông chữ. Tôi đã báo cáo về vấn đề này ở đây: suetracker.google.com/issues/38468964 và đây là lý do tại sao tôi vẫn không sử dụng nó.
nhà phát triển Android

0

Sau khi tôi dùng thử Android Autosizing TextView chính thức , tôi đã tìm thấy nếu phiên bản Android của bạn trước Android 8.0 (API cấp 26), bạn cần sử dụng android.support.v7.widget.AppCompatTextViewvà đảm bảo phiên bản thư viện hỗ trợ của bạn cao hơn 26.0.0. Thí dụ:

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

cập nhật :

Theo trả lời của @ android-developers, tôi kiểm tra AppCompatActivitymã nguồn và tìm thấy hai dòng này trongonCreate

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

AppCompatDelegateImpl'screateView

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

nó sử dụng AppCompatViewInflaterkhung nhìn Inflater, khi tạo, AppCompatViewInflaternó sẽ sử dụng AppCompatTextView cho "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;
    ...
}

Trong dự án của tôi tôi không sử dụng AppCompatActivity, vì vậy tôi cần sử dụng <android.support.v7.widget.AppCompatTextView>trong xml.


1
Trong hầu hết các trường hợp, Inflater sử dụng AppCompatTextView khi bạn chỉ viết "TextView", vì vậy bạn không phải làm điều đó.
nhà phát triển Android

@androiddeveloper Nhưng khi tôi sử dụng TextView, tự động hóa không hoạt động.
Sydi.zh

@androiddeveloper Cảm ơn, lý do là tôi không sử dụng AppCompatActivity. AppCompatActivity sử dụng bố trí thổi phồng AppCompatViewInflater.
Sydi.zh

Tôi không hiểu
nhà phát triển Android
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.