额,为什么会有番外篇呢。。因为新版本上线后,别的同学用我的这个控件,描边显示出问题了-_-!

什么问题呢?

我把问题抽出来,同时把问题放大点,给大家看看(抹眼泪.png)

好嘛,问题不大。。就是描边歪了一点点,对吧。

可是怎么会这样!?,我自己测根本就没有问题,压根就没出现过这样的问题啊。。(抹眼泪.png)

我又去检查了一遍计算描边位置那块的代码,最初是以为其他同学一不小心该了那块的代码,导致描边位置计算出错了,结果发现,代码丝毫没有动过的痕迹。

那怎么会描边出错呢?而且他描边出问题的地方,在我这里这里显示也没什么问题,在他那里会什么会有这么大的偏差呢?

我不信邪,看看那位同学都对StrokeTextView做了哪些设置?结果发现,他多了下面这行代码:

mStrokeTextView.setTypeface(typeface);

我捉摸了一下,发现这行代码很有问题,因为我的StrokeTextView是继承自TextView的,调用setTypeface(),看看它的默认实现:

public void setTypeface(@Nullable Typeface tf) {if (mTextPaint.getTypeface() != tf) {mTextPaint.setTypeface(tf);if (mLayout != null) {nullLayouts();requestLayout();invalidate();}}
}

看了一眼我就明白了,它只是给TextPaint设置了不同的typeFace,而我们的描边是使用不同的TextPaint,也就是说setTypeface()只是给我们的文本设置了字体,却没有给我们的StrokeTextPaint设置相同的字体,导致了两种不同字体之间,没有办法对齐位置,导致了描边差异。

怎么解决?简单,照葫芦画瓢就行,我们在StrokeTextView重写setTypeface()方法。

setTypeface()的默认实现有两种,我们都要重写:

@Override
public void setTypeface(@androidx.annotation.Nullable Typeface tf) {// 模仿TextView的设置// 需在super.setTypeface()调用之前,不然没有效果if (mStrokePaint != null && mStrokePaint.getTypeface() != tf) {mStrokePaint.setTypeface(tf);}super.setTypeface(tf);
}

另一种比较复杂,不过我们会模仿就行了:

public void setTypeface(@Nullable Typeface tf, int style) {if (style > 0) {if (tf == null) {tf = Typeface.defaultFromStyle(style);} else {tf = Typeface.create(tf, style);}setTypeface(tf);// now compute what (if any) algorithmic styling is neededint typefaceStyle = tf != null ? tf.getStyle() : 0;int need = style & ~typefaceStyle;getPaint().setFakeBoldText((need & Typeface.BOLD) != 0);getPaint().setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);// 同步设置mStrokeTextPaintif (mStrokePaint != null) {mStrokePaint.setFakeBoldText((need & Typeface.BOLD) != 0);mStrokePaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);}} else {getPaint().setFakeBoldText(false);getPaint().setTextSkewX(0);// 同步设置mStrokeTextPaintif (mStrokePaint != null) {mStrokePaint.setFakeBoldText(false);mStrokePaint.setTextSkewX(0);}setTypeface(tf);}
}

两步解决,但为什么我这显示没问题,别的同学那里显示就出问题了呢?

我突然想起来,相同字体在不同手机上显示是有差异的,而且有些手机不一定都支持那种字体

我和那位同学用着不同厂商的真机进行测试,而我的真机是不支持他设置的字体的,所以看着没问题,但他的小米是支持的。难怪我这看着没问题,他那看着就很离谱。

修改完后,我们在运行一遍。

怎一个完美形容!ok,bug解决了,准备提交代码

就这样结束了吗?

时隔多日,我又重新审核了一遍代码,我留意到这样一行代码

float heightWeNeed
= getCompoundPaddingTop() + getCompoundPaddingBottom() + mStrokeWidth + mTextRect.height() + DensityUtil.dp2px(getContext(), 4);

我们需要的高度 = 内边距 + 描边高度 + 文本高度 + 一个额外设定的值 ?

怎么会需要一个额外的值呢?要实现wrap_content的效果,我们的宽度不是只需要加上边距、文本高度和一个描边的高度吗?

好奇怪的逻辑,这不是多余嘛,我当时怎么想的来着哈哈?不符合我wrap_content的预期,把它删了试试,再测一遍

把我之前的测试用例都测了一遍,都运行正常

除了。。除了下面这种情况。

果然,去掉额外的高度,就会有这种高度不够显示的情况。看来当时的我,就是遇到了这种情况,然后一个手快,就给heightWeNeed做了这种适配。

不过这种手快的适配方法貌似不太优雅,为了适配单一的这种情况,要牺牲剩下的所有情况都增加一个额外的高度。

而且因为我们适配的额外高度是一个固定值,如果我们给文本字体大小设置大一点,还是会有高度不够显示的可能,毕竟文本变大了,所需要的高度也就更多了。

好吧,这种适配方法看来是用不得了,要换一个吗?但是计算高度的公式 = 内边距 + 文本高度 + 描边高度,这个公式肯定是没错的。

回到我们最初的问题,我们为什么会需要增加一个额外的固定高度呢?明明公式都是对的,为什么还是会有偏差,难道是公式里的对应的值计算错误了?

我们看看再来看看这个式子:

heightWeNeed = getCompoundPaddingTop() + getCompoundPaddingBottom() +mStrokeWidth + mTextRect.height();

其中,getCompoundPaddingTop() 和 getCompoundPaddingBottom() 是Android提供的计算内边距的api,这个肯定不至于错吧。

mStrokeWidth是我们的描边宽度,是由用户使用时自定义的,这个没什么需要计算的,就是一个值而已

那么mTextRect.height() 这个呢,我们需要这里返回一个正确的文本高度。

看看这个mTextRect是在哪里赋值的

getPaint().getTextBounds(text, 0, text.length(), mTextRect);

从getTextBounds()里跟下去,发现最后调用测量的是native方法,看不到内部实现,不过我们可以看看getTextBounds()的注释

/*** Retrieve the text boundary box and store to bounds.** Return in bounds (allocated by the caller) the smallest rectangle that* encloses all of the characters, with an implied origin at (0,0).** @param text string to measure and return its bounds* @param start index of the first char in the string to measure* @param end 1 past the last char in the string to measure* @param bounds returns the unioned bounds of all the text. Must be allocated by the caller*/
public void getTextBounds(String text, int start, int end, Rect bounds) {...// native 方法nGetStringBounds(mNativePaint, text, start, end, mBidiFlags, bounds);
}

Return in bounds the smallest rectangle that encloses all of the characters

在bounds中返回包含所有字符的最小矩形

也就是说bounds返回的高度,只是能够包含文本的最小高度。

我们在三部曲概览里就讨论过,安卓里文本的描绘,是由几根线来确定的

文本的高度应该为(fontMetrics.bottom -fontMetrics. top),但是,bounds中返回的height也够文本显示啊?怎么会显示成下面这个样子?

比如这样

但实际情况好像是这样的

我想到,安卓绘制文本是有起点坐标的,这个起点由gravity,textAlign,和baseline确定,和内容展示高度好像没有关系。

虽然我们展示高度设小了,但它的起点坐标还在原来的位置(比如y坐标baseline),这才导致了18数字显示不完整,底部好像缺了一块。

问题的根本找到了,看来好像有两种解决方法

  1. 调整baseline的位置:把我们的baseline位置上移一些,让它和展示区域底部位置重合,这样就能以最小区域显示完整的文本内容。
  2. 拓宽bounds.height的高度,以(fontMetrics.bottom - fontMetrics.top)作为文本的高度显示,这样就无需改变baseline的位置,但比第一种方案要多需要一些空间。

这里我选了第二种,顺着系统的绘制规则来,图个方便,而且我们的描边也可以利用文本顶部多出来的这些空间。

我们新设个变量 textHeight = fontMetrics.descent - fontMetrics.top

heightWeNeed = getCompoundPaddingTop() + getCompoundPaddingBottom() +textHeight + mStrokeWidth / 2;

为了最大化利用空间,文字顶部到top线的距离已经足够我们的描边显示了,而bottom线到descent线之间的距离很窄,就可能不够我们的描边显示。

所以只需要在文字底部加一半的描边宽度,同时去掉buttom线和descent线之间的距离,这样就能确保文字和描边都有足够的位置显示了。

好了,番外篇终于结束了,看了眼字数,居然比之前的三部曲系列都要多一些。实在没想到需要这么长的篇幅来讲这两个小优化,谢谢小伙伴们能够看到这里啦。

源码我都已经上传到github了,欢迎小伙伴自取,如果觉得写得不错的,还请给这份工程给个star ~_ <

兄dei,如果觉得我写的还不错,麻烦帮个忙呗

理解TextView三部曲之番外篇:或许这会是最终的进化相关推荐

  1. PostCSS自学笔记(二)【番外篇二】

    图解PostCSS的插件执行顺序 文章其实是一系列的早就写完了. 才发现忘了发在SegmentFault上面, 最早发布于https://gitee.com/janking/Inf... 这次我继续研 ...

  2. 系统工程(SE)学习笔记(番外篇之一)——Capella使用体会兼谈SE工具

    系统工程(SE)学习笔记(番外篇之一)--Capella使用体会兼谈SE工具 零.Capella简介 壹. Capella的优势 贰.Capella的缺点 叁. 生态环境 肆. 总结 说到SE,就不能 ...

  3. [zt]数学之美番外篇:平凡而又神奇的贝叶斯方法

    数学之美番外篇:平凡而又神奇的贝叶斯方法 Tags: 数学, 机器学习与人工智能, 计算机科学 save it69 saved tags: 贝叶斯 math bayesian algorithm 数学 ...

  4. 转:数学之美番外篇:平凡而又神奇的贝叶斯方法 收藏

    为什么80%的码农都做不了架构师?>>>    转自:http://blog.csdn.net/pongba/archive/2008/09/21/2958094.aspx 数学之美 ...

  5. 教你从0到1搭建秒杀系统-Canal快速入门(番外篇)

    Canal用途很广,并且上手非常简单,小伙伴们在平时完成公司的需求时,很有可能会用到.本篇介绍一下数据库中间件Canal的使用. 很多时候为了缩短调用延时,我们会对部分接口数据加入了缓存.一旦这些数据 ...

  6. 文本分类入门(番外篇)特征选择与特征权重计算的区别

    文本分类入门(番外篇)特征选择与特征权重计算的区别 在文本分类的过程中,特征(也可以简单的理解为"词")从人类能够理解的形式转换为计算机能够理解的形式时,实际上经过了两步骤的量化- ...

  7. 5年前面试题引发的“血案”(番外篇)(总结和乱侃)

    这货是说好的番外篇-- 所谓的番外篇其实就是对前面的各个知识点做一些总结. 血案(1)中的两个内容主要是日志切换时的检查点和表空间管理. 日志切换其实远远没有简单,有关于检查点和日志文件的内容太多了, ...

  8. python爬虫进程和线程_python爬虫番外篇(一)进程,线程的初步了解-阿里云开发者社区...

    整理这番外篇的原因是希望能够让爬虫的朋友更加理解这块内容,因为爬虫爬取数据可能很简单,但是如何高效持久的爬,利用进程,线程,以及异步IO,其实很多人和我一样,故整理此系列番外篇 一.进程 程序并不能单 ...

  9. 模糊搜索神器FZF番外篇

    模糊搜索神器FZF番外篇 Fuzzy finder 什么是模糊搜索? 广义的模糊搜索是指允许被搜索信息和搜索提问之间存在一定的差异,这种差异就是"模糊"在搜索中的含义.例如,查找名 ...

最新文章

  1. 与英特尔抢市场,英伟达的数据中心业务能增长到多大?
  2. 针对苹果最新审核要求 为应用兼容IPv6
  3. 曾经遭遇的MyEclipse的bug
  4. 开源大数据周刊-第34期
  5. RS232串口交叉直连
  6. cocos2dx 制作单机麻将(二)
  7. Pod详解-生命周期-钩子函数
  8. 模块-from import导入所有工具
  9. IIC软件模拟-读写EEPROM
  10. Windows 8 Directx 开发学习笔记(十二)利用混合实现浮在水面的木箱
  11. html怎么让背景颜色百分比,jquery – CSS设置背景颜色只是表行宽度的一个百分比...
  12. 新浪微博API应用程序接口_什么是API? 应用程序编程接口说明
  13. 线性代数的本质-基向量部分理解
  14. NPN和PNP的区别和总结
  15. 装了冰点还原如何修改计算机ip,冰点还原软件如何使用
  16. 6章7街构造函数和解析函数
  17. 一个屌丝程序猿的人生(九十)
  18. 奇偶校验的快捷判断方法---按位异或
  19. asp.net网站服务器,vs2010制作简单的asp.net网站
  20. 分享下剪辑师必须知道的13个剪辑技巧!

热门文章

  1. 多个div在一个div中一行水平显示(float)
  2. 0201导数的概念-导数与微分-高等数学
  3. Linux小技巧4--如何屏蔽某个特定的网站
  4. 遥感影像出现密密麻麻小空洞「神奇」解决办法
  5. linux没有网卡装置,基于Linux的虚拟网卡实现方法、装置、设备及介质与流程
  6. 本网站受海外服务器保护,该网站服务器受美国保护
  7. onSenUI 常用UI组件 笔记
  8. 如果5G时代到来了,普通人有哪些创业机会?
  9. 站外引流——YouTube篇
  10. 科技云报道:AI写小说、绘画、剪视频,生成式AI更火了!