热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android公交线路VerticalStepView

前言  本篇主要给大家写一个展示公交线路的自定义view,直接上效果图。效果图看到上面的图是不是有些紧张?别慌,蛋定,蛋定!每个站都由几个部分组成:绘制序列号背景圆圈绘制序列号绘制

前言

  本篇主要给大家写一个展示公交线路的自定义view,直接上效果图。

效果图

《Android 公交线路 VerticalStepView》

《Android 公交线路 VerticalStepView》

看到上面的图是不是有些紧张?别慌,蛋定,蛋定!每个站都由几个部分组成:

  • 绘制序列号背景圆圈
  • 绘制序列号
  • 绘制车站名称
  • 绘制上、转、下
    按照上列绘制顺序,以序列号背景圆圈的中心点作为基点进行绘制。
    接下来我们看一下具体实现。

立即体验

扫描以下二维码下载体验App(从0.2.3版本开始,体验App内嵌版本更新检测功能):

《Android 公交线路 VerticalStepView》

JSCKit库传送门:https://github.com/JustinRoom/JSCKit

实现

  1. 创建一个普通的自定义view
    我们给它命名为VerticalStepView,并创建两个画笔:
  • Paint paint—-普通画笔。
  • TextPaint textPaint—-文字画笔。
    VerticalStepView.java

public class VerticalStepView extends View {
//普通画笔
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//文字画笔
private TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
public VerticalStepView(Context context) {
this(context, null);
}
public VerticalStepView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}

  1. 创建数据模型JavaBean。根据每个站的组成部分,我们不难定义出数据模型,我们给它命名RouteViewPoint。数据模型里的每个字段基本上都有注释,自行看注释理解。
    RouteViewPoint.java

public class RouteViewPoint {
public static final int DEFAULT_TEXT_COLOR = 0xFF333333;
private int key;//标识键(这个属性相当于一张数据表的主键字段,辅助你完成一些其他的操作)
private int basicX;//基点(X轴方向坐标)
private int basicY;//基点(Y轴方向坐标)
//这是序列号的背景圆一些相关属性
private float radius = 10;//圆半径
private int borderColor = DEFAULT_TEXT_COLOR;//圆框颜色
private int borderWidth = 2;//圆框粗细
private int backgroundColor = Color.WHITE;//圆背景颜色
private int distance;//与前一个圆的距离
//序号
private String index;
private int indexColor = DEFAULT_TEXT_COLOR;
private float indexSize;
private boolean indexBold;
//公交
private String transit;
private int transitColor = DEFAULT_TEXT_COLOR;
private float transitSize;
private boolean transitBold;//是否加粗显示
//上、转、下
private String cursor;
private int cursorColor = DEFAULT_TEXT_COLOR;
private float cursorSize;
private boolean cursorBold;//是否加粗显示
//起点、站、终点
private String label;
private int labelColor = DEFAULT_TEXT_COLOR;
private float labelSize;
private boolean labelBold;//是否加粗显示
}

  1. 测量VerticalStepView的宽高。我们要根据数据测量出View的宽高,否则即使绘制完毕也看不见效果。这里主要测量高度。
    数据集:
    private List points = new ArrayList<>();
    高度 = getPaddingTop() + 第一个圆的半径 + 所有圆之间的距离和 + 最后一个圆的半径 + getPaddingBottom()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int offsetY = getPaddingTop();
int startX = (int) (getPaddingLeft() + 10 + getMaxTransitWidth() + getMaxRadius() + 0.5f);
if (points.size() > 1) {
for (int i = 0; i RouteViewPoint p = points.get(i);
if (i == 0)
offsetY += p.getRadius();
if (i > 0)
offsetY += p.getDistance();
p.setBasicX(startX);
p.setBasicY(offsetY);
if (i == points.size() - 1)
offsetY += p.getRadius();
}
} else if (points.size() == 1) {
RouteViewPoint p = points.get(0);
offsetY += p.getRadius();
p.setBasicY(offsetY);
offsetY += p.getRadius();
}
offsetY += getPaddingBottom();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(offsetY, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

  1. onDraw(Canvas canvas)方法中绘制数据。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画连接起所有圆的那条竖线:从第一个圆中心到最后一个圆的中心
if (points.size() > 1) {
paint.setStrokeWidth(lineWidth);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(lineColor);
canvas.drawLine(
points.get(0).getBasicX(),
points.get(0).getBasicY(),
points.get(points.size() - 1).getBasicX(),
points.get(points.size() - 1).getBasicY(),
paint
);
}
//绘制所有数据
for (RouteViewPoint p : points) {
drawCircle(canvas, p, paint);
drawIndex(canvas, p, textPaint);
drawLabel(canvas, p, 10, textPaint);
drawTransit(canvas, p, 10, textPaint);
drawCursor(canvas, p, 4, textPaint);
}
}

  • 绘制序列号背景圆圈:

/**
* 画圆
*
* @param canvas
* @param p
* @param paint
*/
private void drawCircle(Canvas canvas, RouteViewPoint p, Paint paint) {
if (p == null)
return;
paint.setColor(p.getBackgroundColor());
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(p.getBasicX(), p.getBasicY(), p.getRadius(), paint);
paint.setColor(p.getBorderColor());
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(p.getBorderWidth());
canvas.drawCircle(p.getBasicX(), p.getBasicY(), p.getRadius(), paint);
}

  • 绘制序列号:

/**
* 画序号
*
* @param canvas
* @param p
* @param textPaint
*/
private void drawIndex(Canvas canvas, RouteViewPoint p, TextPaint textPaint) {
if (p == null)
return;
String index = p.getIndex();
if (index == null || index.length() == 0)
return;
textPaint.setColor(p.getIndexColor());
textPaint.setTextSize(p.getIndexSize());
textPaint.setTypeface(Typeface.defaultFromStyle(p.isIndexBold() ? Typeface.BOLD : Typeface.NORMAL));
textPaint.getTextBounds(index, 0, index.length(), textBoundRect);
Paint.FontMetrics fOntMetrics= textPaint.getFontMetrics();
int w = textBoundRect.right - textBoundRect.left;
int h = textBoundRect.bottom - textBoundRect.top;
float xStart = p.getBasicX() - w / 2.0f;
float baseLine = p.getBasicY() - h / 2.0f + (h - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
canvas.drawText(index, xStart, baseLine, textPaint);
}

  • 绘制站点名称:

/**
* 画标签
*
* @param canvas
* @param p
* @param marginLeft
* 文字与圆左边的距离
* @param textPaint
*/
private void drawLabel(Canvas canvas, RouteViewPoint p, int marginLeft, TextPaint textPaint) {
if (p == null)
return;
String label = p.getLabel();
if (label == null || label.length() == 0)
return;
textPaint.setColor(p.getLabelColor());
textPaint.setTextSize(p.getLabelSize());
textPaint.setTypeface(Typeface.defaultFromStyle(p.isLabelBold() ? Typeface.BOLD : Typeface.NORMAL));
textPaint.getTextBounds(label, 0, label.length(), textBoundRect);
Paint.FontMetrics fOntMetrics= textPaint.getFontMetrics();
int w = textBoundRect.right - textBoundRect.left;
int h = textBoundRect.bottom - textBoundRect.top;
float xStart = p.getBasicX() + getMaxRadius() + marginLeft;
float baseLine = p.getBasicY() - h / 2.0f + (h - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
canvas.drawText(label, xStart, baseLine, textPaint);
}

  • 绘制上、转、下:

/**
* 画上、转、下
*
* @param canvas
* @param p
* @param marginRight
* 文字与圆右边的距离
* @param textPaint
*/
private void drawTransit(Canvas canvas, RouteViewPoint p, int marginRight, TextPaint textPaint) {
if (p == null)
return;
String transit = p.getTransit();
if (transit == null || transit.length() == 0)
return;
textPaint.setColor(p.getTransitColor());
textPaint.setTextSize(p.getTransitSize());
textPaint.setTypeface(Typeface.defaultFromStyle(p.isTransitBold() ? Typeface.BOLD : Typeface.NORMAL));
textPaint.getTextBounds(transit, 0, transit.length(), textBoundRect);
Paint.FontMetrics fOntMetrics= textPaint.getFontMetrics();
int w = textBoundRect.right - textBoundRect.left;
int h = textBoundRect.bottom - textBoundRect.top;
float xStart = p.getBasicX() - w - p.getRadius() - marginRight;
float baseLine = p.getBasicY() - h / 2.0f + (h - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
canvas.drawText(transit, xStart, baseLine, textPaint);
}

  • 绘制游标:

/**
* 画游标
*
* @param canvas
* @param p
* @param marginLeft
* 文字与圆右边的距离
* @param textPaint
*/
private void drawCursor(Canvas canvas, RouteViewPoint p, int marginLeft, TextPaint textPaint) {
if (p == null)
return;
String cursor = p.getCursor();
if (cursor == null || cursor.length() == 0)
return;
textPaint.setColor(p.getCursorColor());
textPaint.setTextSize(p.getCursorSize());
textPaint.setTypeface(Typeface.defaultFromStyle(p.isCursorBold() ? Typeface.BOLD : Typeface.NORMAL));
textPaint.getTextBounds(cursor, 0, cursor.length(), textBoundRect);
Paint.FontMetrics fOntMetrics= textPaint.getFontMetrics();
int w = textBoundRect.right - textBoundRect.left;
int h = textBoundRect.bottom - textBoundRect.top;
float xStart = p.getBasicX() + getMaxRadius() + marginLeft;
float newBasicY = p.getBasicY() + p.getRadius() + h / 2.0f;
float baseLine = newBasicY - h / 2.0f + (h - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
// float xStart = p.getBasicX() + getMaxRadius() + marginLeft + 10 + rect.right - rect.left;
// float baseLine = p.getBasicY() - h / 2.0f + (h - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
canvas.drawText(cursor, xStart, baseLine, textPaint);
}

自此,整个VeritcalStepView就完成了。

用法

这里使用百度地图API路线规划举个栗子:

/**
* 同城路线
*
* @param routeLine
* @return
*/
private List getSameCityPoints(MassTransitRouteLine routeLine) {
float txt10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, itemView.getResources().getDisplayMetrics());
List points = new ArrayList<>();
List> steps = routeLine.getNewSteps();
for (int i = 0; i List subSteps = steps.get(i);
MassTransitRouteLine.TransitStep transitStep = subSteps.get(0);
RouteViewPoint start = getBasePoint();
RouteViewPoint end = getBasePoint();
start.setIndex(String.valueOf(i + 1));
end.setIndex(String.valueOf(i + 2));
switch (transitStep.getVehileType()) {
case ESTEP_WALK:
start.setLabel("起点");
start.setLabelColor(0xFF999999);
RouteViewPoint center = getBasePoint();
center.setKey(100);
center.setRadius(10);
center.setLabelColor(0xFF999999);
center.setLabel(transitStep.getInstructions());
center.setLabelSize(txt10);
end.setLabel("终点");
end.setLabelColor(0xFF999999);
if (i == 0) {
points.add(start);
points.add(center);
} else if (i == steps.size() - 1) {
points.add(center);
points.add(end);
} else {
points.add(center);
}
break;
case ESTEP_TRAIN:
case ESTEP_DRIVING:
case ESTEP_COACH:
case ESTEP_PLANE:
case ESTEP_BUS:
BusInfo busInfo = transitStep.getBusInfo();
start.setKey(-1);
start.setCursor(getBuses(subSteps));
start.setLabel(getStationName(busInfo.getDepartureStation()));
points.add(start);
int stopNum = busInfo.getStopNum() - 1;
if (stopNum <0)
stopNum = 0;
for (int j = 0; j RouteViewPoint stopPoint = getBasePoint();
stopPoint.setKey(101);
stopPoint.setRadius(10);
stopPoint.setBackgroundColor(0xFF00BA86);
stopPoint.setBorderColor(0xFF00BA86);
stopPoint.setLabelColor(0xFF999999);
stopPoint.setLabel("•••");
stopPoint.setLabelSize(txt10);
points.add(stopPoint);
}
end.setKey(1);
end.setLabel(getStationName(busInfo.getArriveStation()));
points.add(end);
break;
}
}
for (int i = 0; i if (i > 0) {
RouteViewPoint p = points.get(i);
RouteViewPoint pre = points.get(i - 1);
switch (p.getKey()) {
case 101:
p.setDistance(pre.getKey() == 101 ? 30 : 80);
break;
case 100:
if (pre.getKey() <100)
p.setDistance(80);
break;
default:
if (pre.getKey() == 101 ||
pre.getKey() == 100)
p.setDistance(80);
break;
}
}
}
return points;
}
/**
* 跨城路线
* @param routeLine
* @return
*/
private List getDifferentCityPoints(MassTransitRouteLine routeLine) {
float txt10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, itemView.getResources().getDisplayMetrics());
List points = new ArrayList<>();
List> steps = routeLine.getNewSteps();
for (int i = 0; i List subSteps = steps.get(i);
for (int j = 0; j MassTransitRouteLine.TransitStep transitStep = subSteps.get(j);
RouteViewPoint start = getBasePoint();
RouteViewPoint end = getBasePoint();
start.setIndex(String.valueOf(i + 1));
end.setIndex(String.valueOf(i + 2));
switch (transitStep.getVehileType()) {
case ESTEP_WALK:
start.setLabel("起点");
start.setLabelColor(0xFF999999);
RouteViewPoint center = getBasePoint();
center.setKey(100);
center.setRadius(10);
center.setLabelColor(0xFF999999);
center.setLabel(transitStep.getInstructions());
center.setLabelSize(txt10);
end.setLabel("终点");
end.setLabelColor(0xFF999999);
if (i == 0) {
points.add(start);
points.add(center);
} else if (i == steps.size() - 1) {
points.add(center);
points.add(end);
} else {
points.add(center);
}
break;
case ESTEP_TRAIN:
case ESTEP_DRIVING:
case ESTEP_COACH:
case ESTEP_PLANE:
case ESTEP_BUS:
BusInfo busInfo = transitStep.getBusInfo();
start.setKey(-1);
start.setCursor(getBuses(subSteps));
start.setLabel(getStationName(busInfo.getDepartureStation()));
points.add(start);
int stopNum = busInfo.getStopNum() - 1;
if (stopNum <0)
stopNum = 0;
for (int k = 0; k RouteViewPoint stopPoint = getBasePoint();
stopPoint.setKey(101);
stopPoint.setRadius(10);
stopPoint.setBackgroundColor(0xFF00BA86);
stopPoint.setBorderColor(0xFF00BA86);
stopPoint.setLabelColor(0xFF999999);
stopPoint.setLabel("•••");
stopPoint.setLabelSize(txt10);
points.add(stopPoint);
}
end.setKey(1);
end.setLabel(getStationName(busInfo.getArriveStation()));
points.add(end);
break;
}
}
}
for (int i = 0; i if (i > 0) {
RouteViewPoint p = points.get(i);
RouteViewPoint pre = points.get(i - 1);
switch (p.getKey()) {
case 101:
p.setDistance(pre.getKey() == 101 ? 30 : 80);
break;
case 100:
if (pre.getKey() <100)
p.setDistance(80);
break;
default:
if (pre.getKey() == 101 ||
pre.getKey() == 100)
p.setDistance(80);
break;
}
}
}
return points;
}
private RouteViewPoint getBasePoint() {
float textSize10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, itemView.getResources().getDisplayMetrics());
float textSize12 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, itemView.getResources().getDisplayMetrics());
RouteViewPoint p = new RouteViewPoint();
p.setDistance(120);
p.setRadius(20);
p.setBackgroundColor(0xFFCCCCCC);
p.setBorderColor(0xFFCCCCCC);
p.setIndexSize(textSize10);
p.setCursorSize(textSize12);
p.setCursorColor(0xFFFF00FF);
p.setTransitSize(textSize12);
p.setTransitColor(0xFF999999);
p.setLabelSize(textSize12);
return p;
}
private String getBuses(List subSteps) {
if (subSteps == null || subSteps.isEmpty())
return "";
StringBuilder builder = new StringBuilder();
builder.append("乘坐|▷ ");
for (int i = 0; i BusInfo busInfo = subSteps.get(i).getBusInfo();
builder.append(busInfo.getName().replace("路", ""));
if (i builder.append(" • ");
}
return builder.toString();
}
private String getStationName(String station) {
if (station == null || station.length() == 0)
return "";
if (station.endsWith("站站"))
return station.substring(0, station.length() - 1);
else
return station;
}

MassTransitRouteLine这个是百度地图API里的跨城路线规划的JavaBean。

结尾

本人纯属Android菜鸟,欢迎各路大神指点!路过的童鞋,要是觉得还不错的,动动你金贵的小手指加个关注,后期我会把觉得对你们有所小帮助的Android技术写成文章共享给大家,谢谢!QQ:1006368252


推荐阅读
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • 在项目部署后,Node.js 进程可能会遇到不可预见的错误并崩溃。为了及时通知开发人员进行问题排查,我们可以利用 nodemailer 插件来发送邮件提醒。本文将详细介绍如何配置和使用 nodemailer 实现这一功能。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • 在 Android 开发中,通过 Intent 启动 Activity 或 Service 时,可以使用 putExtra 方法传递数据。接收方可以通过 getIntent().getExtras() 获取这些数据。本文将介绍如何使用 RoboGuice 框架简化这一过程,特别是 @InjectExtra 注解的使用。 ... [详细]
  • 反向投影技术主要用于在大型输入图像中定位特定的小型模板图像。通过直方图对比,它能够识别出最匹配的区域或点,从而确定模板图像在输入图像中的位置。 ... [详细]
  • 解决Anaconda安装TensorFlow时遇到的TensorBoard版本问题
    本文介绍了在使用Anaconda安装TensorFlow时遇到的“Could not find a version that satisfies the requirement tensorboard”错误,并提供详细的解决方案,包括创建虚拟环境和配置PyCharm项目。 ... [详细]
  • 本文详细探讨了JavaScript中的作用域链和闭包机制,解释了它们的工作原理及其在实际编程中的应用。通过具体的代码示例,帮助读者更好地理解和掌握这些概念。 ... [详细]
  • 丽江客栈选择问题
    本文介绍了一道经典的算法题,题目涉及在丽江河边的n家特色客栈中选择住宿方案。两位游客希望住在色调相同的两家客栈,并在晚上选择一家最低消费不超过p元的咖啡店小聚。我们将详细探讨如何计算满足条件的住宿方案总数。 ... [详细]
  • 本教程详细介绍了如何使用 TensorFlow 2.0 构建和训练多层感知机(MLP)网络,涵盖回归和分类任务。通过具体示例和代码实现,帮助初学者快速掌握 TensorFlow 的核心概念和操作。 ... [详细]
  • C#设计模式学习笔记:观察者模式解析
    本文将探讨观察者模式的基本概念、应用场景及其在C#中的实现方法。通过借鉴《Head First Design Patterns》和维基百科等资源,详细介绍该模式的工作原理,并提供具体代码示例。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • Java 实现二维极点算法
    本文介绍了一种使用 Java 编程语言实现的二维极点算法。该算法用于从一组二维坐标中筛选出极点,适用于需要处理几何图形和空间数据的应用场景。文章不仅详细解释了算法的工作原理,还提供了完整的代码示例。 ... [详细]
  • 历经三十年的开发,Mathematica 已成为技术计算领域的标杆,为全球的技术创新者、教育工作者、学生及其他用户提供了一个领先的计算平台。最新版本 Mathematica 12.3.1 增加了多项核心语言、数学计算、可视化和图形处理的新功能。 ... [详细]
  • 本文总结了优化代码可读性的核心原则与技巧,通过合理的变量命名、函数和对象的结构化组织,以及遵循一致性等方法,帮助开发者编写更易读、维护性更高的代码。 ... [详细]
author-avatar
闌珊脃_
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有