在自己的开发中遇到一个画曲线的需求,就自己尝试画了一下,可能有点杂乱,欢迎提出改进建议
package zhichi.chartmaster; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import java.util.ArrayList; import java.util.List; /** * Created by Administrator on 2017/2/15. */ public class LineChartView extends CharterBase { private onSelectClick onSelectClick; public void setOnSelectClick(onSelectClick onSelectClick) { this.onSelectClick = onSelectClick; } private int xo;//圆点x坐标 private int yo;//圆点y坐标 private int topBound;//上边界的高度 private int firstX;//第一个点x坐标 private int minSlideX;//在移动时,第一个点允许最小的x坐标 private int maxSlideX;//在移动时,第一个点允许允许最大的x坐标 private int interval;//坐标间的间隔 float startX=0;//滑动时候,上一次手指的x坐标 //单位文字 private String unitText=""; public String getUnitText() { return unitText; } public void setUnitText(String unitText) { this.unitText = unitText; } //是否有阴影 private boolean IsShadow=false; //控件宽度 private int width; //控件高度 private int height; //X轴刻度数 private int XScaleNumber=8; //虚线的画笔 private Paint effectPaint; //日期的画笔 private Paint datePaint; //日期线的宽度 private float dateLineWith=dipToPx(0.6f); //日期背景的颜色 private int dateLineColor = Color.parseColor("#f2f2f2"); //折线颜色 private int baseLineColor; //折线路径 private Path baseLinePath; //折线画笔 private Paint baseLinePaint; //折线的宽度 private float baseLineWith = dipToPx(0.8f); //折线下的阴影的画笔 private Paint baseShadow; //圆圈点的画笔 private Paint circlePaint; //折线的弯曲率 private float smoothness=0.35f; //坐标点的集合 private List<PointF> points=new ArrayList<>(); //选中的坐标点 private int selectPoint = -1; //文字的画笔 private Paint textPaint; //文字的大小 private float textSize=dipToPx(12); //文字的颜色 private int textColor= Color.parseColor("#666666"); //背景颜色 private int bg=Color.parseColor("#fa6049"); public int getSelectPoint() { return selectPoint; } public void setSelectPoint(int selectPoint) { this.selectPoint = selectPoint; invalidate(); } public int getXScaleNumber() { return XScaleNumber; } public void setXScaleNumber(int XScaleNumber) { this.XScaleNumber = XScaleNumber; } public boolean isShadow() { return IsShadow; } public void setShadow(boolean shadow) { IsShadow = shadow; } public LineChartView(Context context) { super(context); init(context); } public LineChartView(Context context, AttributeSet attrs) { super(context, attrs); initAttr(context,attrs); init(context); } public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initAttr(context,attrs); init(context); } private void initAttr(Context context, AttributeSet attrs) { } //初始化各类参数 private void init(Context context) { setBackgroundColor(bg); datePaint=new Paint(); datePaint.setAntiAlias(true); datePaint.setStyle(Paint.Style.FILL); datePaint.setColor(dateLineColor); datePaint.setStrokeWidth(dateLineWith); datePaint.setStrokeCap(Paint.Cap.ROUND); effectPaint=new Paint(); effectPaint.setAntiAlias(true); effectPaint.setStyle(Paint.Style.STROKE); effectPaint.setColor(dateLineColor); effectPaint.setStrokeWidth(dateLineWith); effectPaint.setStrokeCap(Paint.Cap.ROUND); baseLineColor=Color.WHITE; baseLinePath = new Path(); baseLinePaint = new Paint(); baseLinePaint.setAntiAlias(true); baseLinePaint.setStyle(Paint.Style.STROKE); baseLinePaint.setStrokeWidth(baseLineWith); baseLinePaint.setColor(baseLineColor); baseLinePaint.setStrokeCap(Paint.Cap.ROUND); baseShadow = new Paint(); baseShadow.setAntiAlias(true); baseShadow.setColor((baseLineColor & 0x40FFFFFF) | 0x10000000); baseShadow.setStyle(Paint.Style.FILL); circlePaint = new Paint(); circlePaint.setAntiAlias(true); circlePaint.setStyle(Paint.Style.FILL); circlePaint.setStrokeWidth(baseLineWith); circlePaint.setColor(baseLineColor); circlePaint.setStrokeCap(Paint.Cap.ROUND); textPaint=new Paint(); textPaint.setAntiAlias(true); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setTextSize(textSize); textPaint.setColor(textColor); textPaint.setStyle(Paint.Style.FILL); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width=w; height=h; xo=0; yo=height-dipToPx(30); topBound=dipToPx(12); if(values!=null){ setParam(values); } } private void setParam(ArrayList<ChartEntity> values){ if(values.size()<XScaleNumber){ interval=width/(values.size()+1); firstX=interval; }else{ interval=width/XScaleNumber; firstX=width-values.size()*interval; } minSlideX=width-values.size()*interval; maxSlideX=interval; selectPoint=values.size()-1; } public void setDataChart(ArrayList<ChartEntity> values){ setValues(values); setParam(values); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawShell(canvas); drawEffect(canvas); getCoordinatePoint(canvas); if (!animFinished && !animator.isRunning()&&values!=null&&values.size()>0) { playAnimation(); } drawEffectText(canvas); } //画日期背景 private void drawShell(Canvas canvas) { datePaint.setColor(dateLineColor); canvas.drawRect(0,yo,width,height,datePaint); } //画虚线 private void drawEffect(Canvas canvas) { effectPaint.setPathEffect(new DashPathEffect(new float[]{8,10,8,10},0)); float interval=(yo-topBound)/4; for(int i=0;i<4;i++){ Path path = new Path(); path.moveTo(0, topBound+(i*interval)); path.lineTo(width, topBound+(i*interval)); canvas.drawPath(path,effectPaint); } } //文字 private void drawEffectText(Canvas canvas) { textPaint.setTextSize(dipToPx(12)); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(dateLineColor); float interval=(yo-topBound)/4; for(int i=0;i<4;i++){ java.text.DecimalFormat df = new java.text.DecimalFormat("#0.00"); canvas.drawText(df.format((maxY-i*((maxY-minY)/4))),interval*0.8f,topBound+(i*interval)+dipToPx(15),textPaint); } } //得到动态的坐标点 private void getCoordinatePoint(Canvas canvas){ if (values == null || values.size() == 0) { return; } final int valuesLength = valuesTransition.length; final float dY = maxY - minY > 0 ? maxY - minY : 1; // calculate point coordinates points = new ArrayList<>(valuesLength); for (int i = 0; i < valuesLength; i++) { float x = i*interval+firstX; float y = topBound + ((yo-topBound) - (valuesTransition[i] - minY) * (yo-topBound) / dY); Log.e("GXL_Chart","yo="+yo+"-----valuesTransition["+i+"]="+valuesTransition[i]+"-----minY="+minY+"-----dY="+dY+"-----y="+y); points.add(new PointF(x, y)); } drawBendLine(canvas,points); drawWeightPoint(canvas,points); drawText(canvas,points); } //绘制X坐标轴文本 private void drawText(Canvas canvas, List<PointF> Points) { if (Points.size() == 0) { return; } for (int i = 0; i < Points.size(); i++) { if (i == selectPoint) { //绘制日期 textPaint.setTextSize(dipToPx(12)); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(textColor); canvas.drawText(valuesText[i], Points.get(i).x, yo + (height - yo) / 2 + dipToPx(12) / 3, textPaint); //绘制提示文字 textPaint.setTextSize(dipToPx(11)); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(textColor); String hintText = String.valueOf(valuesData[i]) + unitText; Rect rect = new Rect(); textPaint.getTextBounds(hintText, 0, hintText.length() - 1, rect); float textHeight = rect.height(); float textWidth = rect.width(); float textX=Points.get(i).x; float textY=Points.get(i).y-textHeight; datePaint.setColor(Color.WHITE); RectF textBg=new RectF(); textBg.left=textX-textWidth*0.8f; textBg.top=textY-textHeight*0.8f-textHeight/2; textBg.right=textX+textWidth*0.8f; textBg.bottom=textY+textHeight*0.8f-textHeight/2; canvas.drawRoundRect(textBg,dipToPx(2),dipToPx(2),datePaint); textPaint.setColor(Color.parseColor("#F75044")); canvas.drawText(hintText, textX, textY, textPaint); } else { textPaint.setTextSize(dipToPx(12)); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(textColor); canvas.drawText(valuesText[i], Points.get(i).x, yo + (height - yo) / 2 + dipToPx(12) / 3, textPaint); } } } //画弯曲的折线 private void drawBendLine(Canvas canvas,List<PointF> Points){ baseLinePath.reset(); if(Points.size() == 0) { return; } List<PointF> NewPoints=new ArrayList<>(); NewPoints.addAll(Points); // PointF head=new PointF(); // head.x=firstX; // head.y=yo; // NewPoints.add(0,head); float lX = 0; float lY = 0; baseLinePath.moveTo(NewPoints.get(0).x, NewPoints.get(0).y); for (int i = 1; i < NewPoints.size(); i++) { PointF p = NewPoints.get(i); PointF firstPointF = NewPoints.get(i - 1); float x1 = firstPointF.x + lX; float y1 = firstPointF.y + lY; PointF secondPointF = NewPoints.get(i + 1 < NewPoints.size() ? i + 1 : i); lX = (secondPointF.x - firstPointF.x) / 2 * smoothness; lY = (secondPointF.y - firstPointF.y) / 2 * smoothness; float x2 = p.x - lX; float y2 = p.y - lY; if (y1 == p.y) { y2 = y1; } baseLinePath.cubicTo(x1, y1, x2, y2, p.x, p.y); } baseLinePaint.setStyle(Paint.Style.STROKE); baseLinePaint.setColor(baseLineColor); canvas.drawPath(baseLinePath, baseLinePaint); //绘制线下面的阴影 if(IsShadow){ drawArea(canvas,points); } } //绘制线下面的阴影 private void drawArea(Canvas canvas,List<PointF> Points){ // fill area LinearGradient mShader = new LinearGradient(0,0,0,getMeasuredHeight(),new int[] {Color.WHITE,Color.parseColor("#f85646")},new float[]{0.5f,0.85f},Shader.TileMode.REPEAT); baseShadow.setShader(mShader); if (Points.size() > 0) { baseLinePath.lineTo(points.get(Points.size() - 1).x , yo); baseLinePath.lineTo(points.get(0).x, yo); baseLinePath.close(); canvas.drawPath(baseLinePath, baseShadow); } } //绘制折线穿过的点 protected void drawWeightPoint(Canvas canvas,List<PointF> Points) { if(Points.size()== 0) { return; } for(int i = 0; i < Points.size(); i++) { if(i==selectPoint){ circlePaint.setColor(Color.WHITE); canvas.drawCircle(Points.get(i).x, Points.get(i).y, dipToPx(3.5f), circlePaint); }else { circlePaint.setColor(Color.WHITE); canvas.drawCircle(Points.get(i).x, Points.get(i).y, dipToPx(3f), circlePaint); circlePaint.setColor(Color.parseColor("#F75044")); canvas.drawCircle(Points.get(i).x, Points.get(i).y, dipToPx(2f), circlePaint); } } } private int mLastX; private int mLastY; @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 要求父控件拦截事件 this.getParent().requestDisallowInterceptTouchEvent(false); startX=event.getX(); break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; int deltaY = y - mLastY; // 如果是左右滑动 if(Math.abs(deltaX) > Math.abs(deltaY)){ // 要求父控件不拦截事件 getParent().requestDisallowInterceptTouchEvent(true); } //如果不用滑动就可以展示所有数据,就不让滑动 if(values.size()<XScaleNumber){ return false; } float dis=event.getX()-startX; startX=event.getX(); if(firstX+dis>maxSlideX){ firstX=maxSlideX; Log.e("GXL_firstX","maxSlideX="+firstX); }else if(firstX+dis<minSlideX){ firstX=minSlideX; Log.e("GXL_firstX","minSlideX="+firstX); }else{ firstX=(int) (firstX+dis); Log.e("GXL_firstX","firstX+dis="+firstX+"-------dis="+dis); } invalidate(); break; case MotionEvent.ACTION_UP: onActionUpEvent(event); this.getParent().requestDisallowInterceptTouchEvent(false); break; case MotionEvent.ACTION_CANCEL: this.getParent().requestDisallowInterceptTouchEvent(false); break; } mLastX = x; mLastY = y; return true; } private void onActionUpEvent(MotionEvent event) { boolean isValidTouch = validateTouch(event.getX(), event.getY()); Log.e("GXL_Event","isValidTouch="+isValidTouch); if(isValidTouch) { invalidate(); Log.e("GXL_Event","selectPoint="+selectPoint); if(onSelectClick!=null){ onSelectClick.onSelect(selectPoint); } } } //是否是有效的触摸范围 private boolean validateTouch(float x, float y) { //曲线触摸区域 for(int i = 0; i < points.size(); i++) { // dipToPx(8)乘以2为了适当增大触摸面积 if(x > (points.get(i).x - dipToPx(8) * 2) && x < (points.get(i).x + dipToPx(8) * 2)) { if(y > (points.get(i).y - dipToPx(8) * 2) && y < (points.get(i).y + dipToPx(8) * 2)) { selectPoint = i; return true; } } } return false; } /** * dip 转换成px * @param dip * @return */ private int dipToPx(float dip) { float density = getContext().getResources().getDisplayMetrics().density; return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1)); } public interface onSelectClick{ void onSelect(int select); } }
最后附上demo链接