完整效果图
思路
1.构思
先看一张静态图片
其实这就是自定义组合控件和自定义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