Android进阶之路 - EditText输入字体自适应

遇到这么一个需求:"控件宽度有限,随着输入内容,动态修改字体大小",如果是你,只如何来实现?又有几种方式?

嗯,就是这么一个简单的需求,让我记录了俩篇blog

起初我曾尝试通过监听TextChanged + 字体自适应 的方式,来实现 输入字体自适应 ,但是效果并不理想 ,所以最终换了别的方式

该篇通过我所使用的几种方式,看看能否帮助大家,具体采用了以下几种方式,先简单介绍一下

  • 监听TextChanged,在一定规则内直接设置字体大小(字体过度不自然)
  • AutoAdjustSizeEditText 自定义控件(基本满足场景,不过单行可支持无线输入,可无限滑动)
  • AutoAdaptSizeEditText 控件借鉴于前者,稍加修改,用于满足某一场景(设置单行场景,超过固定宽度,则不可继续输入)

AutoAdjustSizeEditText、AutoAdaptSizeEditText 效果与布局引入

效果

布局引入

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.example.edittextdemo.AutoAdjustSizeEditText
        android:layout_width="230dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:hint="AutoAdjustSizeEditText"
        android:textSize="20sp"
        app:maxTextSize="30sp"
        app:minTextSize="15sp" />

    <com.example.edittextdemo.AutoAdaptSizeEditText
        android:layout_width="230dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"
        android:hint="AutoAdaptSizeEditText"
        android:singleLine="true"
        android:textSize="20sp"
        app:maxSize="30sp"
        app:minSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>

简单、直接、有点low

这种方式就想我说的使用起来很简单,唯一不足可能在于首先需要了解输入规则的要求,同时字体适应时会生硬一些(目前因为框架原因,我先使用了该方式)

因为框架原因,我直接提供伪代码用于各位借鉴吧

EditText - addTextChangedListener

通过规则,自行定义字体大小

splitties框架 的 EditText.setTextIfDifferent扩展函数,内部会自行设置焦点位置


AutoAdjustSizeEditText

我看了很多篇关于 EditText 输入字体自适应 Blog,大多好像都脱胎于早期这款 AutoAdjustSizeEditText 自定义控件,我直接将源码跑完后发现基本可以适用于大部分场景,其中有优点有不足(仅个人认为),但依旧不可否认可以从前辈的代码中学习和成长(为表尊重,源码不做任何修改)

适用大部分场景,如果对单行显示长度有限定或许不太满足

自定义属性(之前一直没记录过自定义属性的相关blog,等有时间我必须补充一篇)

xml 复制代码
    <!-- 文本自动调整大小显示自定义属性 -->
    <declare-styleable name="AutoAdjustTextSize">
        <attr name="minTextSize" format="dimension" />
        <attr name="maxTextSize" format="dimension" />
    </declare-styleable>

AutoAdjustSizeEditText

java 复制代码
package com.example.edittextdemo;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.EditText;
import android.widget.TextView;


/**
 * 自动调整字体文本输入框
 * 
 * @author 蒋庆意
 * @date 2015-11-4
 * @time 上午11:02:32
 */
@SuppressLint("AppCompatCustomView")
public class AutoAdjustSizeEditText extends EditText {
    /**
     * 默认文字字体大小最小值(单位:像素)
     */
    private static final float DEFAULT_TEXT_SIZE_MIN = 20;

    /**
     * 默认文字字体大小最大值(单位:像素)(貌似用不上)
     */
    @SuppressWarnings("unused")
    private static final float DEFAULT_TEXT_SIZE_MAX = 60;

    /**
     * 画笔(用来测量已输入文字的长度)
     */
    private Paint paint;

    /**
     * 文字字体大小最小值
     */
    private float minTextSize = 0;

    /**
     * 文字字体大小最大值
     */
    private float maxTextSize = 0;

    /**
     * 判断输入文本字体是否变小过
     */
    private boolean hasScaleSmall = false;

    public AutoAdjustSizeEditTextBefore(Context context) {
        super(context);
        paint = new Paint();
    }

    public AutoAdjustSizeEditTextBefore(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        //读取自定义属性, 获取设置的字体大小范围
        if (null != attrs) {
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AutoAdjustTextSize);
            if (null != array) {
                minTextSize = array.getDimension(R.styleable.AutoAdjustTextSize_minTextSize, DEFAULT_TEXT_SIZE_MIN);
                //如果未设置字体最大值,则使用当前字体大小作为最大值
                maxTextSize = array.getDimension(R.styleable.AutoAdjustTextSize_maxTextSize, this.getTextSize());
                //回收 TypedArray
                array.recycle();
            }
        }
        //未设置字体最小值,则使用默认最小值
        if (0 == minTextSize) {
            minTextSize = DEFAULT_TEXT_SIZE_MIN;
        }
        //未设置字体最大值,则使用当前字体大小作为最大值
        if (0 == maxTextSize) {
            //            maxTextSize = DEFAULT_TEXT_SIZE_MAX;
            maxTextSize = this.getTextSize();
        }
        //如果设置的值不正确(例如minTextSize>maxTextSize),则互换
        if (minTextSize > maxTextSize) {
            float minSize = maxTextSize;
            maxTextSize = minTextSize;
            minTextSize = minSize;
        }
        Log.d("AutoScaleSizeEditText",
                "minTextSize=" + String.valueOf(minTextSize));
        Log.d("AutoScaleSizeEditText",
                "maxTextSize=" + String.valueOf(maxTextSize));
    }

    @Override
    protected void onTextChanged(CharSequence text, int start,
                                 int lengthBefore, int lengthAfter) {
        // 根据需要调整字体大小
        adjustTextSize(this);
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // 根据需要调整字体大小
        if (w != oldw) {
            adjustTextSize(this);
        }
        super.onSizeChanged(w, h, oldw, oldh);
    }

    /**
     * 调整文本的显示
     */
    private void adjustTextSize(TextView textView) {
        if (null == textView) {
            //参数错误,不与处理
            return;
        }
        //已输入文本
        String text = textView.getText().toString();
        //已输入文本长度
        int textWidth = textView.getWidth();
        if (null == text || text.isEmpty() || textWidth <= 0) {
            return;
        }
        //获取输入框总的可输入的文本长度
        float maxInputWidth = textView.getWidth() - textView.getPaddingLeft() - textView.getPaddingRight();
        //获取当前文本字体大小
        float currentTextSize = textView.getTextSize();
        Log.d("AutoScaleSizeEditText","currentTextSize=" + String.valueOf(currentTextSize));
        //设置画笔的字体大小
        paint.setTextSize(currentTextSize);
        /*
         * 循环减小字体大小
         * 当  1、文本字体小于最大值
         *     2、可输入文本长度小于已输入文本长度
         * 时
         */
        while ((currentTextSize > minTextSize) && (maxInputWidth < paint.measureText(text))) {
            hasScaleSmall = true;
            Log.d("AutoScaleSizeEditText","TextSizeChange=" + String.valueOf(currentTextSize));
            --currentTextSize;
            if (currentTextSize < minTextSize) {
                currentTextSize = minTextSize;
                break;
            }
            //设置画笔字体大小
            paint.setTextSize(currentTextSize);
        }
        /*
         * 循环增大字体大小
         * 当  1、文本字体小于默认值
         *     2、可输入文本长度大于已输入文本长度
         * 时
         */
        while (hasScaleSmall && (currentTextSize < maxTextSize)
                && (maxInputWidth > paint.measureText(text))) {
            Log.d("AutoScaleSizeEditText",
                    "TextSizeChangeSmall=" + String.valueOf(currentTextSize));
            ++currentTextSize;
            if (currentTextSize > maxTextSize) {
                currentTextSize = maxTextSize;
                break;
            }
            //设置画笔字体大小
            paint.setTextSize(currentTextSize);
        }
        //设置文本字体(单位为像素px)
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, currentTextSize);
        Log.d("AutoScaleSizeEditText",
                "currentTextSize2=" + String.valueOf(currentTextSize));
    }
}

AutoAdaptSizeEditText

之所以修改原始 AutoAdjustSizeEditText 控件,主要是考虑到我当前场景为单行场景,且宽度固定,如果一直对输入内容不做限制,用户体验上可能不太好(感觉部分朋友应该也会遇到类似场景)

自定义属性(因为我Demo中这俩款自定义控件都用到了自定义属性;而自定义属性不可重复,所以这里命名稍有改变)

xml 复制代码
    <!-- 文本自动调整大小显示自定义属性 -->
    <declare-styleable name="AutoAdaptTextSize">
        <attr name="minSize" format="dimension" />
        <attr name="maxSize" format="dimension" />
    </declare-styleable>

AutoAdaptSizeEditText (感觉改的还行,不过还能优化一些写法,有时间再说吧)

java 复制代码
package com.example.edittextdemo;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.EditText;
import android.widget.TextView;


/**
 * 自动调整字体文本输入框,限制单行输入宽度
 *
 * @author ly
 * @date 2023
 */
@SuppressLint("AppCompatCustomView")
public class AutoAdaptSizeEditText extends EditText {
    /**
     * 默认文字字体大小最小值(单位:像素)
     */
    private static final float DEFAULT_TEXT_SIZE_MIN = 20;

    /**
     * 默认文字字体大小最大值(单位:像素)(貌似用不上)
     */
    @SuppressWarnings("unused")
    private static final float DEFAULT_TEXT_SIZE_MAX = 60;

    /**
     * 画笔(用来测量已输入文字的长度)
     */
    private Paint paint;

    /**
     * 文字字体大小最小值
     */
    private float minTextSize = 0;

    /**
     * 文字字体大小最大值
     */
    private float maxTextSize = 0;

    /**
     * 判断输入文本字体是否变小过
     */
    private boolean hasScaleSmall = false;

    /**
     * 可输出文本的最大长度
     */
    private int length = 0;
    /**
     * 可编辑状态
     */
    private boolean editState = false;


    public AutoAdaptTextSize(Context context) {
        super(context);
        paint = new Paint();
    }

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

    public void init(Context context, AttributeSet attrs) {
        paint = new Paint();
        editState = true;
        //读取自定义属性, 获取设置的字体大小范围
        if (null != attrs) {
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AutoAdaptTextSize);
            if (null != array) {
                minTextSize = array.getDimension(R.styleable.AutoAdaptTextSize_minSize, DEFAULT_TEXT_SIZE_MIN);
                //如果未设置字体最大值,则使用当前字体大小作为最大值
                maxTextSize = array.getDimension(R.styleable.AutoAdaptTextSize_maxSize, this.getTextSize());
                //回收 TypedArray
                array.recycle();
            }
        }
        //未设置字体最小值,则使用默认最小值
        if (0 == minTextSize) {
            minTextSize = DEFAULT_TEXT_SIZE_MIN;
        }
        //未设置字体最大值,则使用当前字体大小作为最大值
        if (0 == maxTextSize) {
            //            maxTextSize = DEFAULT_TEXT_SIZE_MAX;
            maxTextSize = this.getTextSize();
        }
        //如果设置的值不正确(例如minTextSize>maxTextSize),则互换
        if (minTextSize > maxTextSize) {
            float minSize = maxTextSize;
            maxTextSize = minTextSize;
            minTextSize = minSize;
        }
    }

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        // 根据需要调整字体大小
        autoAdaptTextSize(this);
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // 对比前后输入前后字体大小
        if (w != oldw) {
            autoAdaptTextSize(this);
        }
        super.onSizeChanged(w, h, oldw, oldh);
    }

    /**
     * 调整文本的显示
     */
    private void autoAdaptTextSize(TextView textView) {
        if (null == textView) {
            //参数错误,不与处理
            return;
        }
        //已输入文本
        String text = textView.getText().toString();
        //已输入文本长度
        int textWidth = textView.getWidth();
        if (text.isEmpty() || textWidth <= 0) {
            return;
        }
        //获取输入框总的可输入的文本长度
        float maxInputWidth = textView.getWidth() - textView.getPaddingLeft() - textView.getPaddingRight();
        //获取当前文本字体大小
        float currentTextSize = textView.getTextSize();
        Log.d("AutoScaleSizeEditText", "currentTextSize=" + String.valueOf(currentTextSize));
        //设置画笔的字体大小
        paint.setTextSize(currentTextSize);
        /*
         * 循环减小字体大小,条件如下
         * 1、文本字体小于最大值
         * 2、可输入文本长度小于已输入文本长度
         */
        while ((currentTextSize > minTextSize) && (paint.measureText(text) > maxInputWidth)) {
            Log.e("tag", "paint.measureText(text)=" + paint.measureText(text) + "maxInputWidth:" + maxInputWidth);
            hasScaleSmall = true;
            --currentTextSize;
            if (currentTextSize < minTextSize) {
                currentTextSize = minTextSize;
                break;
            }
            //设置画笔字体大小
            paint.setTextSize(currentTextSize);
        }
        /*
         * 循环增大字体大小,条件如下
         * 1、文本字体小于默认值
         * 2、可输入文本长度大于已输入文本长度
         */
        while (hasScaleSmall && (currentTextSize < maxTextSize) && (maxInputWidth > paint.measureText(text))) {
            ++currentTextSize;
            if (currentTextSize > maxTextSize) {
                currentTextSize = maxTextSize;
                break;
            }
            //设置画笔字体大小
            paint.setTextSize(currentTextSize);
        }

        /*
         * 限制输入,条件如下
         * 1、当前字体大小已经为我们设置的最小字体(兼容最小值)
         * 2、所有字体的宽度对比控件的最大宽度
         */
        Log.e("tag", "当前字体Size=" + currentTextSize + "最小字体Size:" + minTextSize);
        Log.e("tag", "字体宽度=" + paint.measureText(text) + "控件宽度:" + maxInputWidth);
        if (currentTextSize <= minTextSize && paint.measureText(text) > maxInputWidth) {
            Log.e("tag", "超过预设值,不支持继续输入");
            if (editState) {
                editState = false;
                length = text.length();
            }
            if (text.length() > length) {
                this.setText(text.substring(0, length));
                this.setSelection(length); //光标位于尾部
            }
            //最大可输出入字符数,限制输入的关键点
            this.setEms(length);
        } else {
            editState = true;
        }
        //设置文本字体(单位为像素px)
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, currentTextSize);
    }

}
相关推荐
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台5 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone5 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo5 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui