很多软件中都会展示统计数据给用户,方式就是各种统计图,柱状图,扇形图,折线图等等。当然现在有很多第三方依赖很容易就可以实现上面说的各种图,此篇的目的就是介绍如何自己动手做一个简单的折线图(注释挺详细,话就不多了)。
效果图思路自定义属性数据提供绘制xy轴线,箭头绘制xy轴刻度,文字绘制折线绘制折线上点位圆圈绘制选中d出框和x轴文字选中框x轴滑动自定义属性attrs中创建需要的属性
<!-- xy轴线颜色 --> <attr name="xylinecolor" format="color" /> <!-- xy轴线的宽度 --> <attr name="xylinewidth" format="dimension" /> <!-- xy轴上的文字颜色 --> <attr name="xytextcolor" format="color" /> <!-- xy轴上的文字大小 --> <attr name="xytextsize" format="dimension" /> <!-- 折线的颜色 --> <attr name="linecolor" format="color" /> <!-- x轴坐标点间距 --> <attr name="interval" format="dimension" /> <!-- 折线点位信息颜色 --> <attr name="contentcolor" format="color" /> <!-- 背景颜色 --> <attr name="bgcolor" format="color" /> <!--滑动后手抬起时,折线是否跟随初速度左右滑动--> <attr name="isScroll" format="boolean" /> <declare-styleable name="brokenlineVIEw"> <attr name="xylinecolor" /> <attr name="xylinewidth" /> <attr name="xytextcolor" /> <attr name="xytextsize" /> <attr name="linecolor" /> <attr name="contentcolor" /> <attr name="interval" /> <attr name="bgcolor" /> <attr name="isScroll" /> </declare-styleable>
在自定义view类中初始化
/** * 初始化自定义属性 */ private voID initPar(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.brokenlineVIEw, defStyleAttr, 0); int count = array.getIndexCount(); for (int i = 0; i < count; i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.brokenlineVIEw_xylinecolor://xy轴线颜色 xylinecolor = array.getcolor(attr, xylinecolor); break; case R.styleable.brokenlineVIEw_xylinewidth://xy轴线的宽度 xylinewidth = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xylinewidth, getResources().getdisplayMetrics())); break; case R.styleable.brokenlineVIEw_xytextcolor://xy轴上的文字颜色 xytextcolor = array.getcolor(attr, xytextcolor); break; case R.styleable.brokenlineVIEw_xytextsize://xy轴上的文字大小 xytextsize = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xytextsize, getResources().getdisplayMetrics())); break; case R.styleable.brokenlineVIEw_linecolor://折线的颜色 linecolor = array.getcolor(attr, linecolor); break; case R.styleable.brokenlineVIEw_contentcolor://折线点位信息颜色 contentcolor = array.getcolor(attr, contentcolor); break; case R.styleable.brokenlineVIEw_interval://x轴坐标点间距 interval = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, interval, getResources().getdisplayMetrics())); break; case R.styleable.brokenlineVIEw_bgcolor: //背景颜色 bgcolor = array.getcolor(attr, bgcolor); break; case R.styleable.brokenlineVIEw_isScroll://滑动后手抬起时,折线是否跟随初速度左右滑动 isScroll = array.getBoolean(attr, isScroll); break; } } array.recycle(); }
数据提供数据使用三个List分别为x轴的数据,y轴数据,折线数据
/** * x轴坐标对应的数据 */ private List<brokenlineVIEw.XValue> xValues = new ArrayList<>(); /** * y轴坐标对应的数据 */ private List<brokenlineVIEw.YValue> yValues = new ArrayList<>(); /** * 折线对应的数据 */ private List<brokenlineVIEw.lineValue> lineValues = new ArrayList<>();
绘制xy轴线,箭头
设置大小最好是dp转成px。
//绘制Y轴线 canvas.drawline(xOrigin - xylinewidth / 2, 0, xOrigin - xylinewidth / 2, yOrigin, xyPaint); //绘制y轴箭头 xyPaint.setStyle(Paint.Style.stroke); Path path = new Path(); path.moveto(xOrigin - xylinewidth / 2 - dptopx(5), dptopx(12)); path.lineto(xOrigin - xylinewidth / 2, xylinewidth / 2); path.lineto(xOrigin - xylinewidth / 2 + dptopx(5), dptopx(12)); canvas.drawPath(path, xyPaint);//绘制X轴坐标 canvas.drawline(xOrigin, yOrigin + xylinewidth / 2, wIDth, yOrigin + xylinewidth / 2, xyPaint); //绘制x轴箭头 xyPaint.setStyle(Paint.Style.stroke); path = new Path(); //整个X轴的长度 float xLength = xInit + interval * (xValues.size() - 1) + (wIDth - xOrigin) * 0.1f; if (xLength < wIDth) xLength = wIDth; path.moveto(xLength - dptopx(12), yOrigin + xylinewidth / 2 - dptopx(5)); path.lineto(xLength - xylinewidth / 2, yOrigin + xylinewidth / 2); path.lineto(xLength - dptopx(12), yOrigin + xylinewidth / 2 + dptopx(5)); canvas.drawPath(path, xyPaint);
绘制xy轴刻度,文字 //绘制y轴刻度 int yLength = (int) (yOrigin * (1 - 0.1f) / (yValues.size() - 1));//y轴上面空出10%,计算出y轴刻度间距 for (int i = 0; i < yValues.size(); i++) { //绘制Y轴刻度 canvas.drawline(xOrigin, yOrigin - yLength * i + xylinewidth / 2, xOrigin , yOrigin - yLength * i + xylinewidth / 2, xyPaint); xyTextPaint.setcolor(xytextcolor); //绘制Y轴文本 String text = yValues.get(i).value; Rect rect = getTextBounds(text, xyTextPaint); canvas.drawText(text, 0, text.length(), xOrigin - xylinewidth - dptopx(2) - rect.wIDth(), yOrigin - yLength * i + rect.height() / 2, xyTextPaint); } //绘制x轴刻度 for (int i = 0; i < xValues.size(); i++) { float x = xInit + interval * i; if (x >= xOrigin) {//只绘制从原点开始的区域 xyTextPaint.setcolor(xytextcolor); canvas.drawline(x, yOrigin, x, yOrigin , xyPaint); //绘制X轴文本 String text = xValues.get(i).value; Rect rect = getTextBounds(text, xyTextPaint); if (i == selectIndex - 1) { //绘制x轴选中文字和框 xyTextPaint.setStyle(Paint.Style.FILL); xyTextPaint.setcolor(contentcolor); canvas.drawRoundRect(new RectF(x - xValueRect.wIDth() / 2 - dptopx(3), yOrigin + xylinewidth + dptopx(1), x + xValueRect.wIDth() / 2 + dptopx(3), yOrigin + xylinewidth + dptopx(2) + xValueRect.height() + dptopx(2)), dptopx(2), dptopx(2), xyTextPaint); xyTextPaint.setcolor(color.WHITE); canvas.drawText(text, 0, text.length(), x - rect.wIDth() / 2, yOrigin + xylinewidth + dptopx(2) + rect.height(), xyTextPaint); } else { canvas.drawText(text, 0, text.length(), x - rect.wIDth() / 2, yOrigin + xylinewidth + dptopx(2) + rect.height(), xyTextPaint); } } }
绘制折线/** * 绘制折线 */ private voID drawbrokenline(Canvas canvas) { linePaint.setStyle(Paint.Style.stroke); linePaint.setcolor(linecolor); //绘制折线 Path path = new Path(); float x = xInit + interval * 0; float y = (float) (yOrigin - yOrigin * (1 - 0.1f) * lineValues.get(0).num / yValues.get(yValues.size() - 1).num); path.moveto(x, y); for (int i = 1; i < xValues.size(); i++) { x = xInit + interval * i; y = (float) (yOrigin - yOrigin * (1 - 0.1f) * lineValues.get(i).num / yValues.get(yValues.size() - 1).num); if (x<0)x=0; if (y<0)y=0; path.lineto(x, y); } canvas.drawPath(path, linePaint); }
绘制折线上点位圆圈 /** * 绘制折线对应的点位圆圈 */ private voID drawbrokenPoint(Canvas canvas) { float dp2 = dptopx(2); float dp4 = dptopx(4); float dp7 = dptopx(7); //绘制节点对应的原点 for (int i = 0; i < xValues.size(); i++) { float x = xInit + interval * i; float y = (float) (yOrigin - yOrigin * (1 - 0.1f) * lineValues.get(i).num / yValues.get(yValues.size() - 1).num); //绘制选中的点 if (i == selectIndex - 1) { linePaint.setStyle(Paint.Style.FILL); linePaint.setcolor(contentcolor); canvas.drawCircle(x, y, dp7, linePaint); drawfloatTextBox(canvas, x, y - dp7, lineValues.get(i).value); } //绘制折线点 linePaint.setStyle(Paint.Style.FILL); linePaint.setcolor(color.WHITE); canvas.drawCircle(x, y, dp2, linePaint); linePaint.setStyle(Paint.Style.stroke); linePaint.setcolor(linecolor); canvas.drawCircle(x, y, dp2, linePaint); } }
绘制选中d出框和x轴文字选中框 if (i == selectIndex - 1) { //绘制x轴选中文字和框 xyTextPaint.setStyle(Paint.Style.FILL); xyTextPaint.setcolor(contentcolor); canvas.drawRoundRect(new RectF(x - xValueRect.wIDth() / 2 - dptopx(3), yOrigin + xylinewidth + dptopx(1), x + xValueRect.wIDth() / 2 + dptopx(3), yOrigin + xylinewidth + dptopx(2) + xValueRect.height() + dptopx(2)), dptopx(2), dptopx(2), xyTextPaint); xyTextPaint.setcolor(color.WHITE); canvas.drawText(text, 0, text.length(), x - rect.wIDth() / 2, yOrigin + xylinewidth + dptopx(2) + rect.height(), xyTextPaint); }
if (i == selectIndex - 1) { linePaint.setStyle(Paint.Style.FILL); linePaint.setcolor(contentcolor); canvas.drawCircle(x, y, dp7, linePaint); drawfloatTextBox(canvas, x, y - dp7, lineValues.get(i).value); } /** * 绘制点位信息d出框 */ private voID drawfloatTextBox(Canvas canvas, float x, float y, String text) { int dp6 = dptopx(6); int dp45 = dptopx(45); Path path = new Path(); path.moveto(x, y); path.lineto(x - dp6, y - dp6); path.lineto(x - dp45, y - dp6); path.lineto(x - dp45, y - dp6 - dp45); path.lineto(x + dp45, y - dp6 - dp45); path.lineto(x + dp45, y - dp6); path.lineto(x + dp6, y - dp6); path.lineto(x, y); canvas.drawPath(path, linePaint); //点位信息文字 linePaint.setcolor(color.WHITE); linePaint.setTextSize(sptopx(14)); Rect rect = getTextBounds(text + "", linePaint); canvas.drawText(text + "", x - rect.wIDth() / 2, y - dp6 - (dp45 - rect.height()) / 2, linePaint); }
x轴滑动/** * 滑动x轴 */ private voID scroll() { if (!isScroll) return; final float veLocity = getVeLocity();//得到当前速度 float scrollLength = maxXInit - minXInit; if (Math.abs(veLocity) < 10000)//最大速度 scrollLength = (maxXInit - minXInit) * Math.abs(veLocity) / 10000; ValueAnimator animator = ValueAnimator.offloat(0, scrollLength); animator.setDuration((long) (scrollLength / (maxXInit - minXInit) * 1000));//滑动时间最大1000毫秒 animator.setInterpolator(new DecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @OverrIDe public voID onAnimationUpdate(ValueAnimator valueAnimator) { float lineValues = (float) valueAnimator.getAnimatedValue(); if (veLocity < 0 && xInit > minXInit) {//向左滑动 if (xInit - lineValues <= minXInit) xInit = minXInit; else xInit = xInit - lineValues; } else if (veLocity > 0 && xInit < maxXInit) {//向右滑动 if (xInit + lineValues >= maxXInit) xInit = maxXInit; else xInit = xInit + lineValues; } invalIDate(); } }); animator.addListener(new Animator.AnimatorListener() { @OverrIDe public voID onAnimationStart(Animator animator) { isScrolling = true;//正在滑动 } @OverrIDe public voID onAnimationEnd(Animator animator) { isScrolling = false; } @OverrIDe public voID onAnimationCancel(Animator animator) { isScrolling = false; } @OverrIDe public voID onAnimationRepeat(Animator animator) { } }); animator.start(); }
完整类如下
public class brokenlineVIEw extends VIEw { //xy坐标轴颜色 private int xylinecolor = 0; /** * xy坐标轴线的宽度 */ private int xylinewidth = 1; /** * xy坐标轴文字颜色 */ private int xytextcolor = 0; /** * xy坐标轴文字大小 */ private int xytextsize = sptopx(12); /** * 折线图中折线的颜色 */ private int linecolor = 0; /** * 折线图中点位信息颜色 */ private int contentcolor = 0; /** * x轴各个坐标点水平间距 */ private int interval = dptopx(50); /** * 背景颜色 */ private int bgcolor = 0; /** * 是否在ACTION_UP时,根据速度进行自滑动,没有要求,建议关闭,过于占用GPU */ private boolean isScroll = false; /** * 绘制XY轴坐标对应的画笔 */ private Paint xyPaint; /** * 绘制XY轴的文本对应的画笔 */ private Paint xyTextPaint; /** * 画折线对应的画笔 */ private Paint linePaint; private int wIDth; private int height; /** * x轴的原点坐标 */ private int xOrigin; /** * y轴的原点坐标 */ private int yOrigin; /** * 第一个点X的坐标 */ private float xInit; /** * 第一个点对应的最大Y坐标 */ private float maxXInit; /** * 第一个点对应的最小X坐标 */ private float minXInit; /** * x轴坐标对应的数据 */ private List<XValue> xValues = new ArrayList<>(); /** * y轴坐标对应的数据 */ private List<YValue> yValues = new ArrayList<>(); /** * 折线对应的数据 */ private List<lineValue> lineValues = new ArrayList<>(); /** * 当前选中点 */ private int selectIndex = 1; /** * X轴刻度文本对应的最大矩形,为了选中时,在x轴文本画的框框大小一致 */ private Rect xValueRect; /** * 速度检测 */ private VeLocityTracker veLocityTracker; public brokenlineVIEw(Context context) { this(context, null); } public brokenlineVIEw(Context context, AttributeSet attrs) { this(context, attrs, 0); } public brokenlineVIEw(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPar(context, attrs, defStyleAttr); initPaint(); } /** * 初始化画笔 */ private voID initPaint() { xyPaint = new Paint(); xyPaint.setAntiAlias(true); xyPaint.setstrokeWIDth(xylinewidth); xyPaint.setstrokeCap(Paint.Cap.ROUND); xyPaint.setcolor(xylinecolor); xyTextPaint = new Paint(); xyTextPaint.setAntiAlias(true); xyTextPaint.setTextSize(xytextsize); xyTextPaint.setstrokeCap(Paint.Cap.ROUND); xyTextPaint.setcolor(xytextcolor); xyTextPaint.setStyle(Paint.Style.stroke); linePaint = new Paint(); linePaint.setAntiAlias(true); linePaint.setstrokeWIDth(xylinewidth); linePaint.setstrokeCap(Paint.Cap.ROUND); linePaint.setcolor(linecolor); linePaint.setStyle(Paint.Style.stroke); } /** * 初始化自定义属性 */ private voID initPar(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.brokenlineVIEw, defStyleAttr, 0); int count = array.getIndexCount(); for (int i = 0; i < count; i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.brokenlineVIEw_xylinecolor://xy轴线颜色 xylinecolor = array.getcolor(attr, xylinecolor); break; case R.styleable.brokenlineVIEw_xylinewidth://xy轴线的宽度 xylinewidth = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xylinewidth, getResources().getdisplayMetrics())); break; case R.styleable.brokenlineVIEw_xytextcolor://xy轴上的文字颜色 xytextcolor = array.getcolor(attr, xytextcolor); break; case R.styleable.brokenlineVIEw_xytextsize://xy轴上的文字大小 xytextsize = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xytextsize, getResources().getdisplayMetrics())); break; case R.styleable.brokenlineVIEw_linecolor://折线的颜色 linecolor = array.getcolor(attr, linecolor); break; case R.styleable.brokenlineVIEw_contentcolor://折线点位信息颜色 contentcolor = array.getcolor(attr, contentcolor); break; case R.styleable.brokenlineVIEw_interval://x轴坐标点间距 interval = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, interval, getResources().getdisplayMetrics())); break; case R.styleable.brokenlineVIEw_bgcolor: //背景颜色 bgcolor = array.getcolor(attr, bgcolor); break; case R.styleable.brokenlineVIEw_isScroll://滑动后手抬起时,折线是否跟随初速度左右滑动 isScroll = array.getBoolean(attr, isScroll); break; } } array.recycle(); } @OverrIDe protected voID onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed) { //得到宽度高度 wIDth = getWIDth(); height = getHeight(); //Y轴字宽度 float textYWdith = getTextBounds("000", xyTextPaint).wIDth(); for (int i = 0; i < yValues.size(); i++) {//求取y轴文本最大的宽度 float temp = getTextBounds(yValues.get(i).value, xyTextPaint).wIDth(); if (temp > textYWdith) textYWdith = temp; } int dp2 = dptopx(2); int dp3 = dptopx(3); xOrigin = (int) (dp2 + textYWdith + dp2 + xylinewidth);//dp2是y轴文本距离左边,以及距离y轴的距离 xValueRect = getTextBounds("000", xyTextPaint); //X轴字高度 float textxHeight = xValueRect.height(); for (int i = 0; i < xValues.size(); i++) {//求取x轴文本最大的高度 Rect rect = getTextBounds(xValues.get(i).value + "", xyTextPaint); if (rect.height() > textxHeight) textxHeight = rect.height(); if (rect.wIDth() > xValueRect.wIDth()) xValueRect = rect; } yOrigin = (int) (height - dp2 - textxHeight - dp3 - xylinewidth);//dp3是x轴文本距离底边,dp2是x轴文本距离x轴的距离 xInit = interval + xOrigin; minXInit = wIDth - (wIDth - xOrigin) * 0.1f - interval * (xValues.size() - 1);//减去0.1f是因为最后一个X周刻度距离右边的长度为X轴可见长度的10% maxXInit = xInit; } super.onLayout(changed, left, top, right, bottom); } @OverrIDe protected voID onDraw(Canvas canvas) {// super.onDraw(canvas); canvas.drawcolor(bgcolor); drawXY(canvas); drawbrokenlineAndPoint(canvas); } /** * 绘制折线和折线点 */ private voID drawbrokenlineAndPoint(Canvas canvas) { if (xValues.size() <= 0) return; //设置显示折线的图层 int layer = canvas.saveLayer(0, 0, wIDth, height, null, Canvas.ALL_SAVE_FLAG); drawbrokenline(canvas); drawbrokenPoint(canvas); // 将折线超出x轴坐标的部分截取掉 linePaint.setStyle(Paint.Style.FILL); linePaint.setcolor(bgcolor); linePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); RectF rectF = new RectF(0, 0, xOrigin, height); canvas.drawRect(rectF, linePaint); linePaint.setXfermode(null); //保存图层 canvas.restoretoCount(layer); } /** * 绘制折线对应的点位圆圈 */ private voID drawbrokenPoint(Canvas canvas) { float dp2 = dptopx(2); float dp4 = dptopx(4); float dp7 = dptopx(7); //绘制节点对应的原点 for (int i = 0; i < xValues.size(); i++) { float x = xInit + interval * i; float y = (float) (yOrigin - yOrigin * (1 - 0.1f) * lineValues.get(i).num / yValues.get(yValues.size() - 1).num); //绘制折线点 linePaint.setStyle(Paint.Style.FILL); linePaint.setcolor(color.WHITE); canvas.drawCircle(x, y, dp2, linePaint); linePaint.setStyle(Paint.Style.stroke); linePaint.setcolor(linecolor); canvas.drawCircle(x, y, dp2, linePaint); //绘制选中的点 if (i == selectIndex - 1) { linePaint.setStyle(Paint.Style.FILL); linePaint.setcolor(contentcolor); canvas.drawCircle(x, y, dp7, linePaint); drawfloatTextBox(canvas, x, y - dp7, lineValues.get(i).value); } } } /** * 绘制点位信息d出框 */ private voID drawfloatTextBox(Canvas canvas, float x, float y, String text) { int dp6 = dptopx(6); int dp45 = dptopx(45); Path path = new Path(); path.moveto(x, y); path.lineto(x - dp6, y - dp6); path.lineto(x - dp45, y - dp6); path.lineto(x - dp45, y - dp6 - dp45); path.lineto(x + dp45, y - dp6 - dp45); path.lineto(x + dp45, y - dp6); path.lineto(x + dp6, y - dp6); path.lineto(x, y); canvas.drawPath(path, linePaint); //点位信息文字 linePaint.setcolor(color.WHITE); linePaint.setTextSize(sptopx(14)); Rect rect = getTextBounds(text + "", linePaint); canvas.drawText(text + "", x - rect.wIDth() / 2, y - dp6 - (dp45 - rect.height()) / 2, linePaint); } /** * 绘制折线 */ private voID drawbrokenline(Canvas canvas) { linePaint.setStyle(Paint.Style.stroke); linePaint.setcolor(linecolor); //绘制折线 Path path = new Path(); float x = xInit + interval * 0; float y = (float) (yOrigin - yOrigin * (1 - 0.1f) * lineValues.get(0).num / yValues.get(yValues.size() - 1).num); path.moveto(x, y); for (int i = 1; i < xValues.size(); i++) { x = xInit + interval * i; y = (float) (yOrigin - yOrigin * (1 - 0.1f) * lineValues.get(i).num / yValues.get(yValues.size() - 1).num); if (x < 0) x = 0; if (y < 0) y = 0; path.lineto(x, y); } canvas.drawPath(path, linePaint); } /** * 绘制XY坐标 * * @param canvas */ private voID drawXY(Canvas canvas) { //绘制Y轴线 canvas.drawline(xOrigin - xylinewidth / 2, 0, xOrigin - xylinewidth / 2, yOrigin, xyPaint); //绘制y轴箭头 xyPaint.setStyle(Paint.Style.stroke); Path path = new Path(); path.moveto(xOrigin - xylinewidth / 2 - dptopx(5), dptopx(12)); path.lineto(xOrigin - xylinewidth / 2, xylinewidth / 2); path.lineto(xOrigin - xylinewidth / 2 + dptopx(5), dptopx(12)); canvas.drawPath(path, xyPaint); //绘制y轴刻度 int yLength = (int) (yOrigin * (1 - 0.1f) / (yValues.size() - 1));//y轴上面空出10%,计算出y轴刻度间距 for (int i = 0; i < yValues.size(); i++) { //绘制Y轴刻度 canvas.drawline(xOrigin, yOrigin - yLength * i + xylinewidth / 2, xOrigin, yOrigin - yLength * i + xylinewidth / 2, xyPaint); xyTextPaint.setcolor(xytextcolor); //绘制Y轴文本 String text = yValues.get(i).value; Rect rect = getTextBounds(text, xyTextPaint); canvas.drawText(text, 0, text.length(), xOrigin - xylinewidth - dptopx(2) - rect.wIDth(), yOrigin - yLength * i + rect.height() / 2, xyTextPaint); } //绘制X轴坐标 canvas.drawline(xOrigin, yOrigin + xylinewidth / 2, wIDth, yOrigin + xylinewidth / 2, xyPaint); //绘制x轴箭头 xyPaint.setStyle(Paint.Style.stroke); path = new Path(); //整个X轴的长度 float xLength = xInit + interval * (xValues.size() - 1) + (wIDth - xOrigin) * 0.1f; if (xLength < wIDth) xLength = wIDth; path.moveto(xLength - dptopx(12), yOrigin + xylinewidth / 2 - dptopx(5)); path.lineto(xLength - xylinewidth / 2, yOrigin + xylinewidth / 2); path.lineto(xLength - dptopx(12), yOrigin + xylinewidth / 2 + dptopx(5)); canvas.drawPath(path, xyPaint); //绘制x轴刻度 for (int i = 0; i < xValues.size(); i++) { float x = xInit + interval * i; if (x >= xOrigin) {//只绘制从原点开始的区域 xyTextPaint.setcolor(xytextcolor); canvas.drawline(x, yOrigin, x, yOrigin, xyPaint); //绘制X轴文本 String text = xValues.get(i).value; Rect rect = getTextBounds(text, xyTextPaint); if (i == selectIndex - 1) { //绘制x轴选中文字和框 xyTextPaint.setStyle(Paint.Style.FILL); xyTextPaint.setcolor(contentcolor); canvas.drawRoundRect(new RectF(x - xValueRect.wIDth() / 2 - dptopx(3), yOrigin + xylinewidth + dptopx(1), x + xValueRect.wIDth() / 2 + dptopx(3), yOrigin + xylinewidth + dptopx(2) + xValueRect.height() + dptopx(2)), dptopx(2), dptopx(2), xyTextPaint); xyTextPaint.setcolor(color.WHITE); canvas.drawText(text, 0, text.length(), x - rect.wIDth() / 2, yOrigin + xylinewidth + dptopx(2) + rect.height(), xyTextPaint); } else { canvas.drawText(text, 0, text.length(), x - rect.wIDth() / 2, yOrigin + xylinewidth + dptopx(2) + rect.height(), xyTextPaint); } } } } private float startX; @OverrIDe public boolean ontouchEvent(MotionEvent event) { if (isScrolling) return super.ontouchEvent(event); this.getParent().requestdisallowIntercepttouchEvent(true);//当该vIEw获得点击事件,就请求父控件不拦截事件 obtainVeLocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); break; case MotionEvent.ACTION_MOVE: if (interval * xValues.size() > wIDth - xOrigin) {//当期的宽度不足以呈现全部数据 float dis = event.getX() - startX; startX = event.getX(); if (xInit + dis < minXInit) { xInit = minXInit; } else if (xInit + dis > maxXInit) { xInit = maxXInit; } else { xInit = xInit + dis; } invalIDate(); } break; case MotionEvent.ACTION_UP: clickAction(event); scroll(); this.getParent().requestdisallowIntercepttouchEvent(false); recycleVeLocityTracker(); break; case MotionEvent.ACTION_CANCEL: this.getParent().requestdisallowIntercepttouchEvent(false); recycleVeLocityTracker(); break; } return true; } //是否正在滑动 private boolean isScrolling = false; /** * 滑动x轴 */ private voID scroll() { if (!isScroll) return; final float veLocity = getVeLocity();//得到当前速度 float scrollLength = maxXInit - minXInit; if (Math.abs(veLocity) < 10000)//最大速度 scrollLength = (maxXInit - minXInit) * Math.abs(veLocity) / 10000; ValueAnimator animator = ValueAnimator.offloat(0, scrollLength); animator.setDuration((long) (scrollLength / (maxXInit - minXInit) * 1000));//滑动时间最大1000毫秒 animator.setInterpolator(new DecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @OverrIDe public voID onAnimationUpdate(ValueAnimator valueAnimator) { float lineValues = (float) valueAnimator.getAnimatedValue(); if (veLocity < 0 && xInit > minXInit) {//向左滑动 if (xInit - lineValues <= minXInit) xInit = minXInit; else xInit = xInit - lineValues; } else if (veLocity > 0 && xInit < maxXInit) {//向右滑动 if (xInit + lineValues >= maxXInit) xInit = maxXInit; else xInit = xInit + lineValues; } invalIDate(); } }); animator.addListener(new Animator.AnimatorListener() { @OverrIDe public voID onAnimationStart(Animator animator) { isScrolling = true;//正在滑动 } @OverrIDe public voID onAnimationEnd(Animator animator) { isScrolling = false; } @OverrIDe public voID onAnimationCancel(Animator animator) { isScrolling = false; } @OverrIDe public voID onAnimationRepeat(Animator animator) { } }); animator.start(); } /** * 获取速度 */ private float getVeLocity() { if (veLocityTracker != null) { veLocityTracker.computeCurrentVeLocity(1000); return veLocityTracker.getXVeLocity(); } return 0; } /** * 点击X轴坐标或者折线节点 * * @param event */ private voID clickAction(MotionEvent event) { int dp8 = dptopx(8); float eventX = event.getX(); float eventY = event.getY(); for (int i = 0; i < xValues.size(); i++) { //节点 float x = xInit + interval * i; float y = (float) (yOrigin - yOrigin * (1 - 0.1f) * lineValues.get(i).num / yValues.get(yValues.size() - 1).num); if (eventX >= x - dp8 && eventX <= x + dp8 && eventY >= y - dp8 && eventY <= y + dp8 && selectIndex != i + 1) {//每个节点周围8dp都是可点击区域 selectIndex = i + 1; invalIDate(); return; } //X轴刻度 String text = xValues.get(i).value; Rect rect = getTextBounds(text, xyTextPaint); x = xInit + interval * i; y = yOrigin + xylinewidth + dptopx(2); if (eventX >= x - rect.wIDth() / 2 - dp8 && eventX <= x + rect.wIDth() + dp8 / 2 && eventY >= y - dp8 && eventY <= y + rect.height() + dp8 && selectIndex != i + 1) { selectIndex = i + 1; invalIDate(); return; } } } /** * 获取速度跟踪器 * * @param event */ private voID obtainVeLocityTracker(MotionEvent event) { if (!isScroll) return; if (veLocityTracker == null) { veLocityTracker = VeLocityTracker.obtain(); } veLocityTracker.addMovement(event); } /** * 回收速度跟踪器 */ private voID recycleVeLocityTracker() { if (veLocityTracker != null) { veLocityTracker.recycle(); veLocityTracker = null; } } public int getSelectIndex() { return selectIndex; } public voID setSelectIndex(int selectIndex) { this.selectIndex = selectIndex; invalIDate(); } public voID setxValue(List<XValue> xValues) { this.xValues = xValues; } public voID setyValue(List<YValue> yValues) { this.yValues = yValues; invalIDate(); } public voID setValue(List<lineValue> lineValues) { this.lineValues = lineValues; invalIDate(); } public voID setValue(List<lineValue> lineValues, List<XValue> xValues, List<YValue> yValues) { this.lineValues = lineValues; this.xValues = xValues; this.yValues = yValues; invalIDate(); } public List<XValue> getxValue() { return xValues; } public List<YValue> getyValue() { return yValues; } public List<lineValue> getValue() { return lineValues; } /** * 获取丈量文本的矩形 * * @param text * @param paint * @return */ private Rect getTextBounds(String text, Paint paint) { Rect rect = new Rect(); paint.getTextBounds(text, 0, text.length(), rect); return rect; } /** * dp转化成为px * * @param dp * @return */ private int dptopx(int dp) { float density = getContext().getResources().getdisplayMetrics().density; return (int) (dp * density + 0.5f * (dp >= 0 ? 1 : -1)); } /** * sp转化为px * * @param sp * @return */ private int sptopx(int sp) { float scaledDensity = getContext().getResources().getdisplayMetrics().scaledDensity; return (int) (scaledDensity * sp + 0.5f * (sp >= 0 ? 1 : -1)); } public static class XValue { public double num; public String value; public XValue(double num, String value) { this.num = num; this.value = value; } } public static class YValue { public double num; public String value; public YValue(double num, String value) { this.num = num; this.value = value; } } public static class lineValue { //具体数值 public double num; //显示值 public String value; public lineValue(double num, String value) { this.num = num; this.value = value; } }}
使用方法布局里面添加brokenlineVIEw
<test.com.vIEw.brokenlineVIEw androID:ID="@+ID/chartvIEw" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:layout_marginBottom="8dp" app:bgcolor="@color/white" app:interval="50dp" app:isScroll="false" app:layout_constraintBottom_toBottomOf="parent" app:linecolor="@color/red" app:contentcolor="@color/red" app:xylinecolor="#494848" app:xylinewidth="1.5dp" app:xytextcolor="#494848" app:xytextsize="15sp"/>
activity里面数据准备
private voID initData() { //模拟折线数据 for (int i = 1; i <= 30; i++){ brokenlineVIEw.XValue xValue = new brokenlineVIEw.XValue(i,i+"号"); xValues.add(xValue); double value =new BigDecimal(Math.random()*10).setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue(); brokenlineVIEw.lineValue lineValue = new brokenlineVIEw.lineValue(value,i+"号点位="+value); lineValues.add(lineValue); } for (int i = 0; i <=10; i++) { brokenlineVIEw.YValue yValue = new brokenlineVIEw.YValue(i,i+""); yValues.add(yValue); } brokenlineVIEw.setValue(lineValues, xValues, yValues); } private voID initVIEw() { brokenlineVIEw = (brokenlineVIEw) findVIEwByID(R.ID.brokenlineVIEw); }
总结到此自定义的折线图就介绍完了,具体细节的地方讲的不是很详细望包涵,如果要求不是太高可以直接复制上述类代码拿来使用,要求高的就不适用了,因为只是一个测试的折线图,功能不是很完善,当然也可以在上面基础修改增加自己需要的东西。
点赞收藏分享文章举报浪克oo发布了40 篇原创文章 · 获赞 70 · 访问量 9万+私信 关注 总结以上是内存溢出为你收集整理的android动态绘制自定义折线图(不使用第三方)全部内容,希望文章能够帮你解决android动态绘制自定义折线图(不使用第三方)所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)