自动改变(文字大小)TextSize的TextView——autoSizeTextType

今天要实现一个自动改变TextSize的TextView,以下是我的艰辛历程,不感兴趣的直接跳到结尾获取最终答案

思路: 我的计划是在onSizeChange中获取TextView的宽高,计算当前TextSize占用的高度,再不断减小文字大小直到TextView可以放下,然后将TextSize设置给TextView

说干就干首先我继承了一个textView,然后通过简单的百度我知道了如下API

/**
     * Return the width of the text.
     *
     * @param text  The text to measure. Cannot be null.
     * @return      The width of the text
     */
    public float measureText(String text) {}

/**
     * Return the recommend line spacing based on the current typeface and
     * text size.
     *
     * <p>Note that this is the value for the main typeface, and actual text rendered may need a
     * larger value because fallback fonts may get used in rendering the text.
     *
     * @return  recommend line spacing based on the current typeface and
     *          text size.
     */
    public float getFontSpacing() {
        return getFontMetrics(null);
    }

说人话就是measureText可以测量文本的宽度,getFontSpacing可以获取推荐的行距

然后就有了下面"优雅的"代码

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        String text = getText().toString();
        setTextSize(getAutoTextSize(w,h,getTextSize(),text));
    }

    private float getAutoTextSize(int w, int h, float textSize,String text) {
        Paint paint = new Paint();
        paint.setTextSize(textSize);
        float textW = paint.measureText(text);
        float fontSpacing = paint.getFontSpacing();

        if ((textW / w) > h / fontSpacing) {
            return getAutoTextSize(w, h, --textSize, text);
        } else {
            return textSize;
        }
    }

正当我以为万事大吉的时候,真实的显示情况是這樣的

我来来回回的检查代码,最后在setTextSize的注释找到答案

/**
     * Set the default text size to the given value, interpreted as "scaled
     * pixel" units.  This size is adjusted based on the current density and
     * user font size preference.
     *
     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
     *
     * @param size The scaled pixel size.
     *
     * @attr ref android.R.styleable#TextView_textSize
     */
    @android.view.RemotableViewMethod
    public void setTextSize(float size) {
        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
    }

说人话就是android老哥贴心的为我们调用了另一个重载方法并且传递了一个TypedValue,TypedValue是什么呢,下面的注释给了答案

/**
     * Set the default text size to a given unit and value. See {@link
     * TypedValue} for the possible dimension units.
     *
     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
     *
     * @param unit The desired dimension unit.
     * @param size The desired size in the given units.
     *
     * @attr ref android.R.styleable#TextView_textSize
     */
    public void setTextSize(int unit, float size) {
        if (!isAutoSizeEnabled()) {
            setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
        }
    }

这里的还给了一个贴心的提示:"注意:如果此 TextView 启用了自动调整大小功能,则此功能无操作。"这个贴士很重要,后面会提到

TypedValue被称作维度单位,说白了就是一个单位.直接调用setTextSize方法的话,默认的单位是sp,那我改一下调用的方法总好了吧

我天,文字小是小了,可是并没有显示全,这时候仔细回想一下我的思路就会发现,这思路太理想化了,我们通过measureText获取的值是一行展示的时候的宽度,然而实际展示的时候TextView会在行末根据最后的字符占用宽度进行判断和换行,所以就很容易出现图中的这种情况.

这种时候作为一个优秀的搬砖工,当然是去找找看有没有轮子-----借鉴一下思路咯

然后惊喜的发现了textView的属性 autoSizeTextType , 原来android已经贴心的增加了这样的API

感觉又相信爱了

不过autoSizeTextType的使用还是有一些需要注意的点哦

1. 在Android 8.0 (API level 26) 以上,才可以使用这个属性哦,不过通过万能的拓展库可以最低支持到Android 4.0(API Level 14)及以上的系统(不过你的应用编译的targetSDKVersion必须在26及以上)。

2. 还记得上面setTextSize的注释吗,里面有一个小贴士哦,如果设置了自动大小功能,setTextSize将不起作用了,textSize的大小会根据textView的宽高自动适配

3. 在使用autoSizeTextType属性的时候宽高属性最好是固定的参数,或者设置maxHeightmaxWidth限制一下控件的宽高

4. 如果只设置了autoSizeTextType属性的话,默认的textSize是12sp~112sp,粒度为1px

知其然也要知其所以然,来看一下他是怎么实现的吧,很简单的哦,你忍一下

/**
     * Performs a binary search to find the largest text size that will still fit within the size
     * available to this view.
     */
    private int findLargestTextSizeWhichFits(RectF availableSpace) {
        final int sizesCount = mAutoSizeTextSizesInPx.length;
        if (sizesCount == 0) {
            throw new IllegalStateException("No available text sizes to choose from.");
        }

        int bestSizeIndex = 0;
        int lowIndex = bestSizeIndex + 1;
        int highIndex = sizesCount - 1;
        int sizeToTryIndex;
        while (lowIndex <= highIndex) {
            sizeToTryIndex = (lowIndex + highIndex) / 2;
            if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
                bestSizeIndex = lowIndex;
                lowIndex = sizeToTryIndex + 1;
            } else {
                highIndex = sizeToTryIndex - 1;
                bestSizeIndex = highIndex;
            }
        }

        return mAutoSizeTextSizesInPx[bestSizeIndex];
    }

    private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
        final CharSequence text = mTransformed != null
                ? mTransformed
                : getText();
        final int maxLines = getMaxLines();
        if (mTempTextPaint == null) {
            mTempTextPaint = new TextPaint();
        } else {
            mTempTextPaint.reset();
        }
        mTempTextPaint.set(getPaint());
        mTempTextPaint.setTextSize(suggestedSizeInPx);

        final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
                text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));

        layoutBuilder.setAlignment(getLayoutAlignment())
                .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                .setIncludePad(getIncludeFontPadding())
                .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
                .setBreakStrategy(getBreakStrategy())
                .setHyphenationFrequency(getHyphenationFrequency())
                .setJustificationMode(getJustificationMode())
                .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                .setTextDirection(getTextDirectionHeuristic());

        final StaticLayout layout = layoutBuilder.build();

        // Lines overflow.
        if (maxLines != -1 && layout.getLineCount() > maxLines) {
            return false;
        }

        // Height overflow.
        if (layout.getHeight() > availableSpace.bottom) {
            return false;
        }

        return true;
    }

可以看到findLargestTextSizeWhichFits方法就是计算TextSize的方法了,大神的代码就是优雅呢,使用了二分算法来找最合适的size

最重要的来咯, suggestedSizeFitsInSpace方法,他是用来判断当前testSize是否可以在textView中完美展示的

可以看到大神使用了StaticLayout,通过StaticLayout的getHeight()方法获取了当前testSize下占用的高度然后和展示区域的底部进行比较

ps: 还不了解StaticLayout的小伙伴,只要知道他是一个继承自Layout的可以实现自动换行的文本的布局

总结: 通过上面的源码学习可以发现,自由缩放功能是使用StaticLayout来计算占用的高度,然后不断的放大或缩小TextSize来实现的.android的源码中真是藏着很多的宝藏呢.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值