记录一个温度曲线的View
最近做项目需求的看到需要自定义一个温度曲线的图。由于之前的同事理解需求的时候没有很好的理解产品的需求,将温度的折线图分成了两个View,温度高的在一个View,温度低的在一个View。这样的做法其实是没有很好的理解产品的需求的。为什么这么说,因为一旦拆成两个View,那么哪些相交的点绘制就会有缺陷了。什么意思,看图。
如果按照两个View去做,就会有这种局限性。相交的点就会被切。所以这里就重新修改了这个自定义View。
有了上面的需求,那么就开始我们的设计了。首先为了我们自定义View的能比较好的通用性,我们需要把一些可能会变的东西提取出来。这里只是提取一些很常用的属性,其余需要自定义的,可自己加上。直接看代码
<declare-styleable name="NewWeatherChartView">
<!--开始的x坐标-->
<attr name="new_start_point_x" format="dimension"/>
<!--两点之间x坐标的间隔-->
<attr name="new_point_x_margin" format="dimension"/>
<!--显示温度的字体大小-->
<attr name="temperature_text_size" format="dimension"/>
<!--圆点的半径-->
<attr name="point_radius" format="dimension"/>
<!--选中天气项,温度字体的颜色-->
<attr name="select_temperature_text_color" format="reference|color"/>
<!--未选中天气项,温度字体的颜色-->
<attr name="unselect_temperature_text_color" format="reference|color"/>
<!--选中天气项,圆点的颜色-->
<attr name="select_point_color" format="reference|color"/>
<!--未选中天气项,圆点的颜色-->
<attr name="unselect_point_color" format="reference|color"/>
<!--连接线的颜色-->
<attr name="line_color" format="reference|color"/>
<!--连接线的类型,可以是实线,也可以是虚线,默认是虚线。0虚线,1实线-->
<attr name="line_type" format="integer"/>
</declare-styleable>
public class NewWeatherChartView extends View {
private final static String TAG = "NewWeatherChartView";
private List<WeatherInfo> items;//温度的数据源
//都是可以在XML里面配置的属性,目前项目里面都是用的默认配置。
private int mLineColor;
private int mSelectTemperatureColor;
private int mUnSelectTemperatureColor;
private int mSelectPointColor;
private int mUnselectPointColor;
private int mLineType;
private int mTemperatureTextSize;
private int mPointStartX = 0;
private int mPointXMargin = 0;
private int mPointRadius;
private Point[] mHighPoints; //高温的点的坐标
private Point[] mLowPoints; //低温的点的坐标
//这里是为了方便写代码,多创建了几个画笔,也可以用一个画笔,然后配置不同的属性
private Paint mLinePaint; //用于画线画笔
private Paint mTextPaint; // 用于画小圆点旁边的温度文字的画笔
private Paint mCirclePaint;//用来画小圆点的画笔
private Float mMaxTemperature = Float.MIN_VALUE;//最高温度
private Float mMinTemperature = Float.MAX_VALUE;//最低温度
private Path mPath;//连接线的路径
private DecimalFormat mDecimalFormat;
private int mTodayIndex = -1;//用于判断哪一个被选中
private Context mContext;
...
}
以上就是一些初始化的东西了,那么现在就来思考一下,怎么去画这些东西,上面的初始化也说明了,我们主要是画线,画文字,然后画圆点。那么应该从哪开始呢?首先是从点坐标开始,因为无论是线,还是文字,他们的位置和点都有关系。那么找到点的坐标就是首要的工作。怎么找点的坐标,以及最开始的X坐标是多少。第一个点的X坐标是根据我们的配置来的,那么第二个点的x坐标呢?,第二个点的x坐标就是第一个点的x坐标加上他们之间的在X方向上距离,而在x方向上的距离也是根据属性配置的。所以我们可以很容易得到所有点的x坐标。那么圆点的y坐标呢?首先我们看一张图。
我们的点,应该是均匀分布在剩余高度里面的。
剩余高度 = 控件高度-2*文字的高度。
点的y坐标为
*剩余高度-((当前温度-最低温度)/(最高温度-最低温度)剩余高度)+文字高度
看起来有点复杂,但是有公式的话,代码会比较简单。接下来就需要看初始化的代码了和计算点坐标的代码了
代码如下:
//首先从两个参数的构造函数里面获取各种配置的值
public NewWeatherChartView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NewWeatherChartView);
mPointStartX = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_new_start_point_x, 0);
mPointXMargin = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_new_point_x_margin, 0);
mTemperatureTextSize = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_temperature_text_size, 20);
mPointRadius = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_point_radius, 8);
mSelectPointColor = typedArray.getColor(R.styleable.NewWeatherChartView_select_point_color, context.getResources().getColor(R.color.weather_select_point_color));
mUnselectPointColor = typedArray.getColor(R.styleable.NewWeatherChartView_unselect_point_color, context.getResources().getColor(R.color.weather_unselect_point_color));
mLineColor = typedArray.getColor(R.styleable.NewWeatherChartView_line_color, context.getResources().getColor(R.color.weather_line_color));
mSelectTemperatureColor = typedArray.getColor(R.styleable.NewWeatherChartView_select_temperature_text_color, context.getResources().getColor(R.color.weather_select_temperature_color));
mUnSelectTemperatureColor = typedArray.getColor(R.styleable.NewWeatherChartView_unselect_temperature_text_color, context.getResources().getColor(R.color.weather_unselect_temperature_color));
mLineType = typedArray.getInt(R.styleable.NewWeatherChartView_line_type, 0);
this.mContext = context;
typedArray.recycle();
}
private void initData() {
//初始化线的画笔
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(2);
mLinePaint.setDither(true);
//配置虚线
if (mLineType == 0) {
DashPathEffect pathEffect = new DashPathEffect(new float[]{10, 5}, 1);
mLinePaint.setPathEffect(pathEffect);
}
mPath = new Path();
//初始化文字的画笔
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(sp2px(mTemperatureTextSize));
mTextPaint.setTextAlign(Paint.Align.CENTER);
// 初始化圆点的画笔
mCirclePaint = new Paint();
mCirclePaint.setStyle(Paint.Style.FILL);
mDecimalFormat = new DecimalFormat("0");
for (int i = 0; i < items.size(); i++) {
float highY = items.get(i).getHigh();
float lowY = items.get(i).getLow();
if (highY > mMaxTemperature) {
mMaxTemperature = highY;
}
if (lowY < mMinTemperature) {
mMinTemperature = lowY;
}
if (DateUtil.fromTodayDate(items.get(i).getDate()) == 0) {
mTodayIndex = i;
}
}
float span = mMaxTemperature - mMinTemperature;
//这种情况是为了防止所有温度都一样的情况
if (span == 0) {
span = 6.0f;
}
mMaxTemperature = mMaxTemperature + span / 6.0f;
mMinTemperature = mMinTemperature - span / 6.0f;
mHighPoints = new Point[items.size()];
mLowPoints = new Point[items.size()];
}
public int sp2px(float spValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, Resources.getSystem().getDisplayMetrics());
}
public int dip2px(float dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, Resources.getSystem().getDisplayMetrics());
}
这些准备工作昨晚之后,我们就可以去onDraw里面画图了。
protected void onDraw(Canvas canvas) {
Logging.d(TAG, "onDraw: ");
if (items == null) {
return;
}
int pointX = mPointStartX; // 开始的X坐标
int textHeight = sp2px(mTemperatureTextSize);//文字的高度
int remainingHeight = getHeight() - textHeight * 2;//除去文字后,剩余的高度
// 计算每一个点的X和Y坐标
for (int i = 0; i < items.size(); i++) {
int x = pointX + mPointXMargin * i;
float highTemp = items.get(i).getHigh();
float lowTemp = items.get(i).getLow();
int highY = remainingHeight - (int) (remainingHeight * ((highTemp - mMinTemperature) / (mMaxTemperature - mMinTemperature))) + textHeight;
int lowY = remainingHeight - (int) (remainingHeight * ((lowTemp - mMinTemperature) / (mMaxTemperature - mMinTemperature))) + textHeight;
mHighPoints[i] = new Point(x, highY);
mLowPoints[i] = new Point(x, lowY);
}
// 画线
drawLine(mHighPoints, canvas);
drawLine(mLowPoints, canvas);
for (int i = 0; i < mHighPoints.length; i++) {
// 画文本度数 例如:3°
String yHighText = mDecimalFormat.format(items.get(i).getHigh());
String yLowText = mDecimalFormat.format(items.get(i).getLow());
int highDrawY = mHighPoints[i].y - dip2px(mPointRadius + 8);
int lowDrawY = mLowPoints[i].y + dip2px(mPointRadius + 8 + sp2px(mTemperatureTextSize));
if (i == mTodayIndex) {
mTextPaint.setColor(mSelectTemperatureColor);
mCirclePaint.setColor(mSelectPointColor);
} else {
mTextPaint.setColor(mUnSelectTemperatureColor);
mCirclePaint.setColor(mUnselectPointColor);
}
canvas.drawText(yHighText + "°", mHighPoints[i].x, highDrawY, mTextPaint);
canvas.drawText(yLowText + "°", mLowPoints[i].x, lowDrawY, mTextPaint);
canvas.drawCircle(mHighPoints[i].x, mHighPoints[i].y, mPointRadius, mCirclePaint);
canvas.drawCircle(mLowPoints[i].x, mLowPoints[i].y, mPointRadius, mCirclePaint);
}
}
private void drawLine(Point[] ps, Canvas canvas) {
Point startp;
Point endp;
mPath.reset();
mLinePaint.setAntiAlias(true);
for (int i = 0; i < ps.length - 1; i++) {
startp = ps[i];
endp = ps[i + 1];
mLinePaint.setColor(mLineColor);
canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mLinePaint);
}
}
以上就是所有关键代码了,当然,还有一个赋值的代码
public void setData(List<WeatherInfo> list) {
this.items = list;
initData();
}
来看一下最后的效果图吧。
以上就是一个简单的温度图了,但是这个图有很多地方可以优化,也有很多地方可以提取出来当作属性。比如我举一个优化的点,文字的测量,上面的代码对文字的测量其实是非常粗糙的。仔细观察会发现上面一条线,文字距离点的距离和下面一条线文字距离点的距离是不一样的。这就是上面没有进行文字测量的结果,我这里进行了一轮文字测量的优化,如下图: 这里是不是好很多了呢?大家还可以进行很多地方的优化。以上就是这篇文章的全部内容了。
作者:爱海贼的小码农
链接:https://juejin.cn/post/7119826029463470088
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。