Android 自定义坐标曲线图_android 自定义曲线图-CSDN博客
继上一篇文章,点击折线图上的点,显示提示信息进行修改,之前通过回调,调用外部方法,使用popupwindow或dialog来显示,但是这种方法对于弹框显示的位置很难控制,而且采用popupwindow或dialog是具有唯一性的,也就是显示后,必须先关闭,才能显示下一个点的弹框,这种在某些需求上是不符合的,这种只适合每次只弹一个弹框,且固定在底部,或者居中显示,就可以,实现起来简单。这种方式只适合在页面只有一个折线图的情况下,不适合运用到RecyclerView中,每个item都出现折线图的情况。
如果是要显示在点击到的点的上方,就很难控制,无法精准,并且在分辨率不同的手机会出现较大的差异。因此做了以下修改:
更新如下(20240329):点击点提示信息,不再使用popupwindow或dialog,还是通过自定义,引入xml布局来实现,适合运用到页面只有一个折线图,也适合RecyclerView中出现多个折线图的情况。具体实现代码如下:
public void showDialog(Canvas c, Point point) {
c.save();
c.translate((point.x - dip2px(45f)), (point.y - dip2px(30f) - CIRCLE_SIZE / 2f));
FrameLayout frameLayout = new FrameLayout(mContext);
frameLayout.setLayoutParams(new ViewGroup.LayoutParams(200, 200));
LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = li.inflate(R.layout.dialog_valuation_tracker, null);
v.setLayoutParams(new
FrameLayout.LayoutParams(dip2px(90f), dip2px(26f)));
frameLayout.addView(v);
frameLayout.measure(bWidth, bHeight);
frameLayout.layout(100, 100, 100, 100);
frameLayout.draw(c);
c.restore();
}
可以看到,是通过引入xml的形式来实现,使用xml能更加的实现多样化样式,要显示什么样子的提示框,可自行在xml里面修改,比如可以加入图片等;并且可以更好的控制显示的位置。可以通过再添加一些方法给外部调用即可
完整代码如下
public class BrokenLineView extends View {
private static final int CIRCLE_SIZE = 40;
private static enum LineStyle {LINE, CURVE}
private static enum YLineStyle {DASHES_LINE, FULL_LINE, NOT_LINE}
private static enum ShaderOrientationStyle {ORIENTATION_H, ORIENTATION_V}
private final Context mContext;
private OnClickListener listener;
private LineStyle mStyle = LineStyle.LINE;
private YLineStyle mYLineStyle = YLineStyle.NOT_LINE;
private ShaderOrientationStyle mShaderOrientationStyle = ShaderOrientationStyle.ORIENTATION_V;
private int canvasWidth;
private int bHeight = 0;
private int bWidth = 0;
private int marginLeft;
private int marginRight;
private boolean isMeasure = true;
private int xTextWidth = 0;//Y text
private int spacingHeight;
private double averageValue;
private int marginTop = 0;
private int marginBottom = 0;
/**
* data
*/
private Point[] mPoints;
private List<String> yRawData = new ArrayList<>();
private ValuationTrackerPointData pointData;
private List<String> xRawData = new ArrayList<>();
private final List<Double> dataList = new ArrayList<>();
private final List<Integer> xList = new ArrayList<>();// x value
private final Map<String, Integer> xMap = new HashMap<>();
/**
* paint color
*/
private int xTextPaintColor;
private int yTextPaintColor;
private int startShaderColor;
private int endShaderColor;
private int mCanvasColor;
private int mXLinePaintColor;
/**
* paint size
*/
private int xTextSize = 12;
private int yTextSize = 12;
private Point mSelPoint;
public BrokenLineView(Context context) {
this(context, null);
}
public BrokenLineView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initView();
}
private void initView() {
xTextPaintColor = getColor(mContext, R.color.cl_858585);
yTextPaintColor = getColor(mContext, R.color.cl_858585);
startShaderColor = getColor(mContext, R.color.cl_c53355_30);
endShaderColor = getColor(mContext, R.color.cl_c53355_5);
mCanvasColor = getColor(mContext, R.color.white);
mXLinePaintColor = getColor(mContext, R.color.cl_EBEBEB);
}
public void setData(ValuationTrackerPointData pointData) {
this.pointData = pointData;
averageValue = pointData.getyAverageValue();
xRawData.clear();
yRawData.clear();
dataList.clear();
xRawData = pointData.getxAxis();
xRawData.add(0, "");
yRawData = pointData.getyAxis();
for (int i = 0; i < pointData.getPointInfo().size(); i++) {
dataList.add(pointData.getPointInfo().get(i).getPrice());
}
if (null != dataList) {
mPoints = new Point[dataList.size()];
}
if (null != yRawData) {
spacingHeight = yRawData.size();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
if (isMeasure) {
marginLeft = dip2px(20);
marginRight = dip2px(10);
marginTop = dip2px(5);
marginBottom = dip2px(40);
int canvasHeight = getHeight();
this.canvasWidth = getWidth();
if (bHeight == 0) {
bHeight = canvasHeight - marginBottom - marginTop;
}
if (bWidth == 0) {
bWidth = canvasWidth - marginLeft - marginRight;
}
isMeasure = false;
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(mCanvasColor);//canvas color
//draw X line
drawAllXLine(canvas);
if (YLineStyle.DASHES_LINE == mYLineStyle) {
drawPathYDashesLine(canvas);//draw Y dashes line
} else if (YLineStyle.FULL_LINE == mYLineStyle) {
drawAllYLine(canvas);// draw Y line
} else {
noDrawYLine(canvas);
}
// point init
mPoints = getPoints();
//draw cure line
drawCurve(canvas);
//draw Polygon bg color
drawPolygonBgColor(canvas);
// is click point
if (null == mSelPoint) {
drawDot(canvas);// draw dot
} else {
clickUpdateDot(canvas);// update dot after click
}
}
private void drawCurve(Canvas c) {
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(getColor(mContext, R.color.cl_c53355));
p.setStrokeWidth(dip2px(1f));
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(getColor(mContext, R.color.cl_c53355));
c.drawCircle(point.x, point.y, CIRCLE_SIZE / 2f, p);
p.setColor(getColor(mContext, R.color.cl_d77188));
c.drawCircle(point.x, point.y, CIRCLE_SIZE / 3f, 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(getColor(mContext, R.color.cl_c53355));
c.drawCircle(point.x, point.y, CIRCLE_SIZE / 1.5f, p);
p.setColor(getColor(mContext, R.color.cl_d77188));
c.drawCircle(point.x, point.y, (CIRCLE_SIZE / 2f), p);
showDialog(c, point);
} else {
p.setColor(getColor(mContext, R.color.cl_c53355));
c.drawCircle(point.x, point.y, CIRCLE_SIZE / 2f, p);
p.setColor(getColor(mContext, R.color.cl_d77188));
c.drawCircle(point.x, point.y, CIRCLE_SIZE / 3f, 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);
}
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;
}
private void drawPathYDashesLine(Canvas canvas) {
if (null == xRawData || xRawData.isEmpty()) {
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(getColor(mContext, R.color.colorGray));
p.setPathEffect(new DashPathEffect(new float[]{dashLength, blankLength}, 0));
for (int i = 0; i < xRawData.size(); i++) {
drawTextY(xRawData.get(i), (getMarginWidth() + getBWidth() / xRawData.size() * i) - dip2px(8), bHeight + marginTop + dip2px(26),
canvas);
if (null != xMap) {
xMap.put(xRawData.get(i), getMarginWidth() + getBWidth() / xRawData.size() * i);
}
int startX = (getMarginWidth() + getBWidth() / xRawData.size() * i);
int startY = marginTop;
int endY = bHeight + marginTop;
path.moveTo(startX, startY);
path.lineTo(startX, endY);
canvas.drawPath(path, p);
}
getPointX();
}
/**
* draw Y
*/
private void drawAllYLine(Canvas canvas) {
if (null == xRawData || xRawData.isEmpty()) {
return;
}
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(getColor(mContext, R.color.colorBlack));
for (int i = 0; i < xRawData.size(); i++) {
int w = (getMarginWidth() + getBWidth() / xRawData.size()) * i;
canvas.drawLine(w, marginTop, w, (bHeight + marginTop), p);
drawTextY(xRawData.get(i), getMarginWidth() + getBWidth() / xRawData.size() * i - dip2px(8), bHeight + marginTop + dip2px(26),
canvas);
if (null != xMap) {
xMap.put(xRawData.get(i), getMarginWidth() + getBWidth() / xRawData.size() * i);
}
}
getPointX();
}
private void noDrawYLine(Canvas canvas) {
if (null == xRawData || xRawData.isEmpty()) {
return;
}
for (int i = 0; i < xRawData.size(); i++) {
drawTextY(xRawData.get(i), (getMarginWidth() + getBWidth() / xRawData.size() * i) - dip2px(8), bHeight + marginTop + dip2px(26),
canvas);
if (null != xMap) {
xMap.put(xRawData.get(i), getMarginWidth() + getBWidth() / xRawData.size() * i);
}
}
getPointX();
}
private void getPointX() {
if (null == xMap || xMap.size() == 0) {
return;
}
if (null != pointData && !pointData.getPointInfo().isEmpty()) {
for (ValuationTrackerPointData.PointInfo info : pointData.getPointInfo()) {
for (Map.Entry<String, Integer> entry : xMap.entrySet()) {
if (entry.getKey().equals(info.getMouth())) {
xList.add(xMap.get(entry.getKey()));
}
}
}
}
}
/**
* draw x
*/
private void drawAllXLine(Canvas canvas) {
if (null == yRawData || yRawData.isEmpty()) {
return;
}
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(mXLinePaintColor);
p.setStrokeWidth(dip2px(1f));
p.setStyle(Paint.Style.FILL);
int h = bHeight / spacingHeight;
for (int i = 0; i < yRawData.size(); i++) {
drawTextX(yRawData.get(i), marginLeft / 2,
bHeight - (bHeight / spacingHeight) * i + marginTop + dip2px(2), canvas);
canvas.drawLine(getMarginWidth(), (bHeight - h * i + marginTop), (canvasWidth - marginRight),
(bHeight - h * i + marginTop), p);
}
}
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);
}
}
private void drawTextY(String text, int x, int y, Canvas canvas) {
if (null == yRawData || yRawData.isEmpty()) {
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);
}
private void drawTextX(String text, int x, int y, Canvas canvas) {
if (null == xRawData || xRawData.isEmpty()) {
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 * 1.0f / 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;
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
dealClick(x, y);
}
return true;
}
private void dealClick(int x, int y) {
if (null != mPoints && mPoints.length > 0) {
for (Point p : mPoints) {
if ((p.x - CIRCLE_SIZE) < x && x < (p.x + CIRCLE_SIZE) &&
(p.y - CIRCLE_SIZE) < y && y < (p.y + CIRCLE_SIZE)) {
mSelPoint = p;
invalidate();
if (null != listener) {
listener.onClick(this, p.x, p.y);
}
}
}
}
}
public void showDialog(Canvas c, Point point) {
c.save();
c.translate((point.x - dip2px(45f)), (point.y - dip2px(30f) - CIRCLE_SIZE / 2f));
FrameLayout frameLayout = new FrameLayout(mContext);
frameLayout.setLayoutParams(new ViewGroup.LayoutParams(200, 200));
LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = li.inflate(R.layout.dialog_valuation_tracker, null);
v.setLayoutParams(new
FrameLayout.LayoutParams(dip2px(90f), dip2px(26f)));
frameLayout.addView(v);
frameLayout.measure(bWidth, bHeight);
frameLayout.layout(100, 100, 100, 100);
frameLayout.draw(c);
c.restore();
}
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 setXLinePaintColor(int color) {
mXLinePaintColor = color;
}
public void setShaderColor(int startColor, int endColor) {
this.startShaderColor = startColor;
this.endShaderColor = endColor;
}
private int dip2px(float dpValue) {
float scale = mContext.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public interface OnClickListener {
void onClick(View v, int x, int y);
}
public void setListener(OnClickListener listener) {
this.listener = listener;
}
}