公司做一个类似公交站牌的文字竖排效果,自己捉摸了一下,想想估计能实现,就算不能实现,权当学习了。
先看看做出来的效果图:
先说说思路:
整体为绘制圆,绘制间隔线,然后是绘制数字与文字
1.绘制圆和间隔线:
绘制这两个使用了Canvas.drawPath(Path path,Paint paint),因为这样子便于计算,只需要计算Path的路径,其它的就交给canvas可以了,path支持绘制circle(path.addCircle())与绘制line(path.lineto),本来想文本也用path绘制的,不过最后发现绘制文本只有canvas支持,path不支持。剩下的就是计算绘制起始点了
- /**
- * 计算点圆与线轨迹
- */
- private void computeCircleAndLinePath() {
- if (data != null || data.size() != 0) {
- circlePoints = new Point[data.size()];
- circlePaths = new Path[data.size()];
- linePaths = new Path[data.size() - 1];
- //处理文字显示与圆点显示
- //绘制起始偏移量,如果文本高度一半大于半径,则取差值,否则取0
- int offsetX = textHeight / 2 > (circleRadius + circleStokeWidth / 2) ? textHeight / 2 - (circleRadius + circleStokeWidth / 2) : 0;
- //处理paddingLeft
- offsetX += getPaddingLeft();
- for (int i = 0; i < data.size(); i++) {
- Path circlePath = new Path();
- //计算每个圆点的中心点
- int mCircleCenterX = i * lineLength + circleRadius + circleStokeWidth / 2 + offsetX;
- int mCircleCenterY = circleMarginTop + circleRadius;
- circlePoints[i] = new Point(mCircleCenterX, mCircleCenterY);
- //计算圆点路径
- circlePath.addCircle(mCircleCenterX, mCircleCenterY, circleRadius, Path.Direction.CCW);
- circlePaths[i] = circlePath;
- if (i == data.size() - 1) {
- continue;
- }
- //计算线段路径
- Path linePath = new Path();
- //计算线段起始点
- int mlineStartX = mCircleCenterX + circleRadius + circleStokeWidth / 2;
- int mlineEndX = (i + 1) * lineLength + offsetX;
- linePath.moveTo(mlineStartX, mCircleCenterY);
- linePath.lineTo(mlineEndX, mCircleCenterY);
- linePaths[i] = linePath;
- }
- }
- }
/**
* 计算点圆与线轨迹
*/
private void computeCircleAndLinePath() {
if (data != null || data.size() != 0) {
circlePoints = new Point[data.size()];
circlePaths = new Path[data.size()];
linePaths = new Path[data.size() - 1];
//处理文字显示与圆点显示
//绘制起始偏移量,如果文本高度一半大于半径,则取差值,否则取0
int offsetX = textHeight / 2 > (circleRadius + circleStokeWidth / 2) ? textHeight / 2 - (circleRadius + circleStokeWidth / 2) : 0;
//处理paddingLeft
offsetX += getPaddingLeft();
for (int i = 0; i < data.size(); i++) {
Path circlePath = new Path();
//计算每个圆点的中心点
int mCircleCenterX = i * lineLength + circleRadius + circleStokeWidth / 2 + offsetX;
int mCircleCenterY = circleMarginTop + circleRadius;
circlePoints[i] = new Point(mCircleCenterX, mCircleCenterY);
//计算圆点路径
circlePath.addCircle(mCircleCenterX, mCircleCenterY, circleRadius, Path.Direction.CCW);
circlePaths[i] = circlePath;
if (i == data.size() - 1) {
continue;
}
//计算线段路径
Path linePath = new Path();
//计算线段起始点
int mlineStartX = mCircleCenterX + circleRadius + circleStokeWidth / 2;
int mlineEndX = (i + 1) * lineLength + offsetX;
linePath.moveTo(mlineStartX, mCircleCenterY);
linePath.lineTo(mlineEndX, mCircleCenterY);
linePaths[i] = linePath;
}
}
}
上述代码整体为计算这两个circle与line的起始点,圆的大小,线段的长度都由样式定制来决定,同时记录了每个圆的中心点,每个圆的路径,每个线的路径。
还有一个offsetX,至于这个变量是考虑了因为要定制样式,有字体大于圆的情况,这样的话就会存在文字大小与圆半径之间存在偏移,如果不考虑他,有可能我们的效果会出现第一个条目的文字只显示一半,所以我记录了这个偏移,把他加入了起始点的计算
这里需要注意,android绘制circle时结合了Paint画笔的笔触宽度strokeWidth,为圆半径+strokeWidth/2,所以我们在
Path.addCircle(float x,float y,float radius,Direction dir)时需要考虑传入的半径为设定的圆半径+strokeWidth/2
最后使用canvas绘制即可:
- /**
- * 绘制圆和线段
- */
- private void drawCircleAndLine(Canvas canvas) {
- for (int i = 0; i < circlePaths.length; i++) {
- //改变颜色与风格
- if (i == currSelectedItem) {
- circlePaint.setColor(circleSelectColor);
- circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
- } else {
- circlePaint.setStyle(Paint.Style.STROKE);
- circlePaint.setColor(circleColor);
- }
- canvas.drawPath(circlePaths[i], circlePaint);
- }
- for (int i = 0; i < linePaths.length; i++) {
- canvas.drawPath(linePaths[i], linePaint);
- }
- }
/**
* 绘制圆和线段
*/
private void drawCircleAndLine(Canvas canvas) {
for (int i = 0; i < circlePaths.length; i++) {
//改变颜色与风格
if (i == currSelectedItem) {
circlePaint.setColor(circleSelectColor);
circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
} else {
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setColor(circleColor);
}
canvas.drawPath(circlePaths[i], circlePaint);
}
for (int i = 0; i < linePaths.length; i++) {
canvas.drawPath(linePaths[i], linePaint);
}
}
2.绘制数字和文本:
因为Path不能绘制文本,只能用canvas来绘制,canvas提供了大概三种类型的方法:
canvas.drawText();
canvas.drawPostText();(已废弃)
canvas.drawTextonPath() 该方法不好控制文本的方向以及文本的样式
所以采用drawText()方法
- /**
- * 绘制数字和文本
- */
- private void drawNumberAndItemText(Canvas canvas) {
- for (int i = 0; i < data.size(); i++) {
- //数字绘制起始点
- int mNumberStartX = circlePoints[i].x;
- int mNumberStartY = circlePoints[i].y + circleRadius + numberMarginTop;
- //测量数字宽度
- float textWidth = numberPaint.measureText(String.valueOf(i + 1), 0, String.valueOf(i + 1).length());
- //改变颜色
- if (i == currSelectedItem) {
- numberPaint.setColor(numberSelectColor);
- } else {
- numberPaint.setColor(numberColor);
- }
- canvas.drawText(String.valueOf(i + 1), mNumberStartX - textWidth / 2, mNumberStartY, numberPaint);
- drawItemText(canvas);
- }
- }
/**
* 绘制数字和文本
*/
private void drawNumberAndItemText(Canvas canvas) {
for (int i = 0; i < data.size(); i++) {
//数字绘制起始点
int mNumberStartX = circlePoints[i].x;
int mNumberStartY = circlePoints[i].y + circleRadius + numberMarginTop;
//测量数字宽度
float textWidth = numberPaint.measureText(String.valueOf(i + 1), 0, String.valueOf(i + 1).length());
//改变颜色
if (i == currSelectedItem) {
numberPaint.setColor(numberSelectColor);
} else {
numberPaint.setColor(numberColor);
}
canvas.drawText(String.valueOf(i + 1), mNumberStartX - textWidth / 2, mNumberStartY, numberPaint);
drawItemText(canvas);
}
}
因为为竖直排列,所以要判断每个字的宽度,高度,同时还的考虑每个字之间的间隔,所以确定每个字的范围很有必要
- /**
- * 绘制文本
- */
- private void drawItemText(Canvas canvas) {
- //记录每个条目区域
- textRegions = new Region[data.size()];
- for (int i = 0; i < data.size(); i++) {
- Region region = new Region();
- int mItemStartX = circlePoints[i].x;
- int mItemStartY = circlePoints[i].y + circleRadius + numberMarginTop + textMarginTop;
- String text = data.get(i);
- char[] chars = text.toCharArray();
- //每个条目区域的计算
- Rect textArea = new Rect();
- textArea.left = mItemStartX - textHeight / 2;
- textArea.right = textArea.left + textHeight;
- //减去一个文字高度,因为绘制文字是在baseline上方绘制,基线位置为drawText(text,x,y,paint)的y位置
- textArea.top = mItemStartY - textHeight;
- int lastTextHeight = 0;
- //改变颜色
- if (i == currSelectedItem) {
- textPaint.setColor(textSelectColor);
- } else {
- textPaint.setColor(textColor);
- }
- for (int j = 0; j < chars.length; j++) {
- canvas.drawText(String.valueOf(text.charAt(j)), mItemStartX - textHeight / 2, mItemStartY + lastTextHeight, textPaint);
- lastTextHeight += textHeight + textSpace;
- }
- //此处减去最后一个文字间隔
- textArea.bottom = textArea.top + lastTextHeight - textSpace;
- region.set(textArea);
- textRegions[i] = region;
- }
- }
/**
* 绘制文本
*/
private void drawItemText(Canvas canvas) {
//记录每个条目区域
textRegions = new Region[data.size()];
for (int i = 0; i < data.size(); i++) {
Region region = new Region();
int mItemStartX = circlePoints[i].x;
int mItemStartY = circlePoints[i].y + circleRadius + numberMarginTop + textMarginTop;
String text = data.get(i);
char[] chars = text.toCharArray();
//每个条目区域的计算
Rect textArea = new Rect();
textArea.left = mItemStartX - textHeight / 2;
textArea.right = textArea.left + textHeight;
//减去一个文字高度,因为绘制文字是在baseline上方绘制,基线位置为drawText(text,x,y,paint)的y位置
textArea.top = mItemStartY - textHeight;
int lastTextHeight = 0;
//改变颜色
if (i == currSelectedItem) {
textPaint.setColor(textSelectColor);
} else {
textPaint.setColor(textColor);
}
for (int j = 0; j < chars.length; j++) {
canvas.drawText(String.valueOf(text.charAt(j)), mItemStartX - textHeight / 2, mItemStartY + lastTextHeight, textPaint);
lastTextHeight += textHeight + textSpace;
}
//此处减去最后一个文字间隔
textArea.bottom = textArea.top + lastTextHeight - textSpace;
region.set(textArea);
textRegions[i] = region;
}
}
文本的宽度,高度可以采用如下方法获得:
- /**
- * 计算文本信息,包含每个条目的宽度,高度
- */
- private Rect getTextInfo(String text, Paint paint) {
- Rect mrect = new Rect();
- //获得文本的最小矩形大小,也是测量文本高度,宽度的一种方法
- paint.getTextBounds(text, 0, text.length(), mrect);
- return mrect;
- }
/**
* 计算文本信息,包含每个条目的宽度,高度
*/
private Rect getTextInfo(String text, Paint paint) {
Rect mrect = new Rect();
//获得文本的最小矩形大小,也是测量文本高度,宽度的一种方法
paint.getTextBounds(text, 0, text.length(), mrect);
return mrect;
}
文本宽度也可以使用Paint.measureText(String text,int start,int end)获得
这里需要注意:
android绘制文字的时候,使用drawText(String text,float x,float y,Paint paint)时,大家注意y坐标的位置实际上为绘制文字的baseline位置,在基线位置之上开始绘制
最后因为在布局文件使用了scrollView,torizontalScrollView,所以在View.onMeasure()计算了整个view占据的最小空间大小,如果不这样计算,在绘制view的时候会无法显示,因为scrollview传递给子布局的大小为0
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- //此处为与HorizontalScrollView搭配使用,达到滑动目的,别的滑动方式请修改此处代码或者删除
- int mViewWidth = getPaddingLeft() + getPaddingRight() + computeMinViewWidth();
- int mViewHeight = getPaddingBottom() + getPaddingTop() + computeMinViewHeight();
- setMeasuredDimension(mViewWidth, mViewHeight);
- }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//此处为与HorizontalScrollView搭配使用,达到滑动目的,别的滑动方式请修改此处代码或者删除
int mViewWidth = getPaddingLeft() + getPaddingRight() + computeMinViewWidth();
int mViewHeight = getPaddingBottom() + getPaddingTop() + computeMinViewHeight();
setMeasuredDimension(mViewWidth, mViewHeight);
}
- /**
- * 计算View最小宽度
- */
- private int computeMinViewWidth() {
- int viewWidth = 0;
- if (data != null || data.size() != 0) {
- //计算文本高度,起始不管第一条是否为空,高度都会与其它文本保持一致
- Rect textRect = getTextInfo(data.get(0), textPaint);
- textHeight = textRect.height();
- viewWidth = (data.size() - 1) * lineLength + 2 * circleRadius + circleStokeWidth;
- if (textHeight > (2 * circleRadius + circleStokeWidth)) {
- int offsetX = textHeight / 2 - (circleRadius + circleStokeWidth / 2);
- viewWidth = viewWidth + 2 * offsetX;
- }
- }
- return viewWidth;
- }
- /**
- * 计算View最小高度
- */
- private int computeMinViewHeight() {
- int viewHeight = 0;
- if (data != null || data.size() != 0) {
- String maxText = "";
- for (int i = 0; i < data.size(); i++) {
- //计算文本宽度
- maxText = data.get(i).length() > maxText.length() ? data.get(i) : maxText;
- }
- //获得总文本间距
- int textSpaceWidth = textSpace * (maxText.length() - 1);
- Rect textRect = getTextInfo(maxText, textPaint);
- //最大文本高度
- int textMaxHeight = textRect.width() + textSpaceWidth;
- //计算ViewHeight
- viewHeight = textMaxHeight + textMarginTop + numberMarginTop + (2 * circleRadius + circleStokeWidth) + circleMarginTop;
- }
- return viewHeight;
- }
/**
* 计算View最小宽度
*/
private int computeMinViewWidth() {
int viewWidth = 0;
if (data != null || data.size() != 0) {
//计算文本高度,起始不管第一条是否为空,高度都会与其它文本保持一致
Rect textRect = getTextInfo(data.get(0), textPaint);
textHeight = textRect.height();
viewWidth = (data.size() - 1) * lineLength + 2 * circleRadius + circleStokeWidth;
if (textHeight > (2 * circleRadius + circleStokeWidth)) {
int offsetX = textHeight / 2 - (circleRadius + circleStokeWidth / 2);
viewWidth = viewWidth + 2 * offsetX;
}
}
return viewWidth;
}
/**
* 计算View最小高度
*/
private int computeMinViewHeight() {
int viewHeight = 0;
if (data != null || data.size() != 0) {
String maxText = "";
for (int i = 0; i < data.size(); i++) {
//计算文本宽度
maxText = data.get(i).length() > maxText.length() ? data.get(i) : maxText;
}
//获得总文本间距
int textSpaceWidth = textSpace * (maxText.length() - 1);
Rect textRect = getTextInfo(maxText, textPaint);
//最大文本高度
int textMaxHeight = textRect.width() + textSpaceWidth;
//计算ViewHeight
viewHeight = textMaxHeight + textMarginTop + numberMarginTop + (2 * circleRadius + circleStokeWidth) + circleMarginTop;
}
return viewHeight;
}
大概就是这样子,基本上都是一点点绘制的,暂时没有想到别的办法,如有好的思路,请提供
下面附上源码下载地址:
http://download.csdn.net/download/kongzuoding/9403338
大家可以下载看看效果,如有错误,或者更好的建议,请指出