理解TextView三部曲(三):倔强的StrokeTextView(我无论如何都要展示出来!)

理解TextView三部曲(三):倔强的StrokeTextView(我无论如何都要展示出来!而且要美美的!)

上一篇我们让StrokeTextView支持padding描边,如果有同学没有看过或者对上一篇内容有遗忘的,可以移步上一篇 理解TextView三部曲(二):支持Padding的StrokeTextView


按照惯例,在本篇开头会抛出上一篇优化后的不足,看看是哪里还有问题

1_2_show_bad

首先看第一张图,宽、高都是wrap_content的情况下,“18”左边的描边显示正常,但是右边的描边显然就被切了一块,很明显,这是因为宽度不够我们的StrokeTextView显示。

第二张和第三张图的问题也就更明显了,在宽或高为固定值的情况,如果不够位置显示,StrokeTextView就被挤到一边了或者显示的不知道什么内容。

那么StrokeTextView2经过了本篇的优化,升级成了StrokeTextView3,它达到的效果是什么样的呢?

2_same_gravity

那就是完美兼容各种情况的宽高,即使在宽、高都设置成1dp的情况下,都能够将StrokeTextView完整的显示出来

优化的核心,其实就是在宽高不够显示的时候,给StrokeTextView扩容,因此需要重新测量StrokeTextView的width和height。

那么就涉及到自定义View的**onMeasure()**过程了,在重写onMeasure()方法之前,有一个变量得先说清楚

MeasureSpec

MeasureSpec是View的内部类,一般是个32位int型的值,前2位表示Mode,后30位表示Size

void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

这个值一般在onMeasure()用到,是由父类调用子类的measure()方法时传入的。

对于Mode,也有三种,有特定的含义:

  1. UNSPECIFIED:这个模式一般只有在系统进行测量的时候才会用到
  2. EXACTLY:当设置的宽(高)是一个特定的值或是MATCH_PARENT
  3. AT_MOST:当设置的宽(高)是WRAP_CONTENT

所以,如果给我们的StrokeTextView设置的宽(高)为1dp的话,那么它MeasureSpec的模式就是EXCATLY,如果设置为WRAP_CONTENT的话,那么模式就是AT_MOST。

那我们要重写onMeasure()进行重新测量的话,实际上也就是针对不同的Mode进行处理

首先在StrokeTextView的onMeasure()方法中,拿到宽(高)对应的Mode和Size

int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int modifiedWidth = widthSize;
int modifiedHeight = heightSize;

并定义两个变量,用来记录重新测量后的宽(高)

我们希望,无论StrokeTextView被设置的宽(高)为多少,只要不够完整的显示我们的StrokeTextView,就重新测量。

完整的显示就包括,不仅能显示文字,同时文字的描边也要完整的显示出来,如下图:

3_width&height=wrapContent_no_bg

对于宽度,至少能够容纳左、右padding,textWidth以及strokeWidth

float widthWeNeed = getCompoundPaddingRight() + 			  							getCompoundPaddingLeft() + mStrokeWidth + textWidth;

还记得部曲一我们讨论过Stroke的描边规则吗?如果strokeWidth=20,那么也只会描出10宽度的边,另外一边会因为绘制于text文本的下面而被遮住。所以stroke实际需要的显示宽度就为mStrokeWidth。

mStrokeWidth = DensityUtil.dip2px(getContext(), 3);

textWidth可以这么获得:

float textWidth =getPaint().measureText(getText().toString());

而高度同理,至少能够容纳上下padding,textHeight以及strokeWidth

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

textHeight可以这么获得

mTextRect = new Rect();
text = getText().toString();
getPaint().getTextBounds(text, 0, text.length(), mTextRect);

然后根据不同的Mode进行处理,首先当Mode=MeasureSpec.EXACTLY

Mode = MeasureSpec.EXACTLY

4_mode=exactly

在这里说明一下mCallCount,因为View的一次测量会至少调用两次onMeasure()【不同的系统版本调用次数会有不同,但至少两次】,在第一次调用时,我们给widthSize重设了一个值A,第二次调用时,传进来的widthSize = match_parent【原因未知】,match_parent会大于我们之前设的值A,会再次更新widthSize,会导致最终stroke的描边效果不准确,所以通过mCallCount来控制只调用一次。

当width不够显示时(如width = 1dp),就会把modifiedWidth设置成widthWeNeed,当width = match_parent时,modifiedWidth依旧是match_parent。

最后重新创建MeasureSpec,传入**super.onMeasure()**方法中

相当于做了一次拦截,如果width/height值太小,就扩大它们的空间

为什么可以这么做,我们可以看看TextView的默认处理,看看TextView.onMeasure(),其中有一段:

5_textview_onMeasure()_width_excatly

TextView的默认实现里,当Mode = EXACTLY时,并没有对widthSize进行任何处理,前面说过widthMeasureSpec和heightMeasureSpec都是由父控件测量后传入的,而这个测量出来的widthSize和heightSize,一般就是我们在xml文件中设置width和height

xml中设置的width = 1dp,那么实际显示的也只有1dp的大小,height也是同理

6_textview_onMeasure()_height_excatly

这也是为什么我们设置width = 1dp时,默认显示会成这个样子:

7_width_height=1dp

Mode = EXACTLY时的处理完了,来看看效果:

8_excatly_show

Mode = MeasureSpec.AT_MOST

思想与处理EXCATLY的时候差不多,有一些细微的差别

9_at_most_handle

逻辑上的处理是差不多,这里就不多说明了,不过最后调用的是setMeasuredDimension(),而不是像我们上面处理EXACTLY的时,重新创建MeasureSpec对象。这又是为什么呢?

接着回过头去看TextView.onMeasure()的相关处理,以width为例:

10_show_textview_onmeasure

TextView.onMeasure()里面对于Mode=AT_MOST是有其他运算的,如果我们接着在底部调用super.onMeasure(),那么就会改变我们设置的widthSize,我们要跳过这部分默认的运算,所以直接调用setMeasureDimension()方法即可。

后面有细心的读者发现,当super.onMeasure()没有被调用过时,getBaseLine()是拿不到值的,因为没有进行过测量,baseline默认返回 -1, 不过这种没有调用过super.onMeasure()的情况时在我们这个例子中是很少可能出现的,这一点就由大家自行看源码体会了~

不过为了让代码更严谨,在我们设置修改后的宽高时,还是选择调用super.onMeasure()会更好些。

修改setMeasureDimension()处的代码为:super.onMeasure(MeasureSpec.makeMeasureSpec(modifiedWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(modifiedHeight, MeasureSpec.EXACTLY));


因为我们指定的MeasureMode = EXACTLY,所以最后仍然不会影响我们测量结果,感谢这位读者提供的建议。


至此,Mode=AT_MOST的优化处理完毕,看看效果图:

11_at_most_show

那么,部曲三的优化就结束了,我们的StrokeTextView又倔强了一分~

TextView三部曲的序列就结束咯,难度我觉得不难,挺适合自定义View进阶的,希望各位同学看完后能有所收获!

最后奉上源码地址,有需要的同学自取~

没想到吧,居然还有番外篇 ~

兄dei,如果觉得我写的还不错,麻烦帮个忙呗 😃
  1. 给俺点个赞被,激励激励我,同时也能让这篇文章让更多人看见,(#.#)
  2. 不用点收藏,诶别点啊,你怎么点了?这多不好意思!

拜托拜托,谢谢各位同学!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值