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.hqwc.cn/news/451841.html

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

相关文章

EasyCVR视频融合平台如何助力执法记录仪高效使用

旭帆科技的EasyCVR平台可接入的设备除了常见的智能分析网关与摄像头以外 &#xff0c;还可通过GB28181协议接入执法记录仪&#xff0c;实现对执法过程的全称监控与录像&#xff0c;并对执法轨迹与路径进行调阅回看。那么&#xff0c;如何做到执法记录仪高效使用呢&#xff1f; …

使用PHPStudy搭建Cloudreve网盘服务

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

软件成本度量

1. 软件成本度量的意义 软评的意义主要在于其在软件项目的预算、招投标、实施及决算后评估阶段的重要作用。 在预算及招投标阶段&#xff0c;软评通过软件成本度量有助于制定合理的项目预算&#xff0c;规范招投标行为。这一阶段&#xff0c;甲方需要准确估算项目成本和合理的投…

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

RflySim| 定点位置控制器设计实验三 01 分析实验 1.调节PD控制器的相关参数改善系统控制性能&#xff0c;并记录超调量和调节时间&#xff0c;得到满意的参数。 2.在得到满意参数后&#xff0c;对系统进行扫频以绘制Bod图&#xff0c;观察系统幅频响应、相频响应曲线&#xf…

极速搭建幻兽帕鲁私服,叫上好友春节假期一起联机畅玩帕鲁

文章目录 前言幻兽帕鲁私服详细部署教程查看服务器开始游戏自定义游戏参数配置 前言 行业资讯 《幻兽帕鲁》的火爆对开发商 Pocketpair 来说&#xff0c;代价是巨大的。该游戏的成功让首席执行官沟部拓郎最近在推特上表示&#xff0c;他可能因服务器运营费用而面临破产。据他透…

2024年生成式AI芯片市场规模将达500亿美元

1月24日&#xff0c;德勤发布《2024科技、传媒和电信行业预测》中文版报告&#xff0c;2024年是科技、传媒和电信行业关键的一年&#xff0c;不少科技公司正利用生成式AI升级软件和服务&#xff0c;预计今年全球生成式人工智能芯片销售额可能达到500亿美元以上。 2024年将有许…

加速数字化之旅:MessageBox赋能HubSpot与微信公众号的无缝整合

在数字化时代&#xff0c;企业需要整合关键平台以适应快速变化的市场。HubSpot和微信公众号的整合成为数字化营销的核心策略之一。MessageBox作为整合的关键力量&#xff0c;通过其卓越的能力&#xff0c;极大地加速了HubSpot与微信公众号的融合过程。今天运营坛将深入探讨Mess…

基于springboot篮球竞赛预约平台源码和论文

随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;篮球竞赛预约平台也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&#xff0c;而…

gif怎么压缩文件大小?这个方法一学就会

一些媒体和广告公司使用GIF表情包来吸引用户的注意力&#xff0c;并在广告或宣传活动中增加趣味性和互动性&#xff0c;这些表情包可以用来传达品牌的个性、情感或概念&#xff0c;那么当遇到gif图片过大的时候&#xff0c;就需要gif压缩了&#xff0c;怎么压缩gif动图呢&#…

力扣144 二叉树的前序遍历 Java版本

文章目录 题目描述递归方法代码 非递归方法代码 题目描述 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xf…

解锁MyBatis Plus的强大功能:学习高级操作与DML技巧!

MyBatisPlus 1&#xff0c;DML编程控制1.1 id生成策略控制知识点1&#xff1a;TableId1.1.1 环境构建1.1.2 代码演示AUTO策略步骤1:设置生成策略为AUTO步骤3:运行新增方法 INPUT策略步骤1:设置生成策略为INPUT步骤2:添加数据手动设置ID步骤3:运行新增方法 ASSIGN_ID策略步骤1:设…

MyBatis一些常见知识点!

什么是 ORM 框架&#xff1f; MyBatis 有哪些优缺点&#xff1f; 典型回答&#xff1a; ORM&#xff08;Object-Relational Mapping&#xff0c;对象关系映射&#xff09;框架是一种将关系型数据库中的数据 与 应用程序中的对象进行映射的技术。它通过在程序代码中定义的类和属…