带渐变色的折线图记录
2020-03-30 本文已影响0人
名字不想带英文
单纯因为涉及的基础知识有点多,所以记录一下,成品图如下
image.png
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import com.eluton.helper.ScreenHelper;
import com.eluton.mylibrary.R;
import com.eluton.utils.LogUtil;
import java.util.ArrayList;
import java.util.List;
public class BrokenView extends View {
private Context context;
private Paint paintLine;//折线画笔
private Paint paintShare;//折线图渐变颜色背景
private Paint paintText;//文字画笔
private Paint paintCircle;//园画笔
private Path pathLine;//折线
private Path pathShare;//折线渐变路径(一个闭合的图)
private Shader shader;
private int lineColor;
private int[] shareColor = new int[]{getResources().getColor(R.color.green_00b395_40), getResources().getColor(R.color.green_00b395_30), getResources().getColor(R.color.tran)};
// Color.argb(130, 255, 86, 86), Color.argb(36, 255, 86, 86),
// Color.argb(0, 255, 86, 86)};
private int max = 180;//初始最大值为180(y轴),数组中最大值大于180已最大那个为准,下面有setMax方法
private double unit_h;//每个值的高度
private int[] pad = new int[4];//左、上、右、下4个内间距
private int width_broken;//折线图宽度(总宽度-左右内间距)
private int height_broken;
private int width;
private int height;
private int dx;//一周(横行)有6格,这个表示每格的宽度
private int dy;//竖行有3格,这个表示每格的高度
private Point pointStart = new Point();//折线图开始点
private int text_height;//文字高度
private int text_vertical_width;//垂直文字宽度(max的宽度)
private int text_horizontal_width;//水平文字宽度(max的宽度)
private int text_horizontal_x;//文字水平方向开始的x位置
private int text_horizontal_y;//文字水平方向开始的y位置
private int text_vertical_x;//文字垂直方向开始的x位置
private int text_vertical_y;//文字垂直方向开始的y位置
private int radius = 3;//内圆半径(最小直径12sp,20 )
private int size1;
private int size6;
private int srcSize;
private List<Integer> list_int = new ArrayList<>();
public BrokenView(Context context) {
this(context, null);
}
public BrokenView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BrokenView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}
private Bitmap bitmap;
private Rect mSrcRect = new Rect();//图片原大小
private Rect mDestRect = new Rect();//图片在view要展示的大小
// private ScreenHelper screenHelper;
private void init() {
// screenHelper = new ScreenHelper(context);//
size1 = ScreenHelper.dip2px(context, 1);//将dp转为px
size6 = 6 * size1;
srcSize = 11 * size1;
radius = 3 * size1 / 2;
// bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.timg);
// mSrcRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
imgs[0] = BitmapFactory.decodeResource(getResources(), R.mipmap.studyrecord_bad);//表情图片,下同,随便找找吧
imgs[1] = BitmapFactory.decodeResource(getResources(), R.mipmap.studyrecord_soso);
imgs[2] = BitmapFactory.decodeResource(getResources(), R.mipmap.studyrecord_good);
imgs[3] = BitmapFactory.decodeResource(getResources(), R.mipmap.studyrecord_awsome);
pad[0] = ScreenHelper.dip2px(context, 40);
pad[1] = ScreenHelper.dip2px(context, 40);
pad[2] = ScreenHelper.dip2px(context, 40);
pad[3] = ScreenHelper.dip2px(context, 40);
// lineColor = getResources().getColor(R.color.colorPrimary);
// 初始化画线的画笔,包括背景线和折线
paintLine = new Paint();
paintLine.setStyle(Paint.Style.STROKE);
paintLine.setAntiAlias(true);
paintLine.setStrokeWidth(2);
// paintLine.setColor(lineColor);
//初始化圆的画笔
paintCircle = new Paint();
paintCircle.setStyle(Paint.Style.FILL);
paintCircle.setAntiAlias(true); //抗锯齿
paintCircle.setColor(Color.WHITE);
paintCircle.setTextAlign(Paint.Align.LEFT);
// 初始化文字画笔
paintText = new Paint();
paintText.setStyle(Paint.Style.FILL);
paintText.setAntiAlias(true); //抗锯齿
paintText.setTextSize(ScreenHelper.dip2px(context, 12));
paintText.setColor(getResources().getColor(R.color.green_00b395));
paintText.setTextAlign(Paint.Align.LEFT);
text_height = (int) measureTextHeight(paintText);
// 获取文字(120和02.20)的宽度
text_vertical_width = (int) paintText.measureText("120");
text_horizontal_width = (int) paintText.measureText("02.20");
paintShare = new Paint();
pathLine = new Path();
pathShare = new Path();
}
//获取文字高度
public static float measureTextHeight(Paint paint) {
float height = 0f;
if (null == paint) {
return height;
}
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
height = fontMetrics.descent - fontMetrics.ascent;
return height;
}
private String date[] = {"02.16", "02.17", "02.18", "02.19", "02.20", "02.21", "今日"};//底部日期
private Bitmap imgs[] = new Bitmap[4];//数值顶部图片,没有图片的可以注释掉
public void updateDate(List<String> list_date) {//更新底部日期
for (int i = 0; i < date.length; i++) {
date[i] = list_date.get(i);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initWH();
unit_h = height_broken / (double) max;//每个值占得高度
// LogUtil.i(max + "unit_h:" + unit_h);
if (width_broken > 0 && height_broken > 0 && unit_h != 0) {//折线图宽高大于0才能开始画
paintLine.setColor(getResources().getColor(R.color.green_00b395));
paintLine.setStrokeWidth(0.2f);
text_vertical_x = pointStart.x - text_vertical_width - size6;
text_vertical_y = pointStart.y + text_height / 2 - 2;
text_horizontal_x = pointStart.x - text_horizontal_width / 2;
text_horizontal_y = pointStart.y + text_height * 2 + 2;
mPointList.clear();
for (int i = 0; i < list_int.size(); i++) {//画顶部图片和底部文字
int x = pointStart.x + dx * i;
int y = (int) (pointStart.y - list_int.get(i) * unit_h);
mPointList.add(new Point(x, y));
}
for (int i = 0; i < 7; i++) {//画竖线背景
if (list_int.size() == i + 1) {
paintText.setColor(getResources().getColor(R.color.black_333333));
} else {
paintText.setColor(getResources().getColor(R.color.black_999999));
}
int x = pointStart.x + dx * i;
// int y = (int) (pointStart.y - list_int.get(i) * unit_h);
// if (i != 0 && i != 6) {
canvas.drawLine(x, pointStart.y, x, pointStart.y - height_broken - srcSize, paintLine);
// }
canvas.drawText(date[i], text_horizontal_x + i * dx, text_horizontal_y, paintText);
}
paintText.setColor(getResources().getColor(R.color.green_00b395_30));
for (int i = 1; i < 4; i++) {//画横向背景
canvas.drawLine(pointStart.x - size6, pointStart.y - i * dy, width_broken + pointStart.x + srcSize, pointStart.y - i * dy, paintLine);
canvas.drawText(String.valueOf(max * i / 3), text_vertical_x, text_vertical_y - i * dy, paintText);
}
// paintText.setColor(getResources().getColor(R.color.green_00b395));
if (mPointList.size() > 1) {//画折线图
measurePath(canvas);
}
for (int i = 0; i < list_int.size(); i++) {//画各个点
int x = pointStart.x + dx * i;
int y = (int) (pointStart.y - list_int.get(i) * unit_h);
paintCircle.setColor(getResources().getColor(R.color.green_00b395));
canvas.drawCircle(x, y, 3 * size1, paintCircle);
paintCircle.setColor(Color.WHITE);
canvas.drawCircle(x, y, 2 * size1 + 1, paintCircle);
paintCircle.setColor(getResources().getColor(R.color.green_00b395));
canvas.drawCircle(x, y, radius, paintCircle);
}
if (isLogin) {
for (int i = 0; i < list_int.size(); i++) {//画各个点的顶部图片和底部文字
int x = pointStart.x + dx * i;
int y = (int) (pointStart.y - list_int.get(i) * unit_h);
int proNum = list_int.get(i);
if (proNum < 30) {
bitmap = imgs[0];
} else if (proNum < 60) {
bitmap = imgs[1];
} else if (proNum < 120) {
bitmap = imgs[2];
} else {
bitmap = imgs[3];
}
mSrcRect.left = 0;
mSrcRect.top = 0;
mSrcRect.right = bitmap.getWidth();
mSrcRect.bottom = bitmap.getHeight();
mDestRect.left = x - srcSize / 2;
mDestRect.top = y - srcSize - size6;
mDestRect.right = x + srcSize / 2;
mDestRect.bottom = y - size6;
// Rect mDestRect = new Rect(x - srcSize / 2, y - srcSize - size6 * 2, x + srcSize / 2, y - size6 * 2);
canvas.drawBitmap(bitmap, mSrcRect, mDestRect, null);
// 第一个Rect 代表要绘制的bitmap 区域,第二个 Rect 代表的是要将bitmap
int num_width = (int) paintText.measureText(String.valueOf(list_int.get(i)));//每个值得宽度
if (i + 1 == list_int.size()) {
int textLeft = (int) (x - num_width / (float) 2);
RectF rectTextBg = new RectF();
rectTextBg.left = textLeft - 3 * size1;
rectTextBg.bottom = y + size1 * 4 + text_height;
rectTextBg.right = rectTextBg.left + size6 + num_width + 1;
rectTextBg.top = y + size1 * 4;
int r = text_height / 2 - 1;
paintText.setColor(getResources().getColor(R.color.green_00b395));
canvas.drawRoundRect(rectTextBg, r, r, paintText);
paintText.setColor(getResources().getColor(R.color.white));
canvas.drawText(String.valueOf(list_int.get(i)), x - num_width / (float) 2, y + size6 / 3 + text_height, paintText);
// paintText.setColor(getResources().getColor(R.color.red_ff695e));
} else {
paintText.setColor(getResources().getColor(R.color.green_00b395));
canvas.drawText(String.valueOf(list_int.get(i)), x - num_width / (float) 2, y + size6 / 3 + text_height, paintText);
}
}
}
}
}
public void test() {//测试用得
isLogin = true;
ArrayList<Integer> list = new ArrayList<>();
list.add(0);
list.add(98);
list.add(48);
list.add(180);
list.add(60);
list.add(108);
list.add(150);
updateList(list);
}
private boolean isLogin = false;//是否登录
public void setLogin(boolean flag) {
isLogin = flag;
if (isLogin) {
test();
} else {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 7; i++) {
list.add(0);
}
updateList(list);
}
}
public void updateList(List<Integer> list_i) {
// initWH();
list_int.clear();
max = 0;
for (int i = 0; i < list_i.size(); i++) {
int num = list_i.get(i);
if (num > max) {
max = num;
}
list_int.add(num);
}
if (max == 0 || max < 180) {
max = 180;
}
if (width > 0) {
postInvalidate();
}
}
// 获取view的宽高,同时定义折线图左下角的点
private void initWH() {
if (width == 0) {
width = getWidth();
height = getHeight();
width_broken = width - pad[0] - pad[2];
height_broken = height - pad[1] - pad[3];
shader = new LinearGradient(0, 0, 0, height, shareColor, null, Shader.TileMode.CLAMP);
paintShare.setShader(shader);
dx = width_broken / 6;
dy = height_broken / 3;
LogUtil.i("height_broken:" + height_broken);
pointStart.x = pad[0];
pointStart.y = height - pad[3];
// test();
}
}
private List<Point> mPointList = new ArrayList<>();
private float lineSmoothness = 0.16f;//可自己修改值看变化
// 用曲线连接各点
private void measurePath(Canvas canvas) {
paintLine.setStrokeWidth(size1);//改成2像素
//保存曲线路径
Path mPath = new Path();
//保存辅助线路径
Path mAssistPath = new Path();
float prePreviousPointX = Float.NaN;
float prePreviousPointY = Float.NaN;
float previousPointX = Float.NaN;
float previousPointY = Float.NaN;
float currentPointX = Float.NaN;
float currentPointY = Float.NaN;
float nextPointX;
float nextPointY;
final int lineSize = mPointList.size();
for (int valueIndex = 0; valueIndex < lineSize; ++valueIndex) {
if (Float.isNaN(currentPointX)) {
Point point = mPointList.get(valueIndex);
currentPointX = point.x;
currentPointY = point.y;
}
if (Float.isNaN(previousPointX)) {
//是否是第一个点
if (valueIndex > 0) {
Point point = mPointList.get(valueIndex - 1);
previousPointX = point.x;
previousPointY = point.y;
} else {
//是的话就用当前点表示上一个点
previousPointX = currentPointX;
previousPointY = currentPointY;
}
}
if (Float.isNaN(prePreviousPointX)) {
//是否是前两个点
if (valueIndex > 1) {
Point point = mPointList.get(valueIndex - 2);
prePreviousPointX = point.x;
prePreviousPointY = point.y;
} else {
//是的话就用当前点表示上上个点
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
}
}
// 判断是不是最后一个点了
if (valueIndex < lineSize - 1) {
Point point = mPointList.get(valueIndex + 1);
nextPointX = point.x;
nextPointY = point.y;
} else {
//是的话就用当前点表示下一个点
nextPointX = currentPointX;
nextPointY = currentPointY;
}
if (valueIndex == 0) {
// 将Path移动到开始点
mPath.moveTo(currentPointX, currentPointY);
mAssistPath.moveTo(currentPointX, currentPointY);
} else {
// 求出控制点坐标
final float firstDiffX = (currentPointX - prePreviousPointX);
final float firstDiffY = (currentPointY - prePreviousPointY);
final float secondDiffX = (nextPointX - previousPointX);
final float secondDiffY = (nextPointY - previousPointY);
final float firstControlPointX = previousPointX + (lineSmoothness * firstDiffX);
final float firstControlPointY = previousPointY + (lineSmoothness * firstDiffY);
final float secondControlPointX = currentPointX - (lineSmoothness * secondDiffX);
final float secondControlPointY = currentPointY - (lineSmoothness * secondDiffY);
//画出曲线
mPath.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY,
currentPointX, currentPointY);
//将控制点保存到辅助路径上
mAssistPath.lineTo(firstControlPointX, firstControlPointY);
mAssistPath.lineTo(secondControlPointX, secondControlPointY);
mAssistPath.lineTo(currentPointX, currentPointY);
}
// 更新值,
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
previousPointX = currentPointX;
previousPointY = currentPointY;
currentPointX = nextPointX;
currentPointY = nextPointY;
}
PathMeasure mPathMeasure = new PathMeasure(mPath, false);
canvas.drawPath(mPath, paintLine);
// pathShare.lineTo(pointStart.x, mPointList.get(0).y);
pathShare.reset();
pathShare.addPath(mPath);
pathShare.lineTo(pointStart.x + dx * (list_int.size() - 1), pointStart.y);
pathShare.lineTo(pointStart.x, pointStart.y);
pathShare.close();
canvas.drawPath(pathShare, paintShare);
}
}