如何在ImageSpan上面绘制文本?

news2024/12/24 2:26:29

简介:TextOnImageSpan

  前阵子碰到一个需求:在文本中内嵌图标,并在图标上面绘制特定文本。很自然就会想到用SpannableString去实现,但经过一系列的研究捣鼓,发现根本就没有能在图标上绘制文本的span类,于是乎我去翻ImageSpan跟其它主要span类的源码,发现它们的绘制原理跟自定义View差不多,都是在draw方法里画东西,所以我就自己继承ImageSpan,实现了在图标上面绘制特定文本的Span。
  我管它叫——TextOnImageSpan。

github链接:https://github.com/Coder-HuangBH/TextOnImageSpan

效果图

使用示例

  在使用上还是与其它Span一致,加入到SpannableString或SpannableStringBuilder后,设置进TextView即可。

	val ssb = SpannableStringBuilder()
		
	var span = TextOnImageSpan(this, R.drawable.location, ALIGN_CENTER)
	span.setFakeBoldText(true)
	span.setTextColor(Color.BLUE)
	span.setTextSize(DisplayUtil.sp2px(this, 16F).toFloat())
	span.setImageHeight(DisplayUtil.dip2px(this, 50f))
	span.mOffsetX = DisplayUtil.dip2px(this, 15f).toFloat()
	span.mOffsetY = DisplayUtil.dip2px(this, 15f).toFloat()
	ssb.append("定位", span, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
	
	textView.text = ssb

源码

  • 重写了getSize方法,根据设置的文本与图像之间的最大宽高计算出span最终的尺寸。重写了draw方法,修改了原有的部分图片绘制逻辑,然后再加入了文本的绘制逻辑。
  • 图片的垂直方向位置计算继承了原有ImageSpan的计算规则,而水平方向暂时默认居中绘制,不支持修改。而文本的位置则是相对于图片的位置居中。
  • 支持设置Image的height属性,设置之后内部将自动等比缩放drawable。
  • 对于文本的参数配置:可以修改文字大小、文字颜色、是否加粗,另外还提供了mOffsetX和mOffsetY两个参数,允许使用者对文本的绘制位置进行自定义的偏移(在相对于图片位置居中的前提下)。
class TextOnImageSpan : ImageSpan {

    private var mDrawable : Drawable? = null
    private val mTextPaint: Paint = Paint()
    private var mTextHeight = 0f
    private var mBaseLineOffset = 0f
    var mOffsetX = 0f
    var mOffsetY = 0f

    constructor(context: Context, bitmap : Bitmap) : super(context, bitmap)

    constructor(context: Context, bitmap : Bitmap, verticalAlignment : Int)
            : super(context, bitmap, verticalAlignment)

    constructor(draw : Drawable) : super(draw)

    constructor(draw : Drawable, verticalAlignment : Int) : super(draw, verticalAlignment)

    constructor(context: Context, uri : Uri) : super(context, uri)

    constructor(context: Context, uri : Uri, verticalAlignment : Int)
            : super(context, uri, verticalAlignment)

    constructor(context: Context, @DrawableRes resourceId: Int) : super(context, resourceId)

    constructor(context: Context, @DrawableRes resourceId: Int, verticalAlignment : Int)
            : super(context, resourceId, verticalAlignment)

    init {
        mTextPaint.color = Color.WHITE
        mTextPaint.textSize = 20f
        computerTextParams()
    }

    private fun computerTextParams() {
        val fm = mTextPaint.fontMetrics
        mTextHeight = fm.descent - fm.ascent
        mBaseLineOffset = abs(fm.ascent)
    }

    fun setTextSize(size: Float) {
        mTextPaint.textSize = size
        computerTextParams()
    }

    fun setTextColor(color: Int) {
        mTextPaint.color = color
    }

    fun setFakeBoldText(bold: Boolean) {
        mTextPaint.isFakeBoldText = bold
    }

    fun setImageHeight(height : Int) {
        drawable?.run {
            if (bounds.height() != height) {
                val scale = height.toFloat() / bounds.height()
                setBounds(bounds.left, bounds.top,
                    bounds.left + (bounds.width() * scale).roundToInt(), bounds.top + height)
            }
            mDrawable = this
        }
    }

    override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int,
                         fm: Paint.FontMetricsInt?): Int {

        val d = (mDrawable ?: drawable) ?: return 0

        val rect = d.bounds

        fm?.run {
            ascent = -(max(mTextHeight.toInt(), rect.bottom))
            descent = 0
            top = ascent
            bottom = 0
        }

        var size = rect.right

        text?.takeIf { it.isNotEmpty() }?.let {
            size = max(size, (mTextPaint.measureText(text, start, end)).roundToInt())
        }

        return size
    }

    override fun getDrawable(): Drawable? {
        return mDrawable ?: super.getDrawable()
    }

    override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int,
        x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {

        val draw = (mDrawable ?: drawable) ?: return

        canvas.save()

        val totalWidth = draw.bounds.width().toFloat()
        val textWidth = mTextPaint.measureText(text, start, end)

        var transX = x
        if (textWidth > draw.bounds.width()) {
            transX += (textWidth - draw.bounds.width()) / 2
        }
        var transY = bottom - draw.bounds.bottom
        if (mVerticalAlignment == ALIGN_BASELINE) {
            transY -= paint.fontMetricsInt.descent
        } else if (mVerticalAlignment == ALIGN_CENTER) {
            transY = top + (bottom - top) / 2 - draw.bounds.height() / 2
        }
        if (mTextHeight > draw.bounds.height()) {
            canvas.translate(0f, -(mTextHeight - draw.bounds.height()) / 2)
        }
        canvas.translate(transX, transY.toFloat())

        //drawImage
        draw.draw(canvas)

        //drawText
        text?.takeIf { it.isNotEmpty() }?.let {
            val startX = (totalWidth - textWidth) / 2 + mOffsetX

            val mTextBaseLine = (draw.bounds.height() - mTextHeight) / 2 + mBaseLineOffset
            val startY = mTextBaseLine + mOffsetY

            canvas.drawText(text, start, end, startX, startY, mTextPaint)
        }

        canvas.restore()
    }

}

总结

整体的逻辑较为简单,就没写什么注释了,如果有感兴趣的同学,可以上github拉源码下来自己试试看。创作不易,觉得不错的话,就来个一键三连吧,感谢大家!
  未经许可,请勿转载!
github链接:https://github.com/Coder-HuangBH/TextOnImageSpan
本文链接:https://blog.csdn.net/weixin_44337681/article/details/131062779

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

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

相关文章

单马达可换档六足机器人前进功能的实现

1. 运动功能说明 本文示例将实现R046样机单马达可换档六足机器人前进的功能。 2. 结构说明 本样机只有一个圆周舵机,却可以通过换挡机构实现前进和转向两种行走姿态。 样机由一个圆周舵机带动一个等速齿轮组(下图所示两枚蓝色齿轮)&#xff0…

C#,码海拾贝(26)——求解“一般带状线性方程组”之C#源代码

在大型稀疏方程组中,最常见的是带状方程组,其系数矩阵是带状矩阵,非零元素仅集中在对角线附近的带状区域内。 特别的,当上下带宽相等时我们A称方程组为等带宽方程组。总带宽为3的等带宽方程组我们又叫三对角方程组。 using Syste…

如何在 Elasticsearch 论坛/社群上提出高质量的技术问题?

在网络的海洋中寻求帮助,有时可能会让你感到茫然。你可能已经准备好详细描述你的问题,但如果你不知道如何有效地提问,你可能会发现自己在等待回答时感到挫败。 这篇文章的目标是为你提供一些提示,让你更快地获取你在论坛上的技术问…

数据库管理-第八十二期 EMCC升级教程(20230606)

数据库管理 2023-06-06 第八十二期 EMCC升级教程1 升级EMCC1.1 升级概览1.2 拷贝相关文件1.3 升级OPatch1.4 升级OMSPatcher1.5 升级WLS1.6 升级OMS 2 升级Agent2.1 升级概览2.2 拷贝相关文件2.3 安装或升级AgentPatcher2.4 升级agent 3 升级Oracle数据库ASH包总结 第八十二期 …

2. 分布式文件系统 HDFS

2. 分布式文件系统 HDFS 1. 引入HDFS【面试点】 问题一:如果一个文件中有 10 个数值,一行一个,并且都可以用 int 来度量。现在求 10 个数值的和 思路: 逐行读取文件的内容把读取到的内容转换成 int 类型把转换后的数据进行相加…

一文打通基于注解管理Bean

目录 开启组件扫描 情况一:最基本的扫描方式 情况二:指定要排除的组件 情况三:仅扫描指定组件 使用注解定义 Bean Autowired注入 ①场景一:属性注入 ②场景二:set注入 ③场景三:构造方法注入 ④…

Hbase安装指南

Hbase简介 HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。 HBase是Google Bigtable的开源实现,类似Google Bigtable利用GFS作为其文件存储系统,HBase利用Had…

Java基础学习+面向对象(一)

一,基础概念介绍 1.1Java跨平台原理(一次编译,处处运行) Java 源代码经过编译,生成字节码文件,交由 Java 虚拟机来执行,不同得系统有不同得JVM,借助JVM 实现跨平台。就比如说我们在 Windows 下…

基于Java+uniapp微信小程序的购物商城系统设计与实现

博主介绍:擅长Java、微信小程序、Python、Android等,专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇🏻 不然下次找不到哟 Java项目精品实战案例…

linux和windows爬虫有什么区别

Linux和Windows作为操作系统对于爬虫的差异不是特别大,因为两个操作系统同时都可以用于编写运行爬虫的程序。 主要的差异可能源于开发工具和环境的差异。Linux上通常使用命令行工具来编写和运行爬虫程序,而Windows则更加倾向于使用图形化界面的编程软件…

chatgpt赋能python:Python定义父类的意义及用法

Python定义父类的意义及用法 Python是一种高级编程语言,具有强大的面向对象编程(OOP)能力。在OOP的设计中,定义一个父类可以让多个子类继承其属性和方法,从而提高代码重用率并简化程序的开发。 如何定义Python中的父…

一起看 I/O | 借助 Google Play 管理中心价格实验,优化定价策略

作者 / Google Play 产品经理 Phalene Gowling 今年 Google I/O 大会上的 "通过 Google Play Commerce 提升收益" 演讲重点为您介绍了深度集成至 Google Play 的最新创收工具。此工具专注于帮您优化定价策略。为您的产品或内容确定合适的价格是实现更出色的用户生命周…

大会议题重磅出炉,豪华阵容等你面基!RustChinaConf 2023!【附第一天议程】

本次大会议题品质一流,嘉宾多来自行业一线,干货多多,且在各领域遍地开花,可看出Rust星星之火在中国已成燎原之势! 大会时间地址 6.17 - 6.18 浦东新区张杨路777号 上海锦江汤臣洲际酒店 官网地址 https://rustcc.cn/20…

华为云发布面向消费终端的企业云原生白皮书,开辟移动时代的云原生路径

2013年,程序员Matt Stine提出“CloudNative”概念,后来他又将这项技术的特点归纳为模块化、可观察、可部署、可测试、可替换、可处理6项,这就是大名鼎鼎的云原生。 十年过去,云原生的价值在科技界可谓无人不知,谁都能说…

合宙-Air724模块的程序下载和二次开发下载方法

一、资料准备 参考:Luat社区 (openluat.com) 合宙官方: 银尔达官方提供如下: 下载相应的资料即可,资料链接如下: https://doc.openluat.com/wiki/27?wiki_page_id3038 http://wiki.yinerda.com/index.php/Core-Air724…

2023智源大会议程公开丨基础模型前沿技术论坛

6月9日,2023北京智源大会,将邀请这一领域的探索者、实践者、以及关心智能科学的每个人,共同拉开未来舞台的帷幕,你准备好了吗?与会知名嘉宾包括,图灵奖得主Yann LeCun、图灵奖得主Geoffrey Hinton、OpenAI创…

职业选择的两种路径:向外求 vs 向内求

和很多职场人士、创业者交流过事业的方向选择,抛开具体的决策方法,我感受到背后有两种价值观。 简单说,向外求 vs 向内求。 现如今,如果你人到中年面临“毕业”,又一次需要做职业选择,也可以参考。 前者的选…

【JavaSE】Java(五十六):核心要点总结

文章目录 1. byte 类型 127 1等于多少2. java容器有哪些3. Collection 和Collections 有什么区别4. list 和 set 的区别5. HashMap 和 Hashtable 有什么区别 1. byte 类型 127 1等于多少 当byte类型的值超出它的范围时,它会发生溢出并且变为负数。在Java中&#x…

【Python】Python系列教程-- Python3 输入和输出(二十四)

文章目录 前言输出格式美化旧式字符串格式化读取键盘输入读和写文件文件对象的方法f.read()f.readline()f.readlines() 打开一个文件f.write()f.tell()f.seek()f.close()pickle 模块 前言 往期回顾: Python系列教程–Python3介绍(一)Python…

《Opencv3编程入门》学习笔记—第五章

《Opencv3编程入门》学习笔记 记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。 第五章 core组件进阶 一、访问图像中的像素 (一)图像在内存之中的存储方式 图像矩阵的大小取决于所用的颜色模型,确切地说&#xff…