前言
在项目中当使用TextView显示多行文本内容时,效果如下:
可以发现会非常的难看,右边的文字无法做到对齐控件边缘,既然系统默认的TextView实现不了,就尝试在网上找到了一个左右对齐的TextView的开源控件AlignTextView,GitHub地址为:https://github.com/androiddevelop/AlignTextView
在使用中发现该开源控件存在一个问题,那就是在显示中英文混合文本和英文文本时,很大几率上会出现末尾的单词被分隔换行显示,如下图所示:
会什么会出现这种情况?
通过研究该开源控件的源码发现其实现原理是:
1,首先就算出一个中文字体所占的宽度
2,计算出AlignTextView控件的宽度,然后用控件宽度除以中文字体宽度得到一行最多显示多少字;
3,对字体排布进行重新计算。
这里有个问题就是作者只计算了中文字体所占的宽度,但是标点符号还有英文字体的宽度和中文宽度是不一样的。
如果想避免出现这个问题,就必须判断一个单词是否会被换行这种情况的存在,感觉实现起来有点困难,但是很明显可以知道默认的Textview是不会出现这种将一个单词分行的处理,既然这样就查看下源码看看如何实现这么个功能的,然后借鉴一下。
发现默认的TextView内部有一个很重要的成员变量StaticLayout,textview之所以能够实现一行显示多少文字不会导致单词分割换行就是该类进行处理的,既然google爸爸已经帮我们实现了这么好用的类那我们就直接拿来用就可以了,然后结合AlignTextView的左右对齐思路,便有了如下代码,代码很简洁100行不到:
public class AlignTextView extends AppCompatTextView {
private boolean alignOnlyOneLine;
public AlignTextView(Context context) {
this(context, null);
}
public AlignTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AlignTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AlignTextView);
alignOnlyOneLine = typedArray.getBoolean(R.styleable.AlignTextView_alignOnlyOneLine, false);
typedArray.recycle();
}
protected void onDraw(Canvas canvas) {
TextPaint paint = getPaint();
paint.setColor(getCurrentTextColor());
paint.drawableState = getDrawableState();
CharSequence content = getText();
if (!(content instanceof String)) {
super.onDraw(canvas);
return;
}
String text = (String) content;
Layout layout = getLayout();
for (int i = 0; i < layout.getLineCount(); ++i) {
int lineBaseline = layout.getLineBaseline(i) + getPaddingTop();
int lineStart = layout.getLineStart(i);
int lineEnd = layout.getLineEnd(i);
if (alignOnlyOneLine && layout.getLineCount() == 1) {//只有一行
String line = text.substring(lineStart, lineEnd);
float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, paint);
this.drawScaledText(canvas, line, lineBaseline, width, paint);
} else if (i == layout.getLineCount() - 1) {//最后一行
canvas.drawText(text.substring(lineStart), getPaddingLeft(), lineBaseline, paint);
break;
} else {//中间行
String line = text.substring(lineStart, lineEnd);
float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, paint);
this.drawScaledText(canvas, line, lineBaseline, width, paint);
}
}
}
private void drawScaledText(Canvas canvas, String line, float baseLineY, float lineWidth, TextPaint paint) {
if (line.length() < 1) {
return;
}
float x = getPaddingLeft();
boolean forceNextLine = line.charAt(line.length() - 1) == 10;
int length = line.length() - 1;
if (forceNextLine || length == 0) {
canvas.drawText(line, x, baseLineY, paint);
return;
}
float d = (getMeasuredWidth() - lineWidth - getPaddingLeft() - getPaddingRight()) / length;
for (int i = 0; i < line.length(); ++i) {
String c = String.valueOf(line.charAt(i));
float cw = StaticLayout.getDesiredWidth(c, paint);
canvas.drawText(c, x, baseLineY, paint);
x += cw + d;
}
}
}
这里用到了一个自定义属性alignOnlyOneLine:
<declare-styleable name="AlignTextView">
<attr name="alignOnlyOneLine" format="boolean"/>
</declare-styleable>
该属性的意思就是说当textview只有一行的时候是否需要实现对齐,比方说在实际开发中这种UI展示效果:
如果将alignOnlyOneLine设置为true,“存储金”,“备注”就能轻松做到平分一行的ui效果,如果换成传统实现方式,可能就需要分别为“存”“储”“金”“备”“注”分别分配textview,然后通过居左居右居中等不同对齐方式实现这种布局效果,使用aligntext加alignOnlyOneLine属性可以很方便实现。
最后附上一个使用我们自己写的alignTextView的效果图:
使用建议:
虽然开源控件AlignTextView能够实现左右对齐,但是这能说明可以在项目中大量使用该控件去代替TextView吗,答案是不可以代替Textview,因为它里面的for循环计算每行字符个数的过程是一个比较耗性能的过程,改进后的AlignTextView由于使用google提供的staticlayout所以每行多少个字符就不用自己操心,这方面性能会比开源控件要好,唯一消耗点性能的可能就是计算每行的间隙了,但即使这样在没有左右对齐要求的情况下还是尽量使用TextView比较好。
扩展:
在开发中,经常会用到比如说类似于表格的排版,效果图如下:
一般情况下我们都会采用中间添加空格的方式,但是这种方式实现起来有时候不是很完美,这里给大家介绍一种新的方式,借助如下的占位符:
 
 
 
 
他们与汉字换算关系就是:1个汉字 = 4个  = 4个  = 1个  = 2个 
如:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是测试:"
android:textSize="22sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密        码:"
android:textSize="22sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密  码:"
android:textSize="22sp" />
效果为:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是测试:"
android:textSize="22sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小  密  码:"
android:textSize="22sp" />
效果为: