当前位置:网站首页>记录一个温度曲线的View
记录一个温度曲线的View
2022-07-16 10:48:00 【爱海贼的小码农】

最近做项目需求的看到需要自定义一个温度曲线的图。由于之前的同事理解需求的时候没有很好的理解产品的需求,将温度的折线图分成了两个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();
}
来看一下最后的效果图吧。

以上就是一个简单的温度图了,但是这个图有很多地方可以优化,也有很多地方可以提取出来当作属性。比如我举一个优化的点,文字的测量,上面的代码对文字的测量其实是非常粗糙的。仔细观察会发现上面一条线,文字距离点的距离和下面一条线文字距离点的距离是不一样的。这就是上面没有进行文字测量的结果,我这里进行了一轮文字测量的优化,如下图:
这里是不是好很多了呢?大家还可以进行很多地方的优化。以上就是这篇文章的全部内容了。
边栏推荐
- js单线程理解笔记-原创
- [sharing] layer 2 switching and layer 3 switching forwarding
- 从零开始构建向量数据库:Milvus 的源码编译安装(二)
- 新职业“数据库运行管理员”职业标准启动开发
- 7-16 每日一题 剑指 Offer II 041. 滑动窗口的平均值
- @Conditional的简单使用
- [deep learning] 40000 word notes! Yann Lecun deep learning open class 125 pages of dry goods are here
- SFF1004-MHCHXM(海矽美)二极管SFF1004
- 3D激光SLAM:ALOAM---Ceres 优化部分及代码分析
- @Conditional条件装配
猜你喜欢
随机推荐
OPPO、一加将在德国禁售?原来是被“专利流氓”诺基亚盯上了!
从零开始构建向量数据库:Milvus 的源码编译安装(二)
pycharm之爬虫教程(仅限于技术交流)
【手把手带你学UVM】~ 记录遇到的一切错误
数据库与开源的未来
想通讯?谈钱多俗,谈ProtoBuf
Mongodb slow query and index
够速度,才激情!2022 Amazon DeepRacer 7月赛高调来袭
链表 - 【删除所有重复的元素】
我发现了一款高效管理接口文档的神器
kotlin类和接口
《经济日报》点赞RPA领军企业弘玑,助力金融机构数字化转型
One stop Devops platform makes a big difference in development
无996无内卷,乐视过上了“神仙日子”?
极限实验室上新啦,期待已久的 INFINI Console 最新的 0.3 版本正式发布!
Modern personal blog system modstartblog v5.3.0 message interface added, rich text upgrade
重装mysql后initializing database错误
Directory structure analysis of unity Foundation
About network time protocol (NTP) mod-6 scanning vulnerability handling
Halcon distance calculation








