一、TextView文本布局
TextView是Android系统中设计最复杂的View组件之一,很多View组件都继承自TextView,如Button、 EditText、DigitalClock、CheckedTextView等。其中EditText的字符输入和光标都是继承TextView,可以说TextView完全可以替代EditTex。
TextView文本布局中存在三种类型:
- BoringLayout: 只能处理单行从左到右的文本字符串,不支持Spannable或者Html
- StaticLayout: 可处理多行文本文本,支持从左到右,从右到左等文本字符串,支持Spannable和Html
- DynamicLayout: 可处理多行动态文本输入,支持从左到右和葱油到左的文本字符串,如Editor相关操作,支持Spannable和Html
以上三种文本布局功能非常重合,TextView源码中也并不是因为单行文本就会选用BoringLayout,创建文本布局时优先判断是否存在Spannable文案,如果存在则使用DynamicLayout,最后保底方案是StaticLayout,如果不存在,紧接着判断是否可以使用BoringLayout处理。
我们想象中的应该是 BoringLayout->StaticLayout->DynamicLayout ,但情况恰恰相反,StaticLayout永远是最末尾的选择,为什么会出现这种情况呢?
可能因素如下:
DynamicLayout内部本身通过StaticLayout委托实现了进行文本测量,因此可以能原因就是为了使得TextView更好扩展。
二、使用StaticLayout自定义View
StaticLayout 是一个文本测量工具,并不是View的子类,但因为提供Layout能力,因此,我们可以知道,和绘制普通View不一样的是,我们刷新View的需要调用requestLayout+invalidate() ,rquestLayout虽然大概率能出发invalidate,但有些情况可能不会,你次invalidate是必要的。
public class StaticTextView extends View {
private TextPaint mTextPaint;
private DisplayMetrics mDisplayMetrics;
private int mContentHeight = 0;
private int mContentWidth = 0;
private StaticLayout staticLayout;
public StaticTextView(Context context) {
this(context, null);
}
public StaticTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public StaticTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// ((ViewGroup)getParent()).setLayerType(View.LAYER_TYPE_SOFTWARE,null);
initPaint();
String text="在Android开发中,Canvas.drawText不会换行,即使一个很长的字符串也只会显示一行,超出部分会隐藏在屏幕之外.StaticLayout是android中处理文字的一个工具类,StaticLayout 处理了文字换行的问题";
setText(text);
}
private void initPaint() {
mDisplayMetrics = getResources().getDisplayMetrics();
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG|Paint.DITHER_FLAG);
mTextPaint.setAntiAlias(true);
mTextPaint.setStyle(Paint.Style.STROKE);
mTextPaint.setTextSize(sp2px(20));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
widthSize = mDisplayMetrics.widthPixels / 2;
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
heightSize = staticLayout!=null?staticLayout.getHeight():0;
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mContentHeight = (int) (h - dp2px(4));
mContentWidth = w;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float strokeWidth = mTextPaint.getStrokeWidth() * 2;
if (mContentWidth <= strokeWidth || mContentHeight <= strokeWidth) {
return;
}
if(staticLayout!=null){
staticLayout.draw(canvas);
}
}
public void setText(final CharSequence text) {
post(new Runnable() {
@Override
public void run() {
staticLayout = new StaticLayout(text, mTextPaint, getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
requestLayout();
}
});
}
public float dp2px(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics);
}
public float sp2px(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, dp, mDisplayMetrics);
}
}
三、TextView性能问题
虽然本文讨论的是StaticLayout用法,但是TextView性能问题也值得关注。TextView存在频繁requestLayout+invalidate问题(除去高度不变+宽度不变的情况,之所以是requestLayout+invalidate,在布局不变的情况下requestLayout触发不了重绘),此外,优先选用DynamicLayout,导致内部存在监控机制,性能上存在一些问题。