只是实例,因为Textview就带换行功能。
上一篇最后说canvas.darwText可以来绘制view,实现类似textview的显示,但是超出控件长度就会看不到,并不会换行;至于一段文字计算他在给定长度上如何换行等等很麻烦,这里只举例:如果文字一行可以容纳则一行显示,如果一行不能显示则每行只显示7个字符。
注:这里只处理wrap和match的情况,如果给指定宽度还需要去判断宽度能不能放下7个字符的问题。
这里只说自定义view的情况,自定义viewgroup的先放下:
(1).onMeasure必须做处理,当写wrap的时候必须手动计算宽高:
当一行可以容纳时,宽就是文字的宽,高就是文字的高;
//因为默认写wrap时候相当于match,所以上面测量的width其实就是match也就是屏幕的宽度,实际可以写字的区域是减去左右pading的区域
int canUseWidth = widthSize - getPaddingLeft() - getPaddingRight();
当一行不能容纳时,宽就是当前所有行文字宽度最大那行的宽度(同样7个字符,中英文不一样)+两个padding,高就是行数*行高+两个padding。
计算文字所占宽高的方法如下:
paint = new Paint();
paint.setTextSize(SizeUtil.sp2px(getContext(), 20.0f));
paint.setColor(Color.parseColor("#000000"));
paint.setAntiAlias(true);//抗锯齿
paint.setFilterBitmap(true);//位图过滤
;
rect = new Rect();
//得到写这些文字需要占据的方形区域
paint.getTextBounds(text, 0, text.length(), rect);
判断mode,如果是EXACTLY,那set的时候就是MeasureSpec.getSize的尺寸;
如果不是,那就是上面计算出来的宽高。
(2).draw文字
通过onMeasure已经计算出来warp时文字所占的区域,但是文字并没有被绘制上去。
onMeasure的时候已经知道能分几行,然后对文字也进行了分段,现在把这若干段文字分别画上去就可以了。
可能会想到onLayout,但是这是view,不是ViewGroup,所以只能多canvas.draw几次,只是每次draw的时候基线的坐标不同。
实例代码如下:
//伪换行的textview
public class MyTextview extends View {
public void setText(String text) {
this.text = text;
initView();
invalidate();
}
public MyTextview(Context context) {
super(context);
initView();
}
public MyTextview(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAtters(context, attrs);
initView();
}
public MyTextview(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAtters(context, attrs);
initView();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MyTextview(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initAtters(context, attrs);
initView();
}
private void initAtters(Context context, @Nullable AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextview);
text = array.getString(R.styleable.MyTextview_texcontent);
}
//画笔
Paint paint;
//文字所占的区域,用来确定文字的起始坐标
Rect rect;
String text = "需要显示的文字";
private void initView() {
Log.e("init里面放的数字是", text);
if (paint == null) {
paint = new Paint();
paint.setTextSize(SizeUtil.sp2px(getContext(), 20.0f));
paint.setColor(Color.parseColor("#000000"));
paint.setAntiAlias(true);
paint.setFilterBitmap(true);//位图过滤
;
}
if (rect == null) {
rect = new Rect();
}
paint.getTextBounds(text, 0, text.length(), rect);
}
String TAG = "MyTextview";
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
Log.e(TAG, "onDraw: ");
//为了让文字纵向居中,计算正确的Y值
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
//ascent的绝对值=descent+2*(文字纵向中线与基准线的高度差)
//float baseline = 0;
//Math.abs(fontMetrics.ascent) =fontMetrics.descent+2*(baseline-height/2);
if (lineNum > 1) {
//绘制文字
int startY = getPaddingTop();
for (int i = 0; i < rowStrings.size(); i++) {
Rect rect_T = new Rect();
//得到写这些文字需要占据的方形区域
paint.getTextBounds(rowStrings.get(i), 0, rowStrings.get(i).length(), rect_T);
int height = rect_T.height();
float y = height / 2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;
canvas.drawText(rowStrings.get(i), 0 + getPaddingLeft(), startY + y, paint);
startY = startY + height;
}
} else {
float y = rect.height() / 2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;
canvas.drawText(text, 0 + getPaddingLeft(), y + getPaddingTop(), paint);
}
}
List<String> rowStrings;
int lineNum;
//模拟一行可以放下就不换行,如果大于1行每行只容纳五个字符(只是模拟,实际操作还需要判断当前位置一行还能不能放下5个字符)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//得到写这些文字需要占据的方形区域
Log.e(TAG, "onMeasure: ");
rowStrings = new ArrayList<>();
lineNum = 0;
int widthMode = MeasureSpec.getMode(widthMeasureSpec); //获取宽的模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec); //获取高的模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //获取宽的尺寸
int heightSize = MeasureSpec.getSize(heightMeasureSpec); //获取高的尺寸
//判断,如果文字的长度大于宽度就换行
//因为默认写wrap时候相当于match,所以上面测量的width其实就是match也就是屏幕的宽度
int canUseWidth = widthSize - getPaddingLeft() - getPaddingRight();
if (rect.width() > canUseWidth) {
//表示一行放不下
//一行只容纳7个字符
double rows = text.length() / 7.0;
lineNum = (int) Math.ceil(rows);
//一行文字所包含的文字数
for (int i = 0; i < lineNum; i++) {
if (i == lineNum - 1) {
rowStrings.add(text.substring(i * 7, text.length()));
} else {
rowStrings.add(text.substring(i * 7, (i + 1) * 7));
}
}
} else {
lineNum = 1;
}
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
//如果match_parent或者具体的值,直接赋值
width = widthSize;
} else {
//如果是wrap_content,我们要得到控件需要多大的尺寸
float textWidth = rect.width(); //文本的宽度
//控件的宽度就是文本的宽度加上两边的内边距。内边距就是padding值,在构造方法执行完就被赋值
//根据行数来计算宽高
if (lineNum > 1) {
//计算分割的文字里面的最大宽度
width = 0;
for (String s : rowStrings) {
Rect rect_T = new Rect();
//得到写这些文字需要占据的方形区域
paint.getTextBounds(s, 0, s.length(), rect_T);
int width_T = rect_T.width();
if (width_T > width) {
width = width_T;
}
}
width = (int) (getPaddingLeft() + width + getPaddingRight());
} else {
width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
}
}
//高度跟宽度处理方式一样
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
float textHeight = rect.height();
if (lineNum > 1) {
//计算分割的文字里面的最大宽度
height = 0;
for (String s : rowStrings) {
Rect rect_T = new Rect();
//得到写这些文字需要占据的方形区域
paint.getTextBounds(s, 0, s.length(), rect_T);
int height_T = rect_T.height();
height = height + height_T;
}
height = (int) (getPaddingTop() + height + getPaddingBottom());
} else {
height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
}
Log.e("计算出来textview高度", height + "");
}
//保存测量宽度和测量高度
setMeasuredDimension(width, height);
}
}