Android 公交线路 VerticalStepView

作者: SwitchLife | 来源:发表于2018-01-20 23:10 被阅读143次

    前言

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

    效果图


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

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

    立即体验

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


    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<RouteViewPoint> 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 < points.size(); 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<RouteViewPoint> getSameCityPoints(MassTransitRouteLine routeLine) {
                float txt10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, itemView.getResources().getDisplayMetrics());
                List<RouteViewPoint> points = new ArrayList<>();
                List<List<MassTransitRouteLine.TransitStep>> steps = routeLine.getNewSteps();
                for (int i = 0; i < steps.size(); i++) {
                    List<MassTransitRouteLine.TransitStep> 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 < stopNum; 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 < points.size(); 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<RouteViewPoint> getDifferentCityPoints(MassTransitRouteLine routeLine) {
                float txt10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, itemView.getResources().getDisplayMetrics());
                List<RouteViewPoint> points = new ArrayList<>();
                List<List<MassTransitRouteLine.TransitStep>> steps = routeLine.getNewSteps();
                for (int i = 0; i < steps.size(); i++) {
                    List<MassTransitRouteLine.TransitStep> subSteps = steps.get(i);
                    for (int j = 0; j < subSteps.size(); 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 < stopNum; 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 < points.size(); 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<MassTransitRouteLine.TransitStep> subSteps) {
                if (subSteps == null || subSteps.isEmpty())
                    return "";
    
                StringBuilder builder = new StringBuilder();
                builder.append("乘坐|▷ ");
                for (int i = 0; i < subSteps.size(); i++) {
                    BusInfo busInfo = subSteps.get(i).getBusInfo();
                    builder.append(busInfo.getName().replace("路", ""));
                    if (i < subSteps.size() - 1)
                        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

    相关文章

      网友评论

        本文标题:Android 公交线路 VerticalStepView

        本文链接:https://www.haomeiwen.com/subject/fvxxaxtx.html