Android简单支持项目符号的EditText

一、背景及样式效果      

因项目需要,需要文本编辑时,支持项目符号(无序列表)尝试了BulletSpan,但不是很理想,并且考虑到影响老版本回显等因素,最终决定自定义一个BulletEditText。

        先看效果:

        

视频效果

二、自定义View BulletEditText        

        自定义控件BulletEditText源码:

package com.ml512.widgetimport android.content.Context
import android.util.AttributeSet
import androidx.core.widget.doOnTextChanged/*** @Description: 简单支持项目号的文本编辑器* @Author: Marlon* @CreateDate: 2024/2/1 17:44* @UpdateRemark: 更新说明:* @Version: 1.0*/
class BulletEditText : androidx.appcompat.widget.AppCompatEditText {/*** 是否开启项目符号*/private var isNeedBullet: Boolean = false/*** 项目符号*/private var bulletPoint: String = "• "/*** 项目符号占用字符数,方便设置光标位置*/private var bulletOffsetIndex = bulletPoint.length/*** 相关监听回调*/private var editListener: EditListener? = nullconstructor(context: Context) : super(context)constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr)init {this.doOnTextChanged { text, start, before, count ->//如果是关闭状态不做格式处理if (!isNeedBullet) {return@doOnTextChanged}if (count > before) {//处理项目号逻辑var offset = 0var tmp = text.toString()//连续回车去掉项目符号if (start >= bulletOffsetIndex && tmp.substring(start, start + count) == "\n") {val preSub = tmp.substring(start - bulletOffsetIndex, start)if (preSub == bulletPoint) {changeBulletState(false)tmp = tmp.replaceRange(start-bulletOffsetIndex, start + count, "")offset -= bulletOffsetIndex + 1setTextAndSelection(tmp, start + count + offset)return@doOnTextChanged}}//加入项目符号if (tmp.substring(start, start + count) == "\n") {changeBulletState(true)tmp = tmp.replaceRange(start, start + count, "\n$bulletPoint")offset += bulletOffsetIndexsetTextAndSelection(tmp, start + count + offset)}}}}override fun onSelectionChanged(selStart: Int, selEnd: Int) {super.onSelectionChanged(selStart, selEnd)//复制选择时直接返回,关闭项目符号if (selStart != selEnd) {changeBulletState(false)return}//判断当前段落是否有项目号,有开启,没有关闭val tmp = text.toString()val prefix = tmp.substring(0, selectionStart)if (prefix.isEmpty()) {changeBulletState(false)return}if (prefix.startsWith(bulletPoint) && !prefix.contains("\n")) {changeBulletState(true)return}val lastEnterIndex = prefix.lastIndexOf("\n")if (lastEnterIndex != -1 && lastEnterIndex + bulletOffsetIndex + 1 <= prefix.length) {val mathStr = prefix.substring(lastEnterIndex, lastEnterIndex + bulletOffsetIndex + 1)if (mathStr == "\n$bulletPoint") {changeBulletState(true)return}}changeBulletState(false)}/*** 更新bullet状态*/private fun changeBulletState(isOpen: Boolean) {isNeedBullet = isOpeneditListener?.onBulletStateChange(isOpen)}/*** 设置是否开启项目号*/fun setBullet(isOpen: Boolean) {isNeedBullet = isOpenval tmp = text.toString()var index = selectionStartvar prefix = tmp.substring(0, index)val suffix = tmp.substring(index)//加项目号if (isOpen) {//首个段落if (!prefix.contains("\n") && prefix.startsWith(bulletPoint)) {return}index += bulletOffsetIndexif (prefix.isEmpty() || (!prefix.contains("\n") && !prefix.startsWith(bulletPoint))) {setTextAndSelection("$bulletPoint$prefix$suffix", index)return}prefix = prefix.replaceLast("\n", "\n$bulletPoint")setTextAndSelection("$prefix$suffix", index)return}//去掉项目号if (prefix.startsWith(bulletPoint) && !prefix.contains("\n$bulletPoint")) {//首行逻辑index -= bulletOffsetIndexprefix = prefix.replaceLast(bulletPoint, "")setTextAndSelection("$prefix$suffix", index)return}if (prefix.contains("\n$bulletPoint")) {index -= bulletOffsetIndexprefix = prefix.replaceLast("\n$bulletPoint", "\n")setTextAndSelection("$prefix$suffix", index)}}/*** 设置文本及光标位置*/private fun setTextAndSelection(text: String, index: Int) {setText(text)setSelection(index)}/*** 替换最后一个字符*/private fun String.replaceLast(oldValue: String, newValue: String): String {val lastIndex = lastIndexOf(oldValue)if (lastIndex == -1) {return this}val prefix = substring(0, lastIndex)val suffix = substring(lastIndex + oldValue.length)return "$prefix$newValue$suffix"}/*** 设置监听*/fun setEditListener(listener: EditListener) {editListener = listener}/*** 监听回调*/interface EditListener {/*** 项目符号开关状态变化*/fun onBulletStateChange(isOpen: Boolean)}
}

三、调用

        使用时一个项目符号的按钮开关设置调用setBullet(isOpen: Boolean) 设置是否开启项目符号,同时实现一个setEditListener(listener: EditListener)根据光标位置判断当前段落是否含有项目符号,并回显按钮状态。

 <com.ml512.widget.BulletEditTextandroid:id="@+id/etInput"android:layout_width="match_parent"android:layout_height="200dp"android:layout_below="@+id/tvTitle"android:layout_marginStart="15dp"android:layout_marginTop="15dp"android:layout_marginEnd="15dp"android:layout_marginBottom="15dp"android:autofillHints="no"android:background="@drawable/shape_edit_bg"android:gravity="top"android:hint="@string/text_please_input_some_worlds"android:inputType="textMultiLine"android:padding="15dp"android:textColor="@color/black"android:textColorHint="@color/color_FF_999999"android:textSize="16sp" />
        //点击按钮设置添加/取消项目符号tvBullet.setOnClickListener {tvBullet.isSelected = !tvBullet.isSelectedetInput.setBullet(tvBullet.isSelected)}//项目符号状态监听,回显到按钮etInput.setEditListener(object :BulletEditText.EditListener{override fun onBulletStateChange(isOpen: Boolean) {tvBullet.isSelected = isOpen}})

大功告成!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/666721.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

异步解耦之RabbitMQ(二)_RabbitMQ架构及交换机

异步解耦之RabbitMQ(一)-CSDN博客 RabbitMQ架构 RabbitMQ是一个基于AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;协议的消息代理中间件&#xff0c;它通过交换机和队列实现消息的路由和分发。以下是RabbitMQ的架构图&#xff1a; Producer&#xff08;生产…

NAS系统折腾记 – Emby搭建家庭多媒体服务器

Emby简介 Emby是一款优秀的媒体服务器软件&#xff0c;致力于为用户提供丰富的多媒体体验。通过Emby&#xff0c;您可以方便地在家庭内的各种设备上观看您喜爱的电影、电视剧和其他视频内容。而且&#xff0c;Emby还具备强大的媒体管理功能&#xff0c;让您的影视资源井然有序…

Win10系统给文件夹添加备注

在Win10系统中&#xff0c;相信大多用户都没有看到过文件或者是文件夹上有备注信息。下面给大家分享下在Win10系统中给文件夹或文件添加备注的方法。在添加备注之前&#xff0c;首先我们要在需要显示备注的文件夹中显示“备注”标签&#xff0c;否则就算我们给某个文件夹添加了…

BAPI_PRODORD_CREATE-创建生产订单BAPI测试

目录 实现过程和笔记完整程序 实现过程和笔记 完整程序 *&---------------------------------------------------------------------* *& Report z_test_bapi_prodord_create_lhy *&---------------------------------------------------------------------* *&am…

视频编辑场景手绘白板解决方案

传统的视频编辑方式已经不能满足现代企业的多元化需求&#xff0c;美摄科技推出了手绘白板方案&#xff0c;为企业提供了一种全新的直播和视频编辑方式&#xff0c;让手绘内容成为视频的一部分&#xff0c;增强了互动性和视觉效果。 一、手绘白板方案的优势 1、实时手绘&…

《PCI Express体系结构导读》随记 —— 第II篇 第4章 PCIe总线概述(6)

接前一篇文章&#xff1a;《PCI Express体系结构导读》随记 —— 第II篇 第4章 PCIe总线概述&#xff08;5&#xff09; 4.1 PCIe总线的基础知识 与PCI总线不同&#xff0c;PCIe总线使用端到端的连接方式&#xff0c;在一条PCIe链路的两端只能各连接一个设备&#xff0c;这两个…

RflySim | 定点位置控制器设计实验四

RflySim| 定点位置控制器设计实验四 01 设计实验 1.建立位置控制通道的传递函数模型 使用MATLAB “ControlSystemDesigner”设计校正控制器,使得加入校正环节后系统速度控制环阶跃响应稳态误差 &#xff0c;相位裕度>75截止频率>2.0rad/s。位置控制环截止频率>1rad/…

《Vue3 基础知识》 使用 GoGoCod 升级到Vue3+ElementPlus 适配处理

此篇为 《Vue2ElementUI 自动转 Vue3ElementPlus&#xff08;GoGoCode&#xff09;》 的扩展&#xff01; Vue3 适配 Vue3 不兼容适配 Vue 3 迁移指南 在此&#xff0c;本章只讲述项目或组件库中遇到的问题&#xff1b; Vue3 移除 o n &#xff0c; on&#xff0c; on&#…

第十一章[文件系统]:11.2:文件的复制/删除/移动

一,相关文档: os模块: os --- 多种操作系统接口 — Python 3.12.1 文档源代码: Lib/os.py 本模块提供了一种使用与操作系统相关的功能的便捷式途径。 如果你只是想读写一个文件,请参阅 open() ,如果你想操作文件路径,请参阅 os.path 模块,如果你想读取通过命令行给出的所…

服务器学习

云服务器通常是通过多台物理服务器协同工作来提供的。云服务提供商使用大规模的数据中心&#xff0c;这些数据中心包含许多物理服务器。这些物理服务器上运行着虚拟化技术&#xff0c;允许它们被分割成多个虚拟服务器实例。 当用户请求创建一个云服务器时&#xff0c;云服务提…

【5G SA流程】5G SA下终端完整注册流程介绍

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客内容主要围绕: 5G/6G协议讲解 …

Python算法题集_搜索二维矩阵II

Python算法题集_搜索二维矩阵II 题41&#xff1a;搜索二维矩阵II1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【双层循环】2) 改进版一【行尾检测】3) 改进版二【对角线划分】 4. 最优算法 本文为Python算法题集之一的代码示例 题41&#xf…

这一年让我印象深刻的bug -- 让sql选择更合理的执行过程

1 业务场景 客户需要一个报表统计工单的各种信息&#xff0c;于是我们利用公司报表平台做了一个报表导出功能。可是当我们准备上ver环境时测试反应报表导出虽然数据正确但性能不能达标&#xff0c;导出非常缓慢。于是我就开始分析报表sql。 2 问题分析 相信有过开发经验的同学…

【Vue.js设计与实现】第二篇:响应系统-阅读笔记(持续更新)

从高层设计的角度去探讨框架需要关注的问题。 系列目录&#xff1a; 标题博客第一篇&#xff1a;框架设计概览【Vue.js设计与实现】第一篇&#xff1a;框架设计概览-阅读笔记第二篇&#xff1a;响应系统【Vue.js设计与实现】第二篇&#xff1a;响应系统-阅读笔记第三篇&#x…

Java 使用 ant.jar 执行 SQL 脚本文件

Java 使用 ant.jar 执行 SQL 脚本文件&#xff0c;很简单。 在 pom.xml 中导入 ant 依赖 <dependency><groupId>org.apache.ant</groupId><artifactId>ant</artifactId><version>1.10.11</version> </dependency>sql 脚本文件…

Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(二)

原文&#xff1a;Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第三章&#xff1a;分类 在第一章中&#xff0c;我提到最常见的监督学习任务是回归&#xff08;预测值&#xff09;和分类&#…

IGMP——网际组管理协议

目录 1 IGMP 1.1 IGMP 使用 IP 数据报传递其报文 1.2 IGMP 工作 第一阶段&#xff1a;加入多播组 第二阶段&#xff1a;探询组成员变化情况 1.3 IGMP 采用的一些具体措施&#xff0c;以避免增加大量开销 1 IGMP 标准 1989 年公布的 RFC 1112&#xff08;IGMPv1&#xff…

淘宝镜像到期如何切换镜像及如何安装淘宝镜像

淘宝镜像到期如何切换镜像及如何安装淘宝镜像 一、淘宝镜像到期如何切换新镜像二、第一次使用淘宝镜像如何配置镜像 一、淘宝镜像到期如何切换新镜像 清空缓存&#xff1a;npm cache clean --force切换镜像源&#xff1a;npm config set registry https://registry.npmmirror.…

你今年过年回去吗?

#过年 我是一名21岁刚毕业的大学生&#xff0c;专业是软件技术&#xff0c;主修c#&#xff0c;之前在上海实习了一年&#xff0c;正式工作后来到了深圳&#xff0c;进入了一家电商公司实习。至于我为什么转行了&#xff0c;大家懂的都懂 现在是20240203晚上19.39&#xff0c;还…

【Linux】Linux 开发工具(vim、gcc/g++、make/Makefile)+【小程序:进度条】-- 详解

我们在 Windows 中编写 C/C 程序时&#xff0c;常用的 VS2019 是一个集成开发环境&#xff0c;包含了很多工具包。而在 Linux 下开发&#xff0c;大部分的情况下都是使用一个个独立的工具。比如&#xff1a;编写代码用 vim&#xff0c;编译代码用 gcc&#xff0c;调试代码用 gd…