今天要实现一个自动改变TextSize的TextView,以下是我的艰辛历程,不感兴趣的直接跳到结尾获取最终答案
思路: 我的计划是在onSizeChange中获取TextView的宽高,计算当前TextSize占用的高度,再不断减小文字大小直到TextView可以放下,然后将TextSize设置给TextView
说干就干首先我继承了一个textView,然后通过简单的百度我知道了如下API
/**
* Return the width of the text.
*
* @param text The text to measure. Cannot be null.
* @return The width of the text
*/
public float measureText(String text) {}
/**
* Return the recommend line spacing based on the current typeface and
* text size.
*
* <p>Note that this is the value for the main typeface, and actual text rendered may need a
* larger value because fallback fonts may get used in rendering the text.
*
* @return recommend line spacing based on the current typeface and
* text size.
*/
public float getFontSpacing() {
return getFontMetrics(null);
}
说人话就是measureText可以测量文本的宽度,getFontSpacing可以获取推荐的行距
然后就有了下面"优雅的"代码
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
String text = getText().toString();
setTextSize(getAutoTextSize(w,h,getTextSize(),text));
}
private float getAutoTextSize(int w, int h, float textSize,String text) {
Paint paint = new Paint();
paint.setTextSize(textSize);
float textW = paint.measureText(text);
float fontSpacing = paint.getFontSpacing();
if ((textW / w) > h / fontSpacing) {
return getAutoTextSize(w, h, --textSize, text);
} else {
return textSize;
}
}
正当我以为万事大吉的时候,真实的显示情况是這樣的
我来来回回的检查代码,最后在setTextSize的注释找到答案
/**
* Set the default text size to the given value, interpreted as "scaled
* pixel" units. This size is adjusted based on the current density and
* user font size preference.
*
* <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
*
* @param size The scaled pixel size.
*
* @attr ref android.R.styleable#TextView_textSize
*/
@android.view.RemotableViewMethod
public void setTextSize(float size) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
说人话就是android老哥贴心的为我们调用了另一个重载方法并且传递了一个TypedValue,TypedValue是什么呢,下面的注释给了答案
/**
* Set the default text size to a given unit and value. See {@link
* TypedValue} for the possible dimension units.
*
* <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
*
* @param unit The desired dimension unit.
* @param size The desired size in the given units.
*
* @attr ref android.R.styleable#TextView_textSize
*/
public void setTextSize(int unit, float size) {
if (!isAutoSizeEnabled()) {
setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
}
}
这里的还给了一个贴心的提示:"注意:如果此 TextView 启用了自动调整大小功能,则此功能无操作。"这个贴士很重要,后面会提到
TypedValue被称作维度单位,说白了就是一个单位.直接调用setTextSize方法的话,默认的单位是sp,那我改一下调用的方法总好了吧
我天,文字小是小了,可是并没有显示全,这时候仔细回想一下我的思路就会发现,这思路太理想化了,我们通过measureText获取的值是一行展示的时候的宽度,然而实际展示的时候TextView会在行末根据最后的字符占用宽度进行判断和换行,所以就很容易出现图中的这种情况.
这种时候作为一个优秀的搬砖工,当然是去找找看有没有轮子-----借鉴一下思路咯
然后惊喜的发现了textView的属性 autoSizeTextType , 原来android已经贴心的增加了这样的API
感觉又相信爱了
不过autoSizeTextType的使用还是有一些需要注意的点哦
1. 在Android 8.0 (API level 26) 以上,才可以使用这个属性哦,不过通过万能的拓展库可以最低支持到Android 4.0(API Level 14)及以上的系统(不过你的应用编译的targetSDKVersion必须在26及以上)。
2. 还记得上面setTextSize的注释吗,里面有一个小贴士哦,如果设置了自动大小功能,setTextSize将不起作用了,textSize的大小会根据textView的宽高自动适配
3. 在使用autoSizeTextType属性的时候宽高属性最好是固定的参数,或者设置maxHeight和maxWidth限制一下控件的宽高
4. 如果只设置了autoSizeTextType属性的话,默认的textSize是12sp~112sp,粒度为1px
知其然也要知其所以然,来看一下他是怎么实现的吧,很简单的哦,你忍一下
/**
* Performs a binary search to find the largest text size that will still fit within the size
* available to this view.
*/
private int findLargestTextSizeWhichFits(RectF availableSpace) {
final int sizesCount = mAutoSizeTextSizesInPx.length;
if (sizesCount == 0) {
throw new IllegalStateException("No available text sizes to choose from.");
}
int bestSizeIndex = 0;
int lowIndex = bestSizeIndex + 1;
int highIndex = sizesCount - 1;
int sizeToTryIndex;
while (lowIndex <= highIndex) {
sizeToTryIndex = (lowIndex + highIndex) / 2;
if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
bestSizeIndex = lowIndex;
lowIndex = sizeToTryIndex + 1;
} else {
highIndex = sizeToTryIndex - 1;
bestSizeIndex = highIndex;
}
}
return mAutoSizeTextSizesInPx[bestSizeIndex];
}
private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
final CharSequence text = mTransformed != null
? mTransformed
: getText();
final int maxLines = getMaxLines();
if (mTempTextPaint == null) {
mTempTextPaint = new TextPaint();
} else {
mTempTextPaint.reset();
}
mTempTextPaint.set(getPaint());
mTempTextPaint.setTextSize(suggestedSizeInPx);
final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right));
layoutBuilder.setAlignment(getLayoutAlignment())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
.setIncludePad(getIncludeFontPadding())
.setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
.setBreakStrategy(getBreakStrategy())
.setHyphenationFrequency(getHyphenationFrequency())
.setJustificationMode(getJustificationMode())
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setTextDirection(getTextDirectionHeuristic());
final StaticLayout layout = layoutBuilder.build();
// Lines overflow.
if (maxLines != -1 && layout.getLineCount() > maxLines) {
return false;
}
// Height overflow.
if (layout.getHeight() > availableSpace.bottom) {
return false;
}
return true;
}
可以看到findLargestTextSizeWhichFits方法就是计算TextSize的方法了,大神的代码就是优雅呢,使用了二分算法来找最合适的size
最重要的来咯, suggestedSizeFitsInSpace方法,他是用来判断当前testSize是否可以在textView中完美展示的
可以看到大神使用了StaticLayout,通过StaticLayout的getHeight()方法获取了当前testSize下占用的高度然后和展示区域的底部进行比较
ps: 还不了解StaticLayout的小伙伴,只要知道他是一个继承自Layout的可以实现自动换行的文本的布局
总结: 通过上面的源码学习可以发现,自由缩放功能是使用StaticLayout来计算占用的高度,然后不断的放大或缩小TextSize来实现的.android的源码中真是藏着很多的宝藏呢.
|