当前位置: 首页 > news >正文

android EditText设置后缀

有两种实现方案。
方案一:是自己写一个TextWatcher。
方案二:是重写TextView的getOffsetForPosition方法,返回一个计算好的offset。

我在工作时,使用的是方案一。在离职之后,我还是对这个问题耿耿于怀,所以才去看源码,最后想到了方案二。

但实际测试时,发现方案二存在一些问题,而且看了找了好久的源码,都不知道要重写哪个方法来解决,所以只能提供另一个半成品的方案二出来。

先看看两种方案的gif吧。
方案一
在这里插入图片描述
方案二
在这里插入图片描述
从方案一的gif图片可以看到,在输入文本之后,会在文本后面追加suffix text。如果在suffix text的范围内输入会删除,则会将这些操作传递到已输入的文本上。如果已输入的最后一个文本被删除,则会删除掉所有文本。

方案二则相对简单,输入后依然有suffix text,但suffix 的范围是不可以点击的。咋一看,这个方案好像很好,但我在实际测试时发现了一些问题,而且还不知道要怎么解决。

  • 长按EditText会出现全选的dialog,并且点击全选将选择suffix text。这个操作我认为是有问题的。既然suffix text没办法被selection,那全选就不应该将它包含进来
  • 双击suffix text,也会选择suffix text

我找了很久很久的源码,不断的debug,尝试定位到第一个问题和第二个问题调用的源码,并看看能否重写某些方法来改变其逻辑,但最终都无功而返。
在上面我也提到了,我是重写getOffsetForPosition方法实现了这个功能,所以我也尝试在这个方法上动手脚,但最终的效果不是很好。在模拟器上,如果是用了全选,会出现,先全选,再选回suffix text前面的文本。对于用户来说,两个动画同时出现,未免也太滑稽了吧。所以我最终没有采用这种方案。

无论使用哪种方案,在复制时都会将suffix text复制进来,想要解决这个问题也很简单,可以EditText并重写EditText的onTextContextMenuItem方法,并判断id是否为android.R.id.copy。如果是,就重写一下copy的逻辑。至于为什么我知道可以这样做,可以看一下这篇博客。

图也给了,也解释了图片里面发生了什么,下面贴一下代码,注释已经补在代码里面。
方案一

open class SuffixTextWatcher(private val editText: EditText, private val suffixText: String) : TextWatcher {private var lastText = ""override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {if (suffixText.isEmpty()) {return}editText.removeTextChangedListener(this)val preText = s.toString()val suffixText = suffixText// 如果输入的文本等于suffixText,就说明已经清空了输入的文本,那就直接设置为空字符串if (preText == suffixText) {lastText = ""editText.setText(lastText)} else {// 如果不是以suffixText结尾,则说明suffix已经遭到破坏,或者是还没有suffixTextif (preText.isNotEmpty() && preText.endsWith(suffixText).not()) {// 如果lastText等于空,就说明还没有suffixTextif (lastText.isEmpty()) {lastText = preText.plus(suffixText)editText.setText(lastText)editText.setSelection(preText.length)} else {// 如果执行到该else,就说明suffixText已经被破坏了val suffixTextStartIndex = lastText.length - suffixText.length// 如果两个文本的长度不相等if (lastText.length != preText.length) {// 获取上次已输入的字符串val lastInputText = lastText.substring(0, suffixTextStartIndex)// 如果lastText的长度小于preText的长度,就说明已经suffixText里面输入了字符// 这个if-else就是用于获取实际输入的字符val latestInputText = if (lastText.length < preText.length) {// 获取输入的字符,并拼接到上次已输入的字符串末尾val inputChar = preText.substring(start, start + 1)lastInputText.plus(inputChar)} else {// 如果不大于,那就是小于,因为已经在上面判断了两个不相等// 如果小于,就是将suffixText中的某个字符删除掉// 上次如果只输入了一个字符,本次就将输入的字符设置为空字符串if (lastInputText.length == 1) {""} else {// 否则就删掉最后一个字符lastInputText.substring(0, lastInputText.length - 1)}}// 如果上面获取到的最新输入文本不为空,就在后面追加suffixText// 否则就将lastText设置为空字符串lastText = if (latestInputText.isNotEmpty()) {latestInputText.plus(suffixText)} else ""editText.setText(lastText)if (lastText.isNotEmpty()) {editText.setSelection(latestInputText.length)}} else {// 这个else一般执行不到,但为了防止出现考虑不到的问题,还是简单处理一下if (start in suffixTextStartIndex until lastText.length) {editText.setText(lastText)editText.setSelection(suffixTextStartIndex)}}}} else {// 执行到该else,就说明suffixText没有遭到破坏// 如果用户没有乱来,一般就是执行到这里,但也有例外// 从gif图片可以看到,如果在su中间输入s,也是没有问题的,因为此时endWith为true,但这种情况下,selectionIndex是不正确的// 此时selectionIndex是在su中间,而不是在ss中间,所以下面的的代码就是处理这个问题// 处理的方式也很简单,获取suffixText的长度,并判断start是否在suffixText的范围内// 如果是,就将index设置到suffixText前面if(preText.isNotEmpty()) {val suffixTextStartIndex = preText.length - suffixText.lengthif (start >= suffixTextStartIndex) {editText.setSelection(suffixTextStartIndex)}}lastText = preText}}editText.addTextChangedListener(this)}override fun afterTextChanged(s: Editable?) {}}

方案二

class SuffixEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :AppCompatEditText(context, attrs) {// textWatcher用来补齐后缀private val textWatcher = SuffixTextWatcher(this)var suffixText = ""set(value) {val oldSuffix = fieldfield = valuetextWatcher.suffixText = valueupdateSuffixText(oldSuffix, value)}init {addTextChangedListener(textWatcher)if (suffixText.isNotEmpty()){textWatcher.suffixText = suffixText}}private fun updateSuffixText(oldSuffix: String, newSuffix: String) {val text = text ?: ""if (oldSuffix.isEmpty() || text.isEmpty()) {return}val oldSuffixIndex = text.lastIndexOf(oldSuffix)if (oldSuffixIndex != -1) {setText(text.substring(0, oldSuffixIndex))}}// 这里就是修改selectionIndext的代码,如果用户的touch行为导致selectionIndex发生变化// EditText就会调用这里获取index,所以只需重写该方法即可override fun getOffsetForPosition(x: Float, y: Float): Int {var superResult = super.getOffsetForPosition(x, y)val text = text ?: ""val suffixText = suffixTextif (text.isEmpty() || suffixText.isEmpty()) {return superResult}val textLength = text.length - suffixText.length// 如果index在suffixText的范围内,就设置为inputText的最大indexif (superResult >= textLength) {superResult = textLength}return superResult}override fun onTextContextMenuItem(id: Int): Boolean {// 如果不希望用户复制的内容包含suffix text,就可以重写该方法,并按照我上面提到的代码去做return super.onTextContextMenuItem(id)}private class SuffixTextWatcher(val editText: EditText) : TextWatcher {var suffixText: String = ""override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {val text = s?.toString() ?: ""if (text.isEmpty()) {return}editText.removeTextChangedListener(this)// 如果没有suffix text,就在最后追加suffix textif (text.endsWith(suffixText).not()) {if (text.isNotEmpty()) {editText.setText("$text$suffixText")editText.setSelection(text.length)}} else {if (text == suffixText) {editText.setText("")}}editText.addTextChangedListener(this)}override fun afterTextChanged(s: Editable?) {}}
}

相关文章:

android EditText设置后缀

有两种实现方案。 方案一&#xff1a;是自己写一个TextWatcher。 方案二&#xff1a;是重写TextView的getOffsetForPosition方法&#xff0c;返回一个计算好的offset。 我在工作时&#xff0c;使用的是方案一。在离职之后&#xff0c;我还是对这个问题耿耿于怀&#xff0c;所以…...

prometheus+cadvisor监控docker

官方解释 cAdvisor&#xff08;ContainerAdvisor&#xff09;为容器用户提供了对其运行容器的资源使用和性能特性的了解。它是一个正在运行的守护程序&#xff0c;用于收集、聚合、处理和导出有关正在运行的容器的信息。具体来说&#xff0c;它为每个容器保存资源隔离参数、历史…...

正演(1): 二维声波正演模拟程序(中心差分)Python实现

目录 1、原理&#xff1a; 1&#xff09;二维声波波动方程: ​编辑 2&#xff09;收敛条件&#xff08;不是很明白&#xff09; 3&#xff09;雷克子波 4&#xff09;二维空间衰减函数 5&#xff09;边界吸收条件 (不是很明白。。) 2、编程实现 1&#xff09;参数设置&…...

珠海数据智能监控器+SaaS平台 轻松实现SMT生产管控

数据智能监控器 兼容市面上99%的SMT设备 直接读取设备生产数据与状态&#xff0c;如&#xff1a;计划产出、实际产出、累计产出、停机、节拍、线利用率、直通率、停产时间、工单状态、OEE…… 产品功能价值 ◎ OEE不达标报警&#xff0c;一手掌握生产效能 ◎ 首检/巡检/成…...

习题22对前面21节的归纳总结

笨方法学python --习题22 Vi---Rum 于 2021-01-12 14:16:10 发布 python 习题22 这节内容主要是归纳总结 ex1.py 第一次学习 1.print&#xff1a;打印 2.# &#xff1a;是注释的意思&#xff0c;井号右边的内容不再执行 3.end"":,在句子结尾加上这个就不会再换行…...

使用Vite快速构建前端React项目

一、Vite简介 Vite是一种面向现代浏览器的一个更轻、更快的前端构建工具,能够显著提升前端开发体验。除了Vite外,前端著名的构建工具还有Webpack和Gulp。目前,Vite已经发布了Vite3,Vite全新的插件架构、丝滑的开发体验,可以和Vue3完美结合。 相比Webpack和Gulp等构建工具…...

人工智能高等数学--人工智能需要的数学知识_微积分_线性代数_概率论_最优化---人工智能工作笔记0024

然后我们看一下人工智能中需要的数学知识 数学知识是重要的,对于理解人工智能底层原理来说很重要,但是工作中 工作中一般都不会涉及的自己写算法之类的,只是面试,或者理解底层原理的时候需要 然后看一下人工智能需要哪些数学知识 这里需要微积分 线性代数 概率论 最优化的知识…...

阿里大数据之路总结

一、数据采集 二、数据同步 2.1、数据同步方式&#xff1a; 数据同步的三种方式&#xff1a;直连方式、数据文件同步、数据库日志解析方式 关系型数据库的结构化数据&#xff1a;MYSQL、Oracle、DB2、SQL Server非关系型数据库的非结构化数据&#xff08;数据库表形式存储&am…...

ABAP中Literals的用法(untyped literal vs. typed literal)

1. 什么是Literals ? Literals的字面意思即“文字”。其实&#xff0c;Literals就是在ABAP代码中直接指定的一个字符串&#xff0c;但注意哦&#xff0c;这个字符串并不意味着其类型一定是string哦。 要弄清这个概念&#xff0c;就要清楚ABAP对于Literals 的定义和处理方式。…...

tensorflow1.14.0安装教程

1首先电脑安装好Anaconda3&#xff08;Anaconda介绍、安装及使用教程 - 知乎 (zhihu.com)&#xff0c;&#xff09; 蟒蛇 |全球最受欢迎的数据科学平台 (anaconda.com) 2打开Anaconda Prompt&#xff08;本人更新win11后&#xff0c;主菜单不再显示&#xff0c;那么我们可以打…...

C++赋值运算符重载

赋值运算符重载 目录赋值运算符重载示例1&#xff1a;示例2&#xff1a;示例3&#xff1a;示例4&#xff1a;很巧妙的是&#xff0c;在编写这篇文章时&#xff08;2023年2月27日&#xff09;&#xff0c;再加100天就是6月7日&#xff0c;恰好是今年高考的百日誓师&#xff01; …...

网络性能总不好?专家帮你来“看看”— CANN 6.0 黑科技 | 网络调优专家AOE,性能效率双提升

随着深度学习模型复杂度和数据集规模的增大&#xff0c;计算效率的提升成为不可忽视的问题。然而&#xff0c;算法网络的多样性、输入数据的不确定性以及硬件之间的差异性&#xff0c;使得网络调优耗费巨大成本&#xff0c;即使是经验丰富的专家&#xff0c;也需要耗费数天的时…...

Qss自定义属性

QSS自定义属性 更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;QSS样式学习 &#x1f448;文章目录QSS自定义属性[toc]前言一、实现效果二、使用方式1.QSS设置Q_PROPERTY属性样式2.QSS设置动态属性样式3.qproperty-<属性名称>语法14.qproperty-&…...

连接金蝶云星空,数据交互轻松搞定!丨三叠云

金蝶云星空 路径 拓展 >> 插件 功能简介 新增插件「金蝶云星空」。 用户可通过配置「金蝶云星空」插件&#xff0c;就可以实时获取「金蝶云星空」的数据&#xff0c;同时支持回填数据至金蝶系统内。 地图视图 路径 表单 >> 表单设计 功能简介 新增「地图视…...

JSX是什么,React为什么使用JSX,babel怎么转译JSX的

JSX是什么&#xff0c;React为什么使用JSX&#xff0c;babel怎么转译JSX的 在前端的框架中有两种“描述UI”的方案&#xff0c;一种是JSX语法&#xff0c;一种是模板语言。 其中React就是选择的JSX&#xff0c;Vue就是选择的模板语言。 JSX其实就是一个语法糖&#xff0c;在…...

从工地转行软件测试,拿下13k+年终奖是种什么体验?

最近&#xff0c;一则名为《我&#xff1a;毕业五年&#xff0c;存款5000。她:中传硕士&#xff0c;火锅店保洁》的视频走红网络&#xff0c;两位名校毕业生看似高开低走的就业经历&#xff0c;引起了很多人的共鸣。她们所传达的并不是所谓的躺平、摆烂&#xff0c;而是希望更多…...

前端面试题 —— 计算机网络(二)

目录 一、POST和PUT请求的区别 二、GET方法URL长度限制的原因 三、页面有多张图片&#xff0c;HTTP是怎样的加载表现&#xff1f; 四、HTTP2的头部压缩算法是怎样的&#xff1f; 五、说一下HTTP 3.0 六、HTTP协议的性能怎么样&#xff1f; 七、数字证书是什么&#xff1f…...

山东大学机器学习期末2022

接力&#xff1a;山东大学机器学习期末2021 本来是不想写的&#xff0c;因为不想回忆起考试时啥也不会的伤痛&#xff0c;没想到最后给分老师海底捞&#xff0c;心情好了一些&#xff0c;还是一块写完 备考建议&#xff1a;多看ppt&#xff0c;多看ppt&#xff0c;多看ppt 山东…...

FEBC2022|打造VR内容生态闭环 佳创视讯持续加码轻量化内容建设

2月24日&#xff0c;由陀螺科技主办的未来商业生态链接大会作为 2023 癸卯兔年开年率先召开的行业重要影响力盛会在深圳成功召开。今年大会云集了科技、软件、游戏、XR等元宇宙领域的世界500强、上市公司及行业独角兽企业&#xff0c;围绕游戏、元宇宙、XR、数字营销等多项热门…...

Redis常见的数据类型命令

文章目录Redis 常见的数据类型及命令一、常见的NoSQL二、Redis 简介三、key 键的一些操作命令四、Redis的五种基本数据结构1、String&#xff08;字符串&#xff09;介绍常用命令1.1 set/get1.2 append1.3 strlen1.4 setex1.5 mset/mget1.6 setrange/getrange1.7 setnx1.8 incr…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

QT3D学习笔记——圆台、圆锥

类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

Ubuntu系统复制(U盘-电脑硬盘)

所需环境 电脑自带硬盘&#xff1a;1块 (1T) U盘1&#xff1a;Ubuntu系统引导盘&#xff08;用于“U盘2”复制到“电脑自带硬盘”&#xff09; U盘2&#xff1a;Ubuntu系统盘&#xff08;1T&#xff0c;用于被复制&#xff09; &#xff01;&#xff01;&#xff01;建议“电脑…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…...