繁体   English   中英

如何使用自定义字体在TextView中容纳多行文本?

[英]How to fit multiline text inside a TextView using a custom font?

我正在使用API 10

我坚持了好几天。 我搜索了stackoverflow并尝试了很多类。

我想用默认字体将多行文本放入TextView边界内。 我使用下面的类,它不仅使一台设备运行,而且在所有设备上发挥了作用。 一模一样。 代码是这样的,我在stackoverflow中找到了它:

/**
 * 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
 */
@SuppressWarnings("unused")
public class AutoFitText extends TextView {

    /** 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 final 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);

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

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

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

            @SuppressWarnings("deprecation")
            @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().removeGlobalOnLayoutListener(this);
            }
        });
    }

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

文本会根据需要缩小,以适应TextView边界。

但是现在我想使用自定义字体。 我导入了我的自定义字体,一切都出错了。 如您在下图中所看到的,它不再适合。 文本不适合缩小。

自定义字体自动修复问题

那么,有人知道如何处理吗? 自定义字体是否遇到过类似的问题?

请信誉良好的人启用我的形象。

您还需要将当前字体设置为testTextView。 这解决了我的问题。

private void refitText(String text, int targetFieldWidth, int targetFieldHeight) {

        //
        // Bla bla bla codes
        //

        //
        // THIS IS THE FIX
        // Put the current typeface of textview to the test-textview
        mTestView.setTypeface(getTypeface());

        // 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) {

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM