Android自定义View程序员Android进阶之旅

Android 公交线路 VerticalStepView

2018-01-20  本文已影响143人  SwitchLife

前言

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

效果图


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

立即体验

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


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

实现

  1. 创建一个普通的自定义view
    我们给它命名为VerticalStepView,并创建两个画笔:
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

上一篇下一篇

猜你喜欢

热点阅读