最近产品有个需求,要求做个自定义的每行带下划线的记事本,类似我们的实体笔记本。这不是很简单麽?(实现详见另一篇文章<假装link>待添加</假装link>)。结果在测试时发现了一个问题:
SVID_system.gif
现象: 当API28+后,textview中出现中文时,该单行高度和textview的总高度会增加。与此同时,当含有中文文本内容行的中文被删除时,该行高度和总view高度发生改变,但是textview中其他行文字的绘制位置没有发生变化。这个情况只有当某一行完全被移除时才会刷新正常!
由此总结了API28+的一个bug:当edittext(textview)内容行数不变时,修改某一行的内容,只刷新改动的那一行;当行数变化时,才会刷新改变的那一行及下方的内容,所以显示就会发生错位。
以下是两种解决方案:
方案1:
保留API28+的特性(含中文内容的行高比英文高)。当内容变化时,强制全部文本刷新。经测试,用invalidate()无法实现刷新,所以我们监听textview的内容变化,每次重新setText。
private void initView() {
//android9和10的行高问题的解决暂时解决方案
//会造成开销,最好还是想办法把中英文的行高固定下来(降低中文行高)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
soluteLineHeightMethod1();
}
}
private void soluteLineHeightMethod1() {
this.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//...
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//...
}
@Override
public void afterTextChanged(Editable s) {
//...
if(!isFallbackLineSpacing()){
return;
}
//先关监听
removeTextChangedListener(this);
//处理内容
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
setText(s);
setSelection(selectionStart, selectionEnd);
//再开监听
addTextChangedListener(this);
}
});
}
效果如图:
SVID_method1.gif
此方法效率会稍微低下,更根源的问题是在Layout,这里要重写的太多,有兴趣的可以自行研究。在staticLayout中,有对高度进行处理:
微信图片_20211018141554.png
手动监听行高,变化时让该行后面的内容全部刷新重绘。
方案2:
查看源码,找到API28+后引起行高变化的原因。这个肯定是28+后有新增attr引起的。经查看源码,发现textview中有个mUseFallbackLineSpacing属性,这个属性导致了当异国语言出现时行距会发生变化。在xml中手动将其设为false,或者初始化自定义Textview时将其强行设为false,都可以强制让每行的高度相同。此时的行高等于getLineHeight获取到的高度
private void initView() {
setFocusableInTouchMode(true);
//android9和10的行高问题的解决暂时解决方案
//会造成开销,最好还是想办法把中英文的行高固定下来(降低中文行高)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
soluteLineHeightMethod2();
}
}
private void soluteLineHeightMethod2() {
setFallbackLineSpacing(false);
}
效果图:
SVID_method2.gif