在项目中有一个小功能需要实现,就是对多行文本进行排版布局,每一行的内容又分为两部分,左边为标题,右边为描述,左边内容长度不确定,右边的内容需要对齐,如有换行也需要对齐右边的文本。
效果图
效果图如下图所示:
可以看到内容分成了两部分,左边的颜色与右边不一致,右边的描述文案统一对齐。
实现方案
以上功能,由于输入内容输入行数不确定,并且左边的文案长度也不确定,因此不能直接在布局中实现,基于此这里主要实现了以下6种方式
方案1
采用自定义控件的方式,继承TextView,重新onDraw函数,实现如下:
/**
* 计算出左边最长的显示字符串maxLeftWidth,之后draw每一行字符,右边的描述从maxLeftWidth开始draw
* 当一行显示不完全时,折行并且空出maxLeftWidth的空格长度
*/
public class TypographyView1 extends TextView {
private Paint leftPaint = new Paint();
private Paint rightPaint = new Paint();
private int fullWidth;
private float textSize;
private JSONArray array;
private int middlePadding = 0;
float maxLeftWidth = 0;
int itemSize = 0;
public TypographyView1(Context context) {
super(context);
init();
}
public TypographyView1(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TypographyView1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
textSize = getResources().getDimensionPixelSize(R.dimen.text_size_13);
leftPaint.setAntiAlias(true);
leftPaint.setTextSize(textSize);
leftPaint.setColor(getResources().getColor(R.color.color_black_999999));
rightPaint.setAntiAlias(true);
rightPaint.setTextSize(textSize);
rightPaint.setColor(getResources().getColor(R.color.color_black));
middlePadding = getResources().getDimensionPixelSize(R.dimen.padding_value);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
fullWidth = getWidth();// 整个textView的宽度
}
public void setText(JSONArray array) {
this.array = array;
if (array != null) {
try {
int size = itemSize = array.length();
for (int i = 0; i < size; ++i) {
JSONArray o = (JSONArray) array.get(i);
String key = o.getString(0);
String value = o.getString(1);
if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) {
itemSize--;
continue;
}
float curWidth = leftPaint.measureText(key);
if (curWidth > maxLeftWidth) {
maxLeftWidth = curWidth;
}
}
maxLeftWidth = maxLeftWidth + middlePadding;
invalidate();
} catch (Exception e) {
}
}
}
boolean setHeight = false;
@Override
protected void onDraw(Canvas canvas) {
if (array == null) {
return;
}
int lineCount = 0;
try {
JSONArray item;
float offsetY;
for (int i = 0; i < itemSize; ++i) {
item = (JSONArray) array.get(i);
offsetY = (lineCount + 1) * textSize;
canvas.drawText(item.getString(0), 0, offsetY, leftPaint);
String value = item.getString(1);
float valueWidth = rightPaint.measureText(value);
if (valueWidth > fullWidth - maxLeftWidth) {
// 一行显示不完
char[] textCharArray = value.toCharArray();
float charWidth;
float drawWidth = maxLeftWidth;
for (int j = 0; j < textCharArray.length; j++) {
charWidth = rightPaint.measureText(textCharArray, j, 1);
if (fullWidth - drawWidth < charWidth) {
lineCount++;
drawWidth = maxLeftWidth;
offsetY += textSize;
}
canvas.drawText(textCharArray, j, 1, drawWidth, offsetY, rightPaint);
drawWidth += charWidth;
}
} else {
canvas.drawText(value, maxLeftWidth, offsetY, rightPaint);
}
lineCount += 2;
}
if (!setHeight) {
setHeight((lineCount + 1) * (int) textSize);
setHeight = true;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
添加了setText(JSONArray array)作为数据输入,并且在这里面测量了左边title的最大宽度,之后调用invalidate触发重绘,在onSizeChanged获取整个控件的宽度,重绘会调用onDraw函数,这里不需要调用super函数,TextView的onDraw函数做了非常多的操作,解析传入的数据,分别一行一行调用canvas来进行drawText操作,当绘制描述时,先计算宽度,如果超过剩余控件说明需要换行,最后调用setHeight设置高度,这个加一个判断条件,因为会触发requestLayout()进行重新布局和invalidate()进行重绘,如果不加判断会一直重绘。
方案2
方式2与方式1差不多,不同为所有计算都在onDraw函数中:
/**
* 该方式与方式1很类似,只是所有的计算都放在了onDraw方法中。
*/
public class TypographyView2 extends TextView {
private Paint paint1 = new Paint();
private Paint paint2 = new Paint();
private int middlePadding = 0;
int width;
private float textSize;
private JSONArray array;
public TypographyView2(Context context) {
super(context);
init();
}
public TypographyView2(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TypographyView2(Co