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

}
相关推荐
拭心3 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王5 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡6 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道6 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库7 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道8 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe8 小时前
Android Hook - 动态加载so库
android
居居飒8 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He11 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗12 小时前
Android笔试面试题AI答之Android基础(1)
android