这是基于之前的油耗记录App实现的,数据和数据存储,以及相关资源都在前面的文章里:
油耗记录第一篇
添加油耗曲线,要自己自定义一个view类,然后在界面上画就行。
在添加LineView的代码之前,先加上这个文件(如果复制粘贴的话):
arrts.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LineView">
<attr name="viewMargin" format="dimension" />
<attr name="lineColor" format="color" />
<attr name="shadowColor" format="color" />
<attr name="lineTextSize" format="dimension" />
<attr name="lineTextColor" format="color" />
</declare-styleable>
</resources>
大佬文章的链接
这是在别人的基础上修改成自己想要的。
所有东西都在代码里面注释吧。
private int mViewMargin;//View起点距离顶部和底部的距离
private int mLineColor;//网格线的颜色
private int mShadowColor;//阴影部分的颜色
private int mTextSize;//字体大小
private int mTextColor;//字体颜色
private List<String> mYList;//纵坐标刻度
private List<String> mXList;//横坐标刻度
private List<String> mallOilConsumption;//数据
double mallAverageOil;//总的平均油耗
double mallSpent;//总费用
double mallOilCount;//总加油升数
那些set和get函数方法就不用说了,说一说关键的函数。
进行这个变量相关信息的初始化。当然这个可以不怎么注意。
@SuppressLint("NonConstantResourceId")
public LineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LineView, defStyleAttr, 0);
int count = array.getIndexCount();
for (int i = 0; i < count; i++) {
int index = array.getIndex(i);
switch (index) {
case R.styleable.LineView_viewMargin:
mViewMargin = array.getDimensionPixelSize(index, (int) TypedValue.
applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
break;
case R.styleable.LineView_lineColor:
mLineColor = array.getColor(index, Color.BLACK);
break;
case R.styleable.LineView_shadowColor:
mShadowColor = array.getColor(index, Color.BLACK);
break;
case R.styleable.LineView_lineTextColor:
mTextColor = array.getColor(index, Color.BLACK);
break;
case R.styleable.LineView_lineTextSize:
mTextSize = array.getDimensionPixelSize(index, (int) TypedValue.
applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
break;
}
}
array.recycle();
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(mLineColor);
mPaint.setTextSize(mTextSize);
mPaint.setAntiAlias(true); //取消锯齿
mPaint.setStyle(Paint.Style.STROKE); //设置画笔为空心
mMarginLeft = (mViewMargin * 2); //设置左边的偏移距离
mPaint.setStrokeWidth(2);
}
所有的绘图都在这里。就是说如果你想画什么东西,想添加什么东西,都要经过这个函数处理,不然是没有效果的,比如加上曲线的名称,或者添加图片,添加什么颜色等等。其他相关的不用说了,直接代码里看就行了。
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mHeight == 0) {
mHeight = getHeight() - mViewMargin * 2;
}
drawLine(canvas); //画网格
drawXScale(canvas);//画x轴
drawYScale(canvas);//画Y轴
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mTextColor);
mPaint.setStrokeWidth(3);
mPaint.setAntiAlias(true);
mListPoint = getPointList();
drawLineView(canvas);
Path path = new Path();
if(!mListPoint.isEmpty()){
path.moveTo(mListPoint.get(0).x, mListPoint.get(0).y);
for (int i = 1; i < mallOilConsumption.size() && i < 16; i++) {
path.lineTo(mListPoint.get(i).x, mListPoint.get(i).y);
}
int index = mListPoint.size() - 1;
path.lineTo(mListPoint.get(index).x, getHeight() - mViewMargin);
path.lineTo(mListPoint.get(0).x, getHeight() - mViewMargin);
}
path.close();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mShadowColor);
canvas.drawPath(path, mPaint);
}
private List<String> mallOilConsumption;//数据
注意这个变量,这是存储油耗数据的,我们可以在外面添加、删除或者更新其中的数据,然后通过重新设置函数set,来改变当前的数据,在改变数据的同时重绘界面。
public void setMallOilConsumption(List<String> mallOilConsumption) {
this.mallOilConsumption = mallOilConsumption;
this.postInvalidate();//重绘界面
}
因为屏幕太小,所以就只显示15个点,即最近十五次的加油记录,所以在获取点数的时候要设置范围,第一个点是0,0点,不管有没有数据都加进去的,然后限定最多只加15个点。Point是内部类,只管添加就行。
private List<Point> getPointList() {
List<Point> mList = new ArrayList<>();
float height = getHeight() - mViewMargin * 2;
int i = 0;
for(int j = mallOilConsumption.size() - 1;i < 16 && j >= 0;i++) {
Point point = new Point();
if (i == 0) {
point.x = mMarginLeft;
point.y = getHeight() - mViewMargin;
}
else {
point.x = mMarginLeft + i * (getWidth() / 17);
point.y = mViewMargin + height * (1.0f - Float.parseFloat(mallOilConsumption.get(j)) / 2.5f );
j--;
}
mList.add(point);
}
return mList;
}
private static class Point {
public float x;
public float y;
public Point() {
}
}
package com.example.listadapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class LineView extends View {
private int mViewMargin;//View起点距离顶部和底部的距离
private int mLineColor;//网格线的颜色
private int mShadowColor;//阴影部分的颜色
private int mTextSize;//字体大小
private int mTextColor;//字体颜色
private List<String> mYList;//纵坐标刻度
private List<String> mXList;//横坐标刻度
private List<String> mallOilConsumption;//数据
double mallAverageOil;//总的平均油耗
double mallSpent;//总费用
double mallOilCount;//总加油升数
public double getMallAverageOil() {
return mallAverageOil;
}
public void setMallAverageOil(double mallAverageOil) {
this.mallAverageOil = mallAverageOil;
}
public double getMallSpent() {
return mallSpent;
}
public void setMallSpent(double mallSpent) {
this.mallSpent = mallSpent;
}
public double getMallOilCount() {
return mallOilCount;
}
public void setMallOilCount(double mallOilCount) {
this.mallOilCount = mallOilCount;
}
private int mHeight;//网格线的高度
private float mMarginLeft;//网格线距离左边的距离
private List<Point> mListPoint;
public void setMallOilConsumption(List<String> mallOilConsumption) {
this.mallOilConsumption = mallOilConsumption;
this.postInvalidate();
}
private Paint mPaint;
/*构造函数*/
public LineView(Context context) {
this(context, null);
}
public LineView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
@SuppressLint("NonConstantResourceId")
public LineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LineView, defStyleAttr, 0);
int count = array.getIndexCount();
for (int i = 0; i < count; i++) {
int index = array.getIndex(i);
switch (index) {
case R.styleable.LineView_viewMargin:
mViewMargin = array.getDimensionPixelSize(index, (int) TypedValue.
applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
break;
case R.styleable.LineView_lineColor:
mLineColor = array.getColor(index, Color.BLACK);
break;
case R.styleable.LineView_shadowColor:
mShadowColor = array.getColor(index, Color.BLACK);
break;
case R.styleable.LineView_lineTextColor:
mTextColor = array.getColor(index, Color.BLACK);
break;
case R.styleable.LineView_lineTextSize:
mTextSize = array.getDimensionPixelSize(index, (int) TypedValue.
applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
break;
}
}
array.recycle();
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(mLineColor);
mPaint.setTextSize(mTextSize);
mPaint.setAntiAlias(true); //取消锯齿
mPaint.setStyle(Paint.Style.STROKE); //设置画笔为空心
mMarginLeft = (mViewMargin * 2); //设置左边的偏移距离
mPaint.setStrokeWidth(2);
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mHeight == 0) {
mHeight = getHeight() - mViewMargin * 2;
}
drawLine(canvas); //画网格
drawXScale(canvas);//画x轴
drawYScale(canvas);//画Y轴
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mTextColor);
mPaint.setStrokeWidth(3);
mPaint.setAntiAlias(true);
mListPoint = getPointList();
drawLineView(canvas);
Path path = new Path();
if(!mListPoint.isEmpty()){
path.moveTo(mListPoint.get(0).x, mListPoint.get(0).y);
for (int i = 1; i < mallOilConsumption.size() && i < 16; i++) {
path.lineTo(mListPoint.get(i).x, mListPoint.get(i).y);
}
int index = mListPoint.size() - 1;
path.lineTo(mListPoint.get(index).x, getHeight() - mViewMargin);
path.lineTo(mListPoint.get(0).x, getHeight() - mViewMargin);
}
path.close();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mShadowColor);
canvas.drawPath(path, mPaint);
}
//绘制网格线
private void drawLine(Canvas canvas) {
//左边第一条竖线
canvas.drawLine(mMarginLeft, mViewMargin + 2f * ((getHeight() - mViewMargin * 2) / 5),
mMarginLeft, getHeight() - mViewMargin, mPaint);
//4条水平的横线
for (int i = 0; i < 4; i++) {
canvas.drawLine(mMarginLeft, mViewMargin + (i + 2) * ((getHeight() - mViewMargin * 2) / 5), getWidth() - mViewMargin,
mViewMargin + (i + 2) * ((getHeight() - mViewMargin * 2) / 5), mPaint);
}
//右边最后一条竖线
canvas.drawLine(getWidth() - mViewMargin, mViewMargin + 2f * ((getHeight() - mViewMargin * 2) / 5),
getWidth() - mViewMargin, mViewMargin + ((getHeight() - mViewMargin * 2)), mPaint);
}
//绘制y轴刻度
@SuppressLint("DefaultLocale")
private void drawYScale(Canvas canvas) {
canvas.drawText("总的平均油耗:" + String.format("%.2f",mallAverageOil) + "L/Km", mViewMargin + 50,
mViewMargin + ((getHeight() - mViewMargin * 2) / 5) + 30, mPaint);
canvas.drawText("总费用:" + String.format("%.2f",mallSpent) + "元", mViewMargin + 50,
mViewMargin + ((getHeight() - mViewMargin * 2) / 5) + 60, mPaint);
canvas.drawText("总加油升数:"+ String.format("%.2f",mallOilCount) + "L", mViewMargin + 50,
mViewMargin + ((getHeight() - mViewMargin * 2) / 5) + 90, mPaint);
canvas.drawText("近15次油耗曲线", mViewMargin + 350,
mViewMargin + 2f * ((getHeight() - mViewMargin * 2) / 5) - 30, mPaint);
for (int i = 0; i < mYList.size(); i++) {
String scale = (i == 0) ? mYList.get(i) + " L/Km" : mYList.get(i);
canvas.drawText(scale, mViewMargin,
mViewMargin + (i+2) * ((getHeight() - mViewMargin * 2) / 5), mPaint);
}
}
//绘制x轴刻度
private void drawXScale(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mTextColor);
for (int i = 0; i < mXList.size(); i++) {
if (i == 0) {
canvas.drawText(mXList.get(i), mMarginLeft - dpToPx(getContext(), 3),
getHeight() - mViewMargin + dpToPx(getContext(), 10), mPaint);
}
if (i != 0 && i != 15) {
canvas.drawText(mXList.get(i), mMarginLeft + i * (getWidth() / 17),
getHeight() - mViewMargin + dpToPx(getContext(), 10), mPaint);
}
if (i == 15) {
canvas.drawText(mXList.get(i), mMarginLeft + i * (getWidth() / 17),
getHeight() - mViewMargin + dpToPx(getContext(), 10), mPaint);
}
}
}
// 绘制折线图
private void drawLineView(Canvas canvas) {
Path path = new Path();
if(mListPoint.isEmpty()) {
return;
}
path.moveTo(mListPoint.get(0).x, mListPoint.get(0).y);
for (int i = 1; i < this.mListPoint.size(); i++) {
path.lineTo(mListPoint.get(i).x, mListPoint.get(i).y);
}
canvas.drawPath(path, mPaint);
}
@Override
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
super.onSizeChanged(w, h, old_w, old_h);
}
public void setViewData(List<String> yList, List<String> xList, List<String> allOilConsumption) {
this.mYList = yList;
this.mXList = xList;
this.mallOilConsumption = allOilConsumption;
}
//根据手机分辨率将px 转为 dp
private float dpToPx(Context context, float pxValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (pxValue * scale + 0.5f);
}
private List<Point> getPointList() {
List<Point> mList = new ArrayList<>();
float height = getHeight() - mViewMargin * 2;
int i = 0;
for(int j = mallOilConsumption.size() - 1;i < 16 && j >= 0;i++) {
Point point = new Point();
if (i == 0) {
point.x = mMarginLeft;
point.y = getHeight() - mViewMargin;
}
else {
point.x = mMarginLeft + i * (getWidth() / 17);
point.y = mViewMargin + height * (1.0f - Float.parseFloat(mallOilConsumption.get(j)) / 2.5f );
j--;
}
mList.add(point);
}
return mList;
}
private static class Point {
public float x;
public float y;
public Point() {
}
}
}