推荐学习资料
-
Android进阶学习全套手册
-
Android对标阿里P7学习视频
-
BAT TMD大厂Android高频面试题
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
}
});
一.拆行
监听到文本变化后需要对文本进行拆行,得到每行的文字。我们可以通过Layout实现,代码如下:
String text = layout.getText().toString();
int lineCount = layout.getLineCount();
for (int i = 0; i < lineCount; i++) {
int start = layout.getLineStart(i);
int end = layout.getLineEnd(i);
String rowStr = text.substring(start,end);
}
但是,但是,但是,这里有一个点需要特别注意,不能通过EditText自带的Layout来计算每行文本,不然拿到的每行文本是错误的。
为什么呢?
举个例子:
如下图所示,当你在第二行将要输入“好”时,因为你输入"好"后该行文本宽度已经大于此时EditText的宽度了,所以“好”字会被认为是重启一行,这样你得到的每行文本就是错的了,因为“好”应该显示在第二行才对。
这就涉及到我在思路探究中提到的第一个问题,无论我字体怎么缩小放大,如何保证每行最多可显示的文本都是一样的?
那么如何保证呢?
其实很简单,因为影响到文字自动换行的因素主要就是字体大小和最大文本宽度,那么只要保证这两个因素不变,无论你输入什么文本,都能准确一致的拆分出每一行的文字。
因为EditText的每行字体在变,而且宽度也在变,所以通过EditText自带的Layout算出的每行文本肯定是错误的。
所以,思路应该是这样的,你需要构建一个用于计算的Layout,这个Layout的字体大小和宽度必须是固定不变的,这样它就能够保证每行最多可容纳的文本始终是一样的,这样我就能够准确拆分出每行文本。
前面已经说过EditText的计算工作都是交给DynamicLayout,所以我们需要创建的是DynamicLayout。代码如下
protected Layout buildCalculateLayout(CharSequence text,TextView host){
TextPaint paint = new TextPaint(host.getPaint());
paint.setTextSize(mDefFontSize);
return new DynamicLayout(text,paint, mDefMaxTextWidth,host.getLayout().getAlignment(),host.getLayout().getSpacingMultiplier(),host.getLayout().getSpacingAdd(),host.getIncludeFontPadding());
}
需要注意的是,这里除了字体大小和宽度,其他的参数都需要跟EditText的参数一样。
其中mDefFontSize是一开始定义的一个默认字体大小,mDefMaxTextWidth是EditText在没动态调整宽度前的宽度(需要减去padding)。
这样子每次都是通过自构建的Layout去计算每行的文本,就不需要考虑EditText的字体和宽度的动态变化。
二.按匹配最大宽度计算每行字体大小
搞定了第一步拆行后,其实已经离成功不远了,接下来就是如何确定每行字体大小了。
确定字体大小说简单简单,说难也难,关键是看你有没有想到那个点。比如一开始我一直纠结于每行文字是怎么随着输入文字个数和行数变化动态改变的,陷入了局部细节,搞得自己晕头转向,如果按照这个方向思考我感觉估计是怎么做都搞不定的。
后来,想了两天后还是没搞明白,我就试着换个思维方式,从整体来考虑,接下来就有种恍然大悟的感觉,原来其实没那么难。
首先,有一个规律是很显然的:
每行文字越多,它的字体就越小,文字越少,字体就越大。
那么我就想一开始时你把每行文字的宽度放大到最大文本宽度,算出匹配这个宽度的字体应该多大,这样文字越少的行,字体就越大,文字越多的行,字体就越小,这个不就是符合那个规律吗。
计算文本宽度的代码如下:
float width = paint.measureText(text);
因为需要通过不断更改字体大小,去算出匹配最大宽度的字体,所以为了减少计算量,一开始可以做一个初始字体大小的换算。
当字体大小是mDefFontSize时对应的文本宽度是mDefMaxTextWidth,那么当文本宽度是x时,对应的字体大小是y,因为字体大小和宽度成反比(宽度越小,字体越大),所以y的计算公式就是:
y = mDefMaxTextWidth \ x * mDefFontSize
x = paint.measureText(text);
这样我们就可以得到一个比较接近目标值的字体大小,这时候再去判断此时文本宽度是否匹配最大文本宽度,不等于的话再去改变字体大小,直到文本宽度匹配最大文本宽度为止。
代码如下:
public float calculateMatchWidthSize(Paint paint,String text,int maxWidth){
float textSize = paint.getTextSize();
float width = paint.measureText(text);
if(maxWidth >= width && maxWidth - width <= text.length()){
return textSize;
}
if(width > maxWidth){
textSize = getNarrowFitTextSize(paint,text,maxWidth,1);
}else{
textSize = getZoomFitTextSize(paint,text,maxWidth,1);
}
return textSize;
}
private float getNarrowFitTextSize(Paint paint,String text,int maxWidth,float rate){
float textSize = paint.getTextSize();
textSize -= 1 * rate;
paint.setTextSize(textSize);
float width = paint.measureText(text);
if(maxWidth >= width && maxWidth - width <= text.length()){
return textSize;
}
//结束条件
if(width < maxWidth){
return getZoomFitTextSize(paint,text,maxWidth,rate);
}else{
return getNarrowFitTextSize(paint,text,maxWidth,rate);
}
}
private float getZoomFitTextSize(Paint paint,String text,int maxWidth,float rate){
float textSize = paint.getTextSize();
textSize += 1 * rate;
paint.setTextSize(textSize);
float width = paint.measureText(text);
if(maxWidth >= width && maxWidth - width <= text.length()){
return textSize;
}
//结束条件
if(width < maxWidth){
return getZoomFitTextSize(paint,text,maxWidth,rate);
}else{
return getNarrowFitTextSize(paint,text,maxWidth,rate);
}
}
三.按匹配最大高度计算每行字体大小
按照匹配最大宽度计算出来的字体会很大,导致文本高度很高,这时候就需要再动态调整每行字体大小,直到文本高度匹配最大高度为止。
动态调整字体大小时,每行文字的字体大小需要按比例调整,比如每行字体都调整为原来的0.9倍大小。
计算每行文本高度的代码:
int height = paint.getFontMetricsInt(null);
为了让每行文本高度的累加值等于文本实际总高度,需要设置EditText的边距为0并且去掉文字上下的空白部分。代码如下:
//去掉文本上下空白区域
mHost.setIncludeFontPadding(false);
//不设置行间距
mHost.setLineSpacing(0,1);
为了提高计算速度,采用二分法来动态调整字体大小,代码如下:
/**
- 二分法查找合适的字体大小,字体大小按比例调整
- @return
*/
private void calculateMatchHeightSizeByRate(float lowRate,float highRate,int minHeight,int maxHeight){
if(highRate - lowRate <= RATE_SCALE_ERROR_VALUE){
return;
}
float middleRate= (lowRate+highRate)/2;
scaleFontSizeByRate(middleRate);
int height = getTextHeight();
if(height > maxHeight){
//缩小字体后文字高度大于最大值,需要继续缩小字体
highRate = middleRate;
calculateMatchHeightSizeByRate(lowRate,highRate,minHeight,maxHeight);
} else if(height < minHeight){
//缩小字体后文字高度小于最小值,需要放大字体
lowRate = middleRate;
calculateMatchHeightSizeByRate(lowRate,highRate,minHeight,maxHeight);
}
}
private int getTextHeight(){
int totalHeight = 0;
for(CustomSpanData customSpanData: mCustomTextSpanDataList){
int lineHeight = getSingleLineHeight(customSpanData.getTextSize());
totalHeight += lineHeight;
}
return totalHeight;
}
private int getSingleLineHeight(float fontSize){
Paint paint = new Paint(mHost.getPaint());
paint.setTextSize(fontSize);
return paint.getFontMetricsInt(null);
}
private void scaleFontSizeByRate(float rate){
for(int i=0;i<mOriFontSizePxList.size();i++){
新的开始
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
《系列学习视频》
《系列学习文档》
《我的大厂面试之旅》
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!