这篇文章主要为大家详细介绍了android实现可以滑动的平滑曲线图,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
本文实例为大家分享了android实现可以滑动的平滑曲线图的具体代码,供大家参考,具体内容如下
直接上代码,里面有详细注解
1 attr 属性编写
< attr name="xy_line_color" format="color" /> < !-- xy坐标轴宽度 --> < attr name="xy_line_width" format="dimension" /> < !-- xy坐标轴文字颜色 --> < attr name="xy_text_color" format="color" /> < !-- xy坐标轴文字大小 --> < attr name="xy_text_size" format="dimension" /> < !-- 折线图中折线的颜色 --> < attr name="line_color" format="color" /> < attr name="interval" format="dimension" /> < !-- 背景颜色 --> < attr name="bg_color" format="color" /> < !-- 曲线选中外部颜色 --> < attr name="select_circle_color" format="color" /> < !-- 曲线选中内部颜色 --> < attr name="select_reminder_color" format="color" /> < !--是否抬手滚动--> < attr name="isScroll" format="boolean" /> < declare-styleable name="ChartView"> < attr name="xy_line_color" /> < attr name="xy_line_width" /> < attr name="xy_text_color" /> < attr name="xy_text_size" /> < attr name="line_color" /> < attr name="interval" /> < attr name="bg_color" /> < attr name="select_circle_color" /> < attr name="select_reminder_color" /> < attr name="isScroll" /> < !--提示框跟滑动显示的位置--> < attr name="show_position"> < enum name="first" value="1" /> < enum name="middle" value="2" /> < enum name="end" value="3" /> < /attr> < /declare-styleable>
2 ChartView
package com.laisontech.commonuilibrary.customviews; import android.animation.Animator; import android.animation.ValueAnimator; 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.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.animation.DecelerateInterpolator; import com.laisontech.commonuilibrary.R; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 自定义折线图 */ public class ChartView extends View { private static final int FIRST = 1; private static final int MIDDLE = 2; private static final int END = 3; //xy坐标轴颜色 private int xyLineColor = 0xffCFE2CF; //折线选中的圆形颜色 private int selectCircleColor = 0xff00A8FF; //选中数据提示框颜色 private int selectReminderColor = 0xff00A8FF; //折线中圆形内部部颜色 private int xyTextColor = 0xff0014FF; //折线图中折线的颜色 private int lineColor = 0xffFD00FF; //xy坐标轴宽度 private int xyLineWidth = dpToPx(1); //xy坐标轴文字大小 private int xyTextSize = spToPx(12); //x轴各个坐标点水平间距 private int interval = dpToPx(40); //背景颜色 private int bgColor = 0xffffffff; //是否有起手时的滑动感 private boolean isScroll = false; //提示框显示位置 private int mShowPositionType = 3; //绘制XY轴坐标对应的画笔 private Paint mXYPaint; //绘制XY轴的文本对应的画笔 private Paint mXYTextPaint; //画折线对应的画笔 private Paint mSpinnerLinePaint; private int width; private int height; //x轴的原点坐标 private int mXOri; //y轴的原点坐标 private int mYOri; //第一个点X的坐标 private float mXInit; //第一个点对应的最大X坐标 private float maxXInit; //第一个点对应的最小X坐标 private float minXInit; //x轴坐标对应的数据 private List mXData = new ArrayList(); //y轴坐标对应的数据 private List mYData = new ArrayList(); //折线对应的数据 private Map mSpinnerValue = new HashMap(); //点击的点对应的X轴的第几个点,默认1 private int selectIndex = 1; //X轴刻度文本对应的最大矩形,为了选中时,在x轴文本画的框框大小一致,获取从数据中得到的x轴数据,获得最长数据 private Rect xValueRect; //速度检测器 private VelocityTracker mTracker; //是否为短距离滑动 private boolean isShortSlide = false; //获取尺寸的的中间 private int mSelectMiddle = 0; //曲线切率 private float mLineSmoothness = 0.18f; public ChartView(Context context) { this(context, null); } public ChartView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ChartView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr); initPaint(); } //设置切率 public void setLineSmoothness(float lineSmoothness) { if (lineSmoothness != this.mLineSmoothness) { this.mLineSmoothness = lineSmoothness; } } /** * 初始化 */ private void initPaint() { mXYPaint = new Paint(); mXYPaint.setAntiAlias(true); mXYPaint.setStrokeWidth(xyLineWidth); mXYPaint.setStrokeJoin(Paint.Join.ROUND); mXYPaint.setColor(xyLineColor); mXYTextPaint = new Paint(); mXYTextPaint.setAntiAlias(true); mXYTextPaint.setTextSize(xyTextSize); mXYTextPaint.setStrokeJoin(Paint.Join.ROUND); mXYTextPaint.setColor(xyTextColor); mXYTextPaint.setStyle(Paint.Style.STROKE); mSpinnerLinePaint = new Paint(); mSpinnerLinePaint.setAntiAlias(true); mSpinnerLinePaint.setStrokeWidth(xyLineWidth); mSpinnerLinePaint.setColor(lineColor); mSpinnerLinePaint.setStyle(Paint.Style.STROKE); mSpinnerLinePaint.setStrokeJoin(Paint.Join.ROUND); } /** * 初始化 * * @param context * @param attrs * @param defStyleAttr */ private void init(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ChartView, defStyleAttr, 0); int count = array.getIndexCount(); for (int i = 0; i < count; i++) { int attr = array.getIndex(i); if (attr == R.styleable.ChartView_xy_line_color) { xyLineColor = array.getColor(attr, xyLineColor); } else if (attr == R.styleable.ChartView_xy_line_width) { xyLineWidth = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xyLineWidth, getResources().getDisplayMetrics())); } else if (attr == R.styleable.ChartView_xy_text_color) { xyTextColor = array.getColor(attr, xyTextColor); } else if (attr == R.styleable.ChartView_xy_text_size) { xyTextSize = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xyTextSize, getResources().getDisplayMetrics())); } else if (attr == R.styleable.ChartView_line_color) { lineColor = array.getColor(attr, lineColor); } else if (attr == R.styleable.ChartView_interval) { interval = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, interval, getResources().getDisplayMetrics())); } else if (attr == R.styleable.ChartView_bg_color) { bgColor = array.getColor(attr, bgColor); } else if (attr == R.styleable.ChartView_select_circle_color) { selectCircleColor = array.getColor(attr, selectCircleColor); } else if (attr == R.styleable.ChartView_select_reminder_color) { selectReminderColor = array.getColor(attr, selectReminderColor); } else if (attr == R.styleable.ChartView_isScroll) { isScroll = array.getBoolean(attr, isScroll); } else if (attr == R.styleable.ChartView_show_position) { mShowPositionType = array.getInt(attr, mShowPositionType); } } 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(mYData.get(getListItemMaxIndex(mYData)) + "", mXYTextPaint).width(); for (int i = 0; i < mYData.size(); i++) {//求取y轴文本最大的宽度 float temp = getTextBounds(mYData.get(i) + "", mXYTextPaint).width(); if (temp > textYWdith) textYWdith = temp; } int dp2 = dpToPx(2); int dp3 = dpToPx(3); mXOri = (int) (dp2 + textYWdith + dp2 + xyLineWidth); //获取x轴的最长文本的宽度所占的矩形 xValueRect = getTextBounds(mXData.get(getListItemMaxIndex(mXData)), mXYTextPaint); //X轴文本高度 float textXHeight = xValueRect.height(); for (int i = 0; i < mXData.size(); i++) { Rect rect = getTextBounds(mXData.get(i) + "", mXYTextPaint); if (rect.height() > textXHeight) textXHeight = rect.height(); if (rect.width() > xValueRect.width()) xValueRect = rect; } mYOri = (int) (height - dp2 - textXHeight - dp3 - xyLineWidth); mXInit = mXOri + xValueRect.width() / 2 + dpToPx(5); minXInit = width - (width - mXOri) * 0.1f - interval * (mXData.size() - 1); maxXInit = mXInit; } selectIndex = getSelectIndexFromShowType(mShowPositionType); super.onLayout(changed, left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(bgColor); drawXY(canvas); drawBrokenLineAndPoint(canvas); } /** * 绘制交点处对应的点 */ private void drawBrokenLineAndPoint(Canvas canvas) { if (mXData.size() 0) { previousPointX = getSpinnerPoint(i - 1).x; previousPointY = getSpinnerPoint(i - 1).y; } else { //用当前点表示上一个点 previousPointX = currentPointX; previousPointY = currentPointY; } } if (Float.isNaN(prePreviousPointX)) { //是前两个点? if (i > 1) { prePreviousPointX = getSpinnerPoint(i - 2).x; prePreviousPointY = getSpinnerPoint(i - 2).y; } else { //当前点表示上上个点 prePreviousPointX = previousPointX; prePreviousPointY = previousPointY; } } // 判断是不是最后一个点了 if (i < lineSize - 1) { nextPointX = getSpinnerPoint(i + 1).x; nextPointY = getSpinnerPoint(i + 1).y; } else { //用当前点表示下一个点 nextPointX = currentPointX; nextPointY = currentPointY; } if (i == 0) { // 将Path移动到开始点 path.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 + (mLineSmoothness * firstDiffX); final float firstControlPointY = previousPointY + (mLineSmoothness * firstDiffY); final float secondControlPointX = currentPointX - (mLineSmoothness * secondDiffX); final float secondControlPointY = currentPointY - (mLineSmoothness * secondDiffY); //画出曲线 path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY, currentPointX, currentPointY); } // 更新 prePreviousPointX = previousPointX; prePreviousPointY = previousPointY; previousPointX = currentPointX; previousPointY = currentPointY; currentPointX = nextPointX; currentPointY = nextPointY; } canvas.drawPath(path, mSpinnerLinePaint); } /** * 绘制XY坐标 */ private void drawXY(Canvas canvas) { int length = dpToPx(5);//刻度的长度 //绘制Y坐标 canvas.drawLine(mXOri - xyLineWidth / 2, 0, mXOri - xyLineWidth / 2, mYOri, mXYPaint); //绘制箭头 mXYPaint.setStyle(Paint.Style.STROKE); Path path = new Path(); path.moveTo(mXOri - xyLineWidth / 2 - dpToPx(5), dpToPx(12)); path.lineTo(mXOri - xyLineWidth / 2, xyLineWidth / 2); path.lineTo(mXOri - xyLineWidth / 2 + dpToPx(5), dpToPx(12)); canvas.drawPath(path, mXYPaint); //绘制刻度 int yLength = (int) (mYOri * (1 - 0.1f) / (mYData.size() - 1));//y轴上面空出10%,计算出y轴刻度间距 for (int i = 0; i < mYData.size(); i++) { //绘制刻度 canvas.drawLine(mXOri, mYOri - yLength * i + xyLineWidth / 2, mXOri + length, mYOri - yLength * i + xyLineWidth / 2, mXYPaint); mXYTextPaint.setColor(xyTextColor); //绘制文本 String text = mYData.get(i) + ""; Rect rect = getTextBounds(text, mXYTextPaint); canvas.drawText(text, 0, text.length(), mXOri - xyLineWidth - dpToPx(2) - rect.width(), mYOri - yLength * i + rect.height() / 2, mXYTextPaint); } //绘制坐标 canvas.drawLine(mXOri, mYOri + xyLineWidth / 2, width, mYOri + xyLineWidth / 2, mXYPaint); //绘制箭头 mXYPaint.setStyle(Paint.Style.STROKE); path = new Path(); //整个长度 float xLength = mXInit + interval * (mXData.size() - 1) + (width - mXOri) * 0.1f; if (xLength < width) xLength = width; path.moveTo(xLength - dpToPx(12), mYOri + xyLineWidth / 2 - dpToPx(5)); path.lineTo(xLength - xyLineWidth / 2, mYOri + xyLineWidth / 2); path.lineTo(xLength - dpToPx(12), mYOri + xyLineWidth / 2 + dpToPx(5)); canvas.drawPath(path, mXYPaint); //绘制x轴刻度 for (int i = 0; i < mXData.size(); i++) { float x = mXInit + interval * i; if (x >= mXOri) {//只绘制从原点开始的区域 mXYTextPaint.setColor(xyTextColor); canvas.drawLine(x, mYOri, x, mYOri - length, mXYPaint); //绘制X轴文本 String text = mXData.get(i); Rect rect = getTextBounds(text, mXYTextPaint); if (i == selectIndex - 1) { mXYTextPaint.setColor(lineColor); canvas.drawText(text, 0, text.length(), x - rect.width() / 2, mYOri + xyLineWidth + dpToPx(2) + rect.height(), mXYTextPaint); canvas.drawRoundRect(x - xValueRect.width() / 2 - dpToPx(3), mYOri + xyLineWidth + dpToPx(1), x + xValueRect.width() / 2 + dpToPx(3), mYOri + xyLineWidth + dpToPx(2) + xValueRect.height() + dpToPx(2), dpToPx(2), dpToPx(2), mXYTextPaint); } else { canvas.drawText(text, 0, text.length(), x - rect.width() / 2, mYOri + xyLineWidth + dpToPx(2) + rect.height(), mXYTextPaint); } } } } private float startX; private float startx; @Override public boolean onTouchEvent(MotionEvent event) { if (isScrolling) return super.onTouchEvent(event); //当该view获得点击事件,就请求父控件不拦截事件 this.getParent().requestDisallowInterceptTouchEvent(true); obtainVelocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); startx = event.getX(); Log.e("XXXX", "down:" + startX + ""); break; case MotionEvent.ACTION_MOVE: //滑动距离小于等于8的时候任务为短距离滑动 //当前x轴的尺寸与设置的x轴间隔的距离之乘积大于 屏幕中的显示布局宽度与x轴七点之差时,开始移动 if (interval * mXData.size() > width - mXOri) { //获取滑动的距离 float dis = event.getX() - startX; //重新赋值给startX startX = event.getX(); //当前x原点距离与左右滑动的距离之和没有最小值大,则将当前x距离赋值为最小,以下相似 if (mXInit + dis < minXInit) { mXInit = minXInit; } else if (mXInit + dis > maxXInit) { mXInit = maxXInit; } else { mXInit = mXInit + dis; } invalidate(); } break; case MotionEvent.ACTION_UP: isShortSlide = Math.abs(event.getX() - startx) minXInit) {//向左滑动 if (mXInit - value 0 && mXInit < maxXInit) {//向右滑动 if (mXInit + value >= maxXInit) mXInit = maxXInit; else mXInit = mXInit + value; } 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(); } /** * 获取速度 * * @return */ private float getVelocity() { if (mTracker != null) { mTracker.computeCurrentVelocity(1000); return mTracker.getXVelocity(); } return 0; } /** * 点击X轴坐标或者折线节点 * */ // 44 142 139 private void clickAction(MotionEvent event) { int dp8 = dpToPx(8); float eventX = event.getX(); float eventY = event.getY(); if (!isShortSlide) { for (int i = 0; i < mXData.size(); i++) { float x = mXInit + interval * i; float start = mXOri; if (x >= start + (mSelectMiddle - 1) * interval && x < start + mSelectMiddle * interval) { selectIndex = i + 1; invalidate(); } } return; } for (int i = 0; i < mXData.size(); i++) { //节点 float x = mXInit + interval * i; float y = mYOri - mYOri * (1 - 0.1f) * mSpinnerValue.get(mXData.get(i)) / mYData.get(mYData.size() - 1); if (eventX >= x - dp8 && eventX = y - dp8 && eventY = x - rect.width() / 2 - dp8 && eventX = y - dp8 && eventY max) { return i; } } return NULL_INDEX; } //获得在滑动结束的时候在屏幕内的点 private int middleIndex(int size) { if (size % 2 == 0) { return size / 2; } else { return size / 2 + 1; } } /** * 根据两点坐标获取中间某个点 * * @param from 坐标1 * @param to 坐标2 */ //获取已知点的斜率 y = kx+b private float getSlope(Point from, Point to) { float k = (to.y - from.y) / (to.x - from.x); Log.e("Point", "参数b:" + k); return k; } //获取参数 b private float getParams(Point from, Point to) { float b = from.y - (getSlope(from, to) * from.x); Log.e("Point", "参数b:" + b); return b; } //根据两点间的坐标获取x轴的任意一个坐标x值, private float getArbitrarilyX(Point from, Point to, int grade, int needGrade) { //获得输入的新坐标 float x = ((to.x - from.x) * needGrade) / grade + from.x; Log.e("Point", "x坐标值:" + x); return x; } //获取坐标值 private Point getPoint(Point from, Point to, int grade, int needGrade) { Point point = new Point(); point.setX(getArbitrarilyX(from, to, grade, needGrade)); float slope = getSlope(from, to); point.setY(slope * point.x + getParams(from, to)); return point; } //获取绘制折线的点 private Point getSpinnerPoint(int valueIndex) { float x = mXInit + interval * (valueIndex); float y = mYOri - mYOri * (1 - 0.1f) * mSpinnerValue.get(mXData.get(valueIndex)) / mYData.get(mYData.size() - 1); return new Point(x, y); } private class Point { float x; float y; public Point() { } public float getX() { return x; } public void setX(float x) { this.x = x; } public float getY() { return y; } public void setY(float y) { this.y = y; } public Point(float x, float y) { this.x = x; this.y = y; } @Override public String toString() { return "Point{" + "x=" + x + ", y=" + y + '}'; } } }
以上就是本文的全部内容,希望对大家的学习有所帮助。