完整效果图

android 天气折线图 天气折线图怎么画_源码下载

思路

1.构思

先看一张静态图片

android 天气折线图 天气折线图怎么画_组合控件_02


其实这就是自定义组合控件和自定义View的结合使用,黄色区域的折线部分,明显是需要自定义View的。而其它的相关信息就没什么难度了,简单的组合控件。

2.确认思路

使用RecyclerView,放置多个自定义控件WeatherView。其实最中心的计算是对当天温度圆点坐标的计算,当天的温度圆点计算完成后,就可以计算出文字的基点了。

自定义步骤

第一步 计算WeatherView的可用高度

什么是可用高度:WeatherView包含着文字,圆点,可用高度 = 总高度 - 2 * 文字高度 - 2 * 文字距离顶部/底部的marging - 2 * 文字距离圆点的marging。也就是圆点之间的最大距离(包括上下圆点)。

//baseY:代表的是 文字距离顶部/底部的距离 + 文字距离dot的距离 + 文字本身的高度
baseY = txtToBorder + txtToDot + (int) (fontMetrics.bottom - fontMetrics.ascent);

//最重要的一个数据 baseHeight: 可以看到 h - 2*baseY 其实就是在计算真实可用的温度线的高度区间
int baseHeight = h - 2 * baseY;

此处的baseHeight就是可用高度。

第二步 计算基于可用高度的每一度的高度

具体例子: WeatherView 的高度是 100,文字高度是 15, 文字距离顶部/底部距离是10, 文字距离圆点5,那么可用高度就是40,可以想见,最高温度的坐标和最低温度的坐标分别是 (w/2, 30)和(w/2 , 70)。那么 :
每一度的高度 = 可用高度 / (最高温度 - 最低温度 ).
在计算了每一度的高度之后,才能计算出圆点中心在控件中的坐标。就好像知道了最高温度10度的坐标,那么温度是8的话,通过每一度的温度高度,计算出8度所在的坐标。

第三步 根据可用高度和每一度高度的值计算出当天的圆点圆心的坐标

/**
     * 计算当天温度 dot 圆心的坐标
     *
     * @param t
     * @return
     */
    protected Point[] getTPoints(T t) {
        Point[] points = new Point[2];
        /**
         * 从View的左上角(0,0)计算,
         * baseY + dotRadiu / 2 计算出以圆点圆心为最高
         * (highestDegree - t.getHighDegree()) * degreeHeight 针对最高温度,当前温度距离最高温度的距离
         * 最终计算出高处圆点圆心的坐标
         */
        int highY = baseY + dotRadiu / 2 + (highestDegree - t.getHighDegree()) * degreeHeight;
        Point highPoint = new Point(w / 2, highY);
        //通过高处圆点坐标,计算出低温圆点的坐标
        int lowY = highY + (t.getHighDegree() - t.getLowDegree()) * degreeHeight;
        Point lowPoint = new Point(w / 2, lowY);
        points[0] = highPoint;
        points[1] = lowPoint;
        return points;
    }

第四步 根据圆心坐标,画出圆点,计算文本的基点坐标

画出圆点:

protected void drawDots(Canvas canvas, Point[] tPoints) {
        dotPaint.setColor(highDotColor);
        canvas.drawCircle(tPoints[0].x, tPoints[0].y, dotRadiu, dotPaint);
        dotPaint.setColor(lowDotColor);
        canvas.drawCircle(tPoints[1].x, tPoints[1].y, dotRadiu, dotPaint);
    }

计算文本的基点坐标:

/**
     * 计算温度文字的基线的起始点
     *
     * @param t
     * @param tPoints
     * @return
     */
    protected Point[] getTxtBaseLinePoint(T t, Point[] tPoints) {
        Point[] baseLinePoints = new Point[2];
        Point middelHighPoint = tPoints[0];
        int disY = dotRadiu / 2 + txtToDot;//文字基点距离圆心的距离
        int highBaseLineY = middelHighPoint.y - disY;//高处文本的基点Y坐标
        int highBaseLineX = (w - (int) txtPaint.measureText(t.getHighDegree() + "")) / 2;//高处文本的基点X坐标
        Point highBaseLinePoint = new Point(highBaseLineX, highBaseLineY);
        Point middleLowPoint = tPoints[1];
        int textHeight = Math.abs((int) (fontMetrics.bottom - fontMetrics.ascent));
        int lowBaseLineY = middleLowPoint.y + disY + textHeight / 3 * 2;//低处文本的基点Y坐标
        int lowBaseLineX = (w - (int) txtPaint.measureText(t.getLowDegree() + "")) / 2;//低处文本的基点X坐标
        Point lowBaseLinePoint = new Point(lowBaseLineX, lowBaseLineY);
        baseLinePoints[0] = highBaseLinePoint;
        baseLinePoints[1] = lowBaseLinePoint;
        return baseLinePoints;
    }

 protected void drawTexts(Canvas canvas, T t, Point[] txtBaseLinePoints) {
        txtPaint.setColor(highTXTColor);
        canvas.drawText(t.getHighDegree() + "", txtBaseLinePoints[0].x, txtBaseLinePoints[0].y, txtPaint);
        txtPaint.setColor(lowTXTColor);
        canvas.drawText(t.getLowDegree() + "", txtBaseLinePoints[1].x, txtBaseLinePoints[1].y, txtPaint);
    }

第五步 计算当天,昨天,明天,三天的圆点圆心坐标计算出当天左右两边的连接坐标

计算左右连接点的坐标,第一个参数都是当天的圆点坐标

/**
     * 计算当天line左侧 endX endY 的坐标
     *
     * @param tPoints
     * @param preT
     * @return
     */
    protected Point[] getLeftPoints(Point[] tPoints, T preT) {
        Point[] points = new Point[2];
        Point[] preTPoints = getTPoints(preT);
        //算出当天圆心坐标和昨天圆心坐标的中心连接点
        points[0] = new Point(0, (tPoints[0].y + preTPoints[0].y) / 2);
        points[1] = new Point(0, (tPoints[1].y + preTPoints[1].y) / 2);
        return points;
    }

    /**
     * 计算当天line右侧 endX endY 的坐标
     *
     * @param tPoints
     * @param nextT
     * @return
     */
    protected Point[] getRightPoints(Point[] tPoints, T nextT) {
        Point[] points = new Point[2];
        Point[] nextTPoints = getTPoints(nextT);
        points[0] = new Point(w, (tPoints[0].y + nextTPoints[0].y) / 2);
        points[1] = new Point(w, (tPoints[1].y + nextTPoints[1].y) / 2);
        return points;
    }

第六步 根据第五步的坐标,画出温度折线

//只要 position >0 都需要画左边的连线
        if (position > 0) {
            T preT = datas.get(position - 1);
            Point[] leftPoints = getLeftPoints(tPoints, preT);
            drawLines(canvas, tPoints, leftPoints, 0);
        }

        //只要 position 不是最后一个 都需要画右边的连线
        if (position < datas.size() - 1) {
            T nextT = datas.get(position + 1);
            Point[] rightPoints = getRightPoints(tPoints, nextT);
            drawLines(canvas, tPoints, rightPoints, 1);
        }

/**
     * 跟据坐标点画温度线
     * direct 0 代表左边    1 代表右边
     * @param canvas
     * @param tPoints
     * @param points
     */
    protected void drawLines(Canvas canvas, Point[] tPoints, Point[] points, int direct) {
        linePaint.setStrokeWidth(lineStrokeWidth);
        if (direct == 0)
            linePaint.setColor(leftHighLineColor);
        else
            linePaint.setColor(rightHighLineColor);
        canvas.drawLine(tPoints[0].x, tPoints[0].y, points[0].x, points[0].y, linePaint);
        if (direct == 0)
            linePaint.setColor(leftLowLineColor);
        else
            linePaint.setColor(rightLowLineColor);
        canvas.drawLine(tPoints[1].x, tPoints[1].y, points[1].x, points[1].y, linePaint);
    }

最后

至此,基本的功能就已经实现了,剩下的就是扩展性的参数和方法,文章最后有源码下载,上面描述中不太清楚的可以下载看看。

源码下载

WeatherView