很多人在做天气开发app的时候经常需要做到温度折线图
简单地对自定义的View做个详解
首先一个要弄懂使用canvas画什么,一个温度折现图其实就“画”3样东西,温度点,数值,折线段。另外一个很重要的注意点就是位置,处理不好的话很容易出现折线图不在视图中。
首先复写onMeasure方法
google已经封装好了,直接调用即可,有兴趣可以去看看resolveSize的源码,
setMeasuredDimension(resolveSize(mDefaultWidth,widthMeasureSpec),resolveSize(mDefaultHeight,heightMeasureSpec));
另外,在layout中使用的最好把layout_width和layout_height两个参数设置为wrap_parent
每个温度点对应的都是一个View,如图:
pointX设置为View宽度的一半,即:pointX = viewWidth / 2;
pointY比较难了,因为要考虑到一组数据最大值最小值有可能相差很大,数值与数值之间的差距大小问题,这里我这样处理:
首先我们知道这个View的宽高都是wrap_parent,它要配合RecyclerView使用,这个RecyclerView在layout中的高可以设置为270dp,然后添加许多自定义的View到适配器中,
自定义View的高度设为220dp:
令pointTopY<pointY<pointBottomY,那么就可以让温度点落在这个区间内。
现在有一组数据:24、18、22、19、23、24、26、28
其实算法很简单,一组数据取平均值(这里是23)让这个平均值位于中间的位置,即(pointTopY+pointBottomY) / 2 的位置,然后最大值在最上方,最小值在最下方,其他数值位于他们之间。之所以使用(pointBottomY-pointTopY) * 1f / (maxValue - minValue)去乘是因为:如果最大值最小值差别特别大,那么点与点之间的pointY的差别就比较小,如果最大值最小值差别不大,那么点与点之间的pointT的差别就大些。
接下来是绘制折线,绘制一条线段只要弄懂它的初始坐标即可,如图:
其实我们只需要绘制点1→a1
a1→点2
点2→a2
…
a4→点5
这些线段,整个折线其实就绘制出来了,a1的坐标怎么知道呢?很容易可以看出,它的x坐标是viewWidth,它的y坐标是点1与点2坐标相加除2。然后第一个View与最后一个View分别绘制右线段和左线段,中间夹着的View绘制左右线段就完成了。
总结来说,第一个View绘制右线段,最后一个View绘制左线段,之间的所有View绘制左右线段,所有线段组合起来就是折线了。
最后贴上所有代码:
public class TemperatureView extends View {
private static final String TAG = “TemperatureView”;
private int minValue; private int maxValue; private int currentValue; private int lastValue; private int nextValue; private Paint mPaint; private int viewHeight; private int viewWidth; private int pointX; private int pointY; private boolean isDrawLeftLine; private boolean isDrawRightLine; private int pointTopY = (int) (40 * Util.getDensity(getContext())); private int pointBottomY = (int) (200 * Util.getDensity(getContext())); private int mMiddleValue; public TemperatureView(Context context) { super(context); } public TemperatureView(Context context, AttributeSet attrs) { super(context, attrs); } public TemperatureView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } //设置最小值 public void setMinValue(int minValue){ this.minValue = minValue; } //设置最大值 public void setMaxValue(int maxValue){ this.maxValue = maxValue; } //设置目前的值 public void setCurrentValue(int currentValue){ this.currentValue = currentValue; } //设置是否画左边线段(只有第一个View是false) public void setDrawLeftLine(boolean isDrawLeftLine){ this.isDrawLeftLine = isDrawLeftLine; } //设置是否画右边线段(只有最后一个View是false) public void setDrawRightLine(boolean isDrawRightLine){ this.isDrawRightLine = isDrawRightLine; } //设置之前温度点的值 public void setLastValue(int lastValue){ this.lastValue = lastValue; } //设置下一个温度点的值 public void setNextValue(int nextValue){ this.nextValue = nextValue; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //给一个初始长、宽 int mDefaultWidth = 200; int mDefaultHeight = (int) (220 * Util.getDensity(getContext())); setMeasuredDimension(resolveSize(mDefaultWidth,widthMeasureSpec),resolveSize(mDefaultHeight,heightMeasureSpec)); viewHeight = getMeasuredHeight(); viewWidth = getMeasuredWidth(); pointX = viewWidth / 2; Log.d(TAG, "onMeasure: " + viewWidth); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); mMiddleValue = (pointTopY + pointBottomY) / 2; pointY = mMiddleValue + (int) ((pointBottomY-pointTopY) * 1f / (maxValue - minValue) * ((maxValue + minValue) / 2 - currentValue)); Log.d(TAG, "onDraw: " + pointY); mPaint = new Paint(); drawGraph(canvas); drawValue(canvas); drawPoint(canvas); } //绘制数值 private void drawValue(Canvas canvas){ mPaint.setTextSize(40); setTextColor(); mPaint.setStrokeWidth(0); mPaint.setStyle(Paint.Style.FILL); mPaint.setTextAlign(Paint.Align.CENTER); canvas.drawText(currentValue+"°",pointX , pointY - 20, mPaint); } //设置字体颜色 public void setTextColor(){ if(currentValue <= 10 && currentValue >= 0){ mPaint.setColor(Color.BLUE); }else if(currentValue <= 20 && currentValue > 10){ mPaint.setColor(Color.GREEN); }else if(currentValue <= 30 && currentValue > 20){ mPaint.setColor(0xFFFF8000); }else if(currentValue <= 40 && currentValue > 30){ mPaint.setColor(Color.RED); } } //绘制温度点 public void drawPoint(Canvas canvas){ mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(2); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(pointX, pointY, 10, mPaint); mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(pointX, pointY, 5, mPaint); } //绘制线段(线段组成折线) public void drawGraph(Canvas canvas){ mPaint.setColor(0xFF24C3F1); mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(3); mPaint.setAntiAlias(true); //设置抗锯齿 //判断是否画左线段(第一个View不用,其他全要) if(isDrawLeftLine){ float middleValue = currentValue - (currentValue - lastValue) / 2f; float middleY = mMiddleValue + (int) ((pointBottomY-pointTopY) * 1f / (maxValue - minValue) * ((maxValue + minValue) / 2 - middleValue)); canvas.drawLine(0, middleY, pointX, pointY, mPaint); } //判断是否画右线段(最后View不用,其他全要) if(isDrawRightLine){ float middleValue = currentValue - (currentValue - nextValue) / 2f; float middleY = mMiddleValue + (int) ((pointBottomY-pointTopY) * 1f / (maxValue - minValue) * ((maxValue + minValue) / 2 - middleValue)); canvas.drawLine(pointX, pointY, viewWidth, middleY, mPaint); } }
}
最后把demo链接贴出来:https://github.com/lyx19970504/TemperatureView