Android 自定义坐标曲线图

先看效果

项目开发中,被安排去调研实现 坐标曲线图,网上第三方的库很多,可以实现,但是有些样式无法做到符合自己要求,Android 与iOS效果上也存在差异,所以自己自定义了一个;

其实比较简单,就是画点,画线,画虚线,画曲线,添加点击事件即可;这里面需要涉及到的知识点主要是有:对自定义View有一点基础,比如onMeasure()、onLayout()、onDraw();至少得了解这三个方法;

另外就是需要会用画笔Paint、点Point、路径Path等,至少会使用这三个API,那基本就没有问题了,画点、点与点连接成线、然后闭合起来就是一个多边形、再给多边形填充颜色即可;

另外横坐标纵坐标,以及点的数据,都是外部传入,具体情况具体考虑;

先看使用方法:

在布局中添加

<com.test.jsontouijson.weight.LineGraphicViewandroid:id="@+id/lineGraphicView"android:layout_weight="1"android:layout_width="match_parent"android:layout_height="wrap_content"/>

在activity或fragment中初始化,添加数据

lineGraphicView = findViewById(R.id.lineGraphicView); 
lineGraphicView.setData(pointData);
//添加点击事件
lineGraphicView.setListener((x, y) -> initPopupWindow(lineGraphicView, x, y));

点击后的弹框是外部实现的,这个自行使用popupWindow去实现,点击已经有返回点的坐标x、y了,弹框的显示位置可通过这个坐标点定位。

大概就是这些,下面是LineGraphicView具体代码,代码有比较详细的备注形式


import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class LineGraphicView extends View {private static final int CIRCLE_SIZE = 40;private static enum LineStyle {LINE, CURVE}private static enum YLineStyle {DASHES_LINE, FULL_LINE}private static enum ShaderOrientationStyle {ORIENTATION_H, ORIENTATION_V}private Context mContext;private Resources res;private DisplayMetrics dm;private OnClickListener listener;private LineStyle mStyle = LineStyle.LINE;private YLineStyle mYLineStyle = YLineStyle.DASHES_LINE;private ShaderOrientationStyle mShaderOrientationStyle = ShaderOrientationStyle.ORIENTATION_V;private int canvasHeight;private int canvasWidth;private int bHeight = 0;private int bWidth = 0;private int marginLeft;private boolean isMeasure = true;private boolean isShowFirstXContent = false;private int xTextWidth = 0;//Y轴内容宽度private int spacingHeight;private double averageValue;private int marginTop = 0;private int marginBottom = 0;/*** data*/private Point[] mPoints;//点private List<String> yRawDatas;//y轴数据private PointData pointData;//外部传入数据private List<String> xRawDatas;//x轴数据private List<Double> dataList = new ArrayList<>();//点的数据private List<Integer> xList = new ArrayList<>();// 记录每个x的值private Map<String, Integer> xMap = new HashMap<>();//用于保存 点-X坐标 对应起来/*** paint color*/private int xTextPaintColor;private int yTextPaintColor;private int startShaderColor;private int endShaderColor;private int mCanvasColor;/*** paint size*/private int xTextSize = 12;private int yTextSize = 12;private Point mSelPoint;public LineGraphicView(Context context) {this(context, null);}public LineGraphicView(Context context, AttributeSet attrs) {super(context, attrs);this.mContext = context;initView();}private void initView() {this.res = mContext.getResources();xTextPaintColor = res.getColor(R.color.black);yTextPaintColor = res.getColor(R.color.black);startShaderColor = res.getColor(R.color.colorYellow);endShaderColor = res.getColor(R.color.colorYellow1);mCanvasColor = res.getColor(R.color.colorGray1);dm = new DisplayMetrics();WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);wm.getDefaultDisplay().getMetrics(dm);}public void setData(PointData pointData) {this.pointData = pointData;this.averageValue = pointData.getyAverageValue();this.xRawDatas = pointData.getxAxis();this.yRawDatas = pointData.getyAxis();for (int i = 0; i < pointData.getPointInfo().size(); i++) {dataList.add(pointData.getPointInfo().get(i).getPrice());}if (null != dataList) {this.mPoints = new Point[dataList.size()];}if (null != yRawDatas) {this.spacingHeight = yRawDatas.size();}}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {if (isMeasure) {marginLeft = dip2px(30);marginTop = dip2px(30);marginBottom = dip2px(80);this.canvasHeight = getHeight();//获取画布高度this.canvasWidth = getWidth();//获取画布宽度if (bHeight == 0) {bHeight = getHeight() - marginBottom;//实际坐标图高度}if (bWidth == 0) {bWidth = canvasWidth - marginLeft;//实际坐标图宽度}isMeasure = false;}}@Overrideprotected void onDraw(Canvas canvas) {//draw X linedrawAllXLine(canvas);if (YLineStyle.DASHES_LINE == mYLineStyle) {drawPathYDashesLine(canvas);//draw Y dashes line} else {drawAllYLine(canvas);// draw Y ine)}// point initmPoints = getPoints();//draw cure linedrawCurve(canvas);//draw Polygon bg colordrawPolygonBgColor(canvas);// is click pointif (null == mSelPoint) {drawDot(canvas);// draw dot} else {clickUpdateDot(canvas);// update dot after click}canvas.drawColor(mCanvasColor);//canvas color}private void drawCurve(Canvas c) {Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);p.setColor(res.getColor(R.color.colorPrimaryDark));p.setStrokeWidth(dip2px(0.5f));p.setStyle(Paint.Style.STROKE);if (mStyle == LineStyle.CURVE) {drawScrollLine(c, p);} else {drawLine(c, p);}}//画点private void drawDot(Canvas c) {if (null == mPoints || mPoints.length == 0) {return;}Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);p.setStyle(Paint.Style.FILL);for (Point point : mPoints) {p.setColor(res.getColor(R.color.colorYellow2));c.drawCircle(point.x, point.y, CIRCLE_SIZE / 2, p);p.setColor(res.getColor(R.color.colorYellow3));c.drawCircle(point.x, point.y, CIRCLE_SIZE / 3, p);}}//更新点private void clickUpdateDot(Canvas c) {if (null == mPoints || mPoints.length == 0) {return;}Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);p.setStyle(Paint.Style.FILL);for (Point point : mPoints) {if (null != mSelPoint && mSelPoint.x == point.x && mSelPoint.y == point.y) {p.setColor(res.getColor(R.color.colorPrimary));c.drawCircle(point.x, point.y, CIRCLE_SIZE, p);p.setColor(res.getColor(R.color.colorYellow3));c.drawCircle(point.x, point.y, (float) (CIRCLE_SIZE / 1.5), p);} else {p.setColor(res.getColor(R.color.colorYellow2));c.drawCircle(point.x, point.y, CIRCLE_SIZE / 2, p);p.setColor(res.getColor(R.color.colorYellow3));c.drawCircle(point.x, point.y, CIRCLE_SIZE / 3, p);}}}//填充曲线闭合起来的图private void drawPolygonBgColor(Canvas c) {if (null == mPoints || mPoints.length == 0) {return;}Path p = new Path();float startX = 0;float endX = 0;int endPoint = mPoints.length - 1;for (int i = 0; i < mPoints.length; i++) {if (i == 0) {startX = mPoints[i].x;p.moveTo(mPoints[i].x, 0);p.lineTo(mPoints[i].x, mPoints[i].y);} else {p.lineTo(mPoints[i].x, mPoints[i].y);if (i == endPoint) {endX = mPoints[i].x;}}}p.lineTo(endX, bHeight + marginTop);p.lineTo(startX, bHeight + marginTop);p.close();Paint paint = new Paint();paint.setStyle(Paint.Style.FILL);Shader shader = null;if (mShaderOrientationStyle == ShaderOrientationStyle.ORIENTATION_H) {shader = new LinearGradient(endX, bHeight + marginTop, startX, bHeight + marginTop,startShaderColor, endShaderColor, Shader.TileMode.REPEAT);} else {Point point = getYBiggestPoint();if (null != point) {shader = new LinearGradient(point.x, point.y, endX, bHeight + marginTop,startShaderColor, endShaderColor, Shader.TileMode.REPEAT);}}paint.setShader(shader);c.drawPath(p, paint);}//获取Y坐标最高的点private Point getYBiggestPoint() {Point p = null;if (null != mPoints && mPoints.length > 0) {p = mPoints[0];for (int i = 0; i < mPoints.length - 1; i++) {if (p.y > mPoints[i + 1].y) {p = mPoints[i + 1];}}}return p;}//画Y方向虚线private void drawPathYDashesLine(Canvas canvas) {if (null == xRawDatas || xRawDatas.size() == 0) {return;}Path path = new Path();int dashLength = 16;int blankLength = 16;Paint p = new Paint();p.setStyle(Paint.Style.STROKE);p.setStrokeWidth(4);p.setColor(res.getColor(R.color.colorGray));p.setPathEffect(new DashPathEffect(new float[]{dashLength, blankLength}, 0));if (!isShowFirstXContent && null == mSelPoint) {xRawDatas.add(0, "");}for (int i = 0; i < xRawDatas.size(); i++) {drawTextY(xRawDatas.get(i), (getMarginWidth() + getBWidth() / xRawDatas.size() * i) - dip2px(8), bHeight + marginTop + dip2px(26),canvas);if (null != xMap) {xMap.put(xRawDatas.get(i), getMarginWidth() + getBWidth() / xRawDatas.size() * i);}float startX = getMarginWidth() + getBWidth() / xRawDatas.size() * i;float startY = marginTop;float endY = bHeight + marginTop;path.moveTo(startX, startY);path.lineTo(startX, endY);canvas.drawPath(path, p);}getPointX();}/*** 画所有Y方向实线*/private void drawAllYLine(Canvas canvas) {if (null == xRawDatas || xRawDatas.size() == 0) {return;}Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);p.setColor(res.getColor(R.color.colorBlack));for (int i = 0; i < xRawDatas.size(); i++) {canvas.drawLine(getMarginWidth() + getBWidth() / xRawDatas.size() * i, marginTop, getMarginWidth()+ getBWidth() / xRawDatas.size() * i, bHeight + marginTop, p);drawTextY(xRawDatas.get(i), getMarginWidth() + getBWidth() / xRawDatas.size() * i - dip2px(8), bHeight + marginTop + dip2px(26),canvas);if (null != xMap) {xMap.put(xRawDatas.get(i), getMarginWidth() + getBWidth() / xRawDatas.size() * i);}}getPointX();}//获取点的X坐标private void getPointX() {if (null == xMap || xMap.size() == 0) {return;}if (null != pointData && pointData.getPointInfo().size() > 0) {for (PointData.PointInfo info : pointData.getPointInfo()) {for (String key : xMap.keySet()) {if (key.equals(info.getMouth())) {xList.add(xMap.get(key));}}}}}/*** 画所有X方向的线*/private void drawAllXLine(Canvas canvas) {if (null == yRawDatas || yRawDatas.size() == 0) {return;}Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);p.setColor(res.getColor(R.color.colorBlack));p.setStyle(Paint.Style.FILL);for (int i = 0; i < yRawDatas.size(); i++) {drawTextX(yRawDatas.get(i), marginLeft / 2,bHeight - (bHeight / spacingHeight) * i + marginTop + dip2px(2), canvas);canvas.drawLine(getMarginWidth(), bHeight - (bHeight / spacingHeight) * i + marginTop, canvasWidth,bHeight - (bHeight / spacingHeight) * i + marginTop, p);// Y坐标}}//画圆滑的曲线private void drawScrollLine(Canvas canvas, Paint paint) {if (null == mPoints || mPoints.length == 0) {return;}Point startP;Point endP;for (int i = 0; i < mPoints.length - 1; i++) {startP = mPoints[i];endP = mPoints[i + 1];int wt = (startP.x + endP.x) / 2;Point p3 = new Point();Point p4 = new Point();p3.y = startP.y;p3.x = wt;p4.y = endP.y;p4.x = wt;Path path = new Path();path.moveTo(startP.x, startP.y);path.cubicTo(p3.x, p3.y, p4.x, p4.y, endP.x, endP.y);canvas.drawPath(path, paint);}}//画笔直的曲线private void drawLine(Canvas canvas, Paint paint) {if (null == mPoints || mPoints.length == 0) {return;}Point startP;Point endP;for (int i = 0; i < mPoints.length - 1; i++) {startP = mPoints[i];endP = mPoints[i + 1];canvas.drawLine(startP.x, startP.y, endP.x, endP.y, paint);}}//添加X轴方向的文字private void drawTextY(String text, int x, int y, Canvas canvas) {if (null == yRawDatas || yRawDatas.size() == 0) {return;}Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);p.setTextSize(dip2px(yTextSize));p.setColor(yTextPaintColor);p.setTextAlign(Paint.Align.LEFT);canvas.drawText(text, x, y, p);}//添加Y轴方向的文字private void drawTextX(String text, int x, int y, Canvas canvas) {if (null == xRawDatas || xRawDatas.size() == 0) {return;}Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);p.setTextSize(dip2px(xTextSize));p.setColor(xTextPaintColor);p.setTextAlign(Paint.Align.LEFT);xTextWidth = (int) p.measureText(text);canvas.drawText(text, x, y, p);}//获取所有曲线点private Point[] getPoints() {Point[] points = new Point[dataList.size()];for (int i = 0; i < dataList.size(); i++) {int ph = bHeight - (int) (((dataList.get(i) - pointData.getyAxisSmallValue()) / averageValue) * (bHeight / spacingHeight));points[i] = new Point(xList.get(i), ph + marginTop);}return points;}//获取实际的左边距private int getMarginWidth() {if (xTextWidth == 0) {return marginLeft;} else {return xTextWidth + marginLeft;}}//获取实际的坐标图宽度private int getBWidth() {if (xTextWidth == 0) {return bWidth;} else {return bWidth - xTextWidth;}}@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();int action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:dealClick(x, y);break;}return true;}//添加点击事件,并更新点private void dealClick(int x, int y) {if (null != mPoints && mPoints.length > 0) {for (int i = 0; i < mPoints.length; i++) {if ((mPoints[i].x - CIRCLE_SIZE) < x && x < (mPoints[i].x + CIRCLE_SIZE) &&(mPoints[i].y - CIRCLE_SIZE) < y && y < (mPoints[i].y + CIRCLE_SIZE)) {mSelPoint = mPoints[i];invalidate();if (null != listener) {listener.onClick(mPoints[i].x, mPoints[i].y);}Toast.makeText(mContext, "点击了第" + i + "个", Toast.LENGTH_SHORT).show();}}}}public void setAverageValue(int averageValue) {this.averageValue = averageValue;}public void setMarginTop(int marginTop) {this.marginTop = marginTop;}public void setMarginBottom(int marginBottom) {this.marginBottom = marginBottom;}public void setMStyle(LineStyle mStyle) {this.mStyle = mStyle;}public void setMYLineStyle(YLineStyle style) {this.mYLineStyle = style;}public void setShaderOrientationStyle(ShaderOrientationStyle shaderOrientationStyle) {this.mShaderOrientationStyle = shaderOrientationStyle;}public void setBHeight(int bHeight) {this.bHeight = bHeight;}public void setXTextPaintColor(int xTextPaintColor) {this.xTextPaintColor = xTextPaintColor;}public void setYTextPaintColor(int yTextPaintColor) {this.yTextPaintColor = yTextPaintColor;}public void setXTextSize(int xTextSize) {this.xTextSize = xTextSize;}public void setYTextSize(int yTextSize) {this.yTextSize = yTextSize;}public void setShaderColor(int startColor, int endColor) {this.startShaderColor = startColor;this.endShaderColor = endColor;}public void setIsShowFirstXContent(boolean isShowFirstXContent) {this.isShowFirstXContent = isShowFirstXContent;}/*** 根据手机的分辨率从 dp 的单位 转成为 px(像素)*/private int dip2px(float dpValue) {return (int) (dpValue * dm.density + 0.5f);}public interface OnClickListener {void onClick(int x, int y);}public void setListener(OnClickListener listener) {this.listener = listener;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/211347.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

主流的低代码平台有哪些?程序员应该如何与低代码相处?

本文主要阐述低代码的概念&#xff0c;介绍目前主流的低代码平台&#xff0c;总结低代码平台的典型特征、存在优势以及未来发展趋势。并站在程序员的角度&#xff0c;分析如何在已经到来的低代码战争中&#xff0c;找到自己的定位&#xff0c;一展所长。 什么是低代码&#xff…

GitHub使用学习

关注侧边栏的Release Fork 可以直接把当前项目的所有代码都拷贝到自己的主页上 Issue 给作者反馈问题&#xff0c;或者查看别人提出的问题

Shell 通配符与正则表达元字符

Author&#xff1a;rab 目录 前言一、通配符1.1 *1.2 ?1.3 []1.4 {} 二、正则表达元字符2.1 *2.2 .2.3 ^2.4 $2.5 []2.6 \2.7 \<\>2.8 \{\} 总结 前言 不管是学任何语言&#xff0c;几乎都会涉及到通配符与正则的使用。有时候对于 Linux 初学者来说&#xff0c;往往会将…

C++中在一个cpp文件中引用另外一个cpp文件的方法

C中在一个cpp文件中引用另外一个cpp文件 可以通过导入cpp文件或者.h文件来实现&#xff0c; 类似python中的import 导入 下面距离说明下 创建1个func1.cpp 内容如下&#xff1a; #include<iostream> using namespace std;int sum (int num1, int num2) {return (num1…

shell 条件语句 if case

目录 测试 test测试文件的表达式 是否成立 格式 选项 比较整数数值 格式 选项 字符串比较 常用的测试操作符 格式 逻辑测试 格式 且 &#xff08;全真才为真&#xff09; 或 &#xff08;一真即为真&#xff09; 常见条件 双中括号 [[ expression ]] 用法 &…

测试开发(二) 开发chrome插件,提升测试效率

chrome插件截图 功能说明 自定义拦截请求response数据、并根据需要做解析&#xff0c;方便检查数据&#xff0c;提升测试效率。 chrome插件截图 功能说明 自定义修改请求的header、接口返回的response的header&#xff0c;提升模拟请求的效率&#xff0c;进而提升测试效率。…

tp8 使用rabbitMQ(4)路由模式

路由模式 在第三节中我们使用的 交换机的 fanout 把生产者的消息广播到了所有与它绑定的队列中处理&#xff0c;但是我们能不能把特定的消息&#xff0c;发送给指定的队列&#xff0c;而不是广播给所有队列呢&#xff1f; 如图&#xff0c;交换机把 orange 类型的消息发送给了…

七要素微气象仪气象数据监测助手

WX-WQX7 随着科技的发展&#xff0c;气象预测的准确性已成为人们日常生活的重要参考。而七要素微气象仪&#xff0c;作为新型的气象探测设备&#xff0c;以其精细化的数据测量和解析能力&#xff0c;正在改变我们的天气预测方式。 一、产品介绍 七要素微气象仪是一款集成了温…

从裸机启动开始运行一个C++程序(十三)

前序文章请看&#xff1a; 从裸机启动开始运行一个C程序&#xff08;十二&#xff09; 从裸机启动开始运行一个C程序&#xff08;十一&#xff09; 从裸机启动开始运行一个C程序&#xff08;十&#xff09; 从裸机启动开始运行一个C程序&#xff08;九&#xff09; 从裸机启动开…

【IEEE-TRANS】CCF-B类,IF:11+, 1区顶刊,无需版面费,最快2个月左右录用!

论文写作堪比西天取经&#xff0c;当我们经历“九九八十一难&#xff0c;取得真经“&#xff0c;还有最关键的一步&#xff0c;就是选刊发表。是“投石问路”&#xff0c;还是“投其所好”&#xff1f; 选刊有多重要&#xff0c;相信只要有过发表SCI经验的人都十分清楚。如果不…

每天5分钟复习OpenStack(十)Ceph 架构

1、Ceph是什么&#xff1f; “Ceph is a unified, distributed storage system designed for excellent performance, reliability and scalability.”这句话说出了Ceph的特性&#xff0c;它是可靠的、可扩展的、统一的、分布式的存储系统。Ceph可以同时提供对象存储RADOSGW&am…

OSG动画与声音-路径动画之导出与导入(2)

路径的导出示例 路径的导出示例的代码如程序清单10-2所示。 1. // 创建路径 2. osg::ref_ptr<osg::AnimationPath> createAnimationPath(osg::Vec3 ¢er, 3. float radius, float looptime) 4. { 5. // 创建一个Path对象 6. osg::ref_ptr<…