首先看下要实现的效果图。 折线图的绘制主要有一下几个步骤。 一、定义LineChartView类并继承View。 二、添加自定义属性。以在value目录下创建attrs.xml文件,文件中我们可以定义一些用到的属性,比如折线颜色、字体大小等属性。文件内容如下:
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="utf-8"?> <resources > <declare-styleable name ="LineChartView" > <attr name ="axesColor" format ="color" /> <attr name ="axesWidth" format ="dimension" /> <attr name ="textColor" format ="color" /> <attr name ="textSize" format ="dimension" /> <attr name ="lineColor" format ="color" /> </declare-styleable > </resources >
接下来在LineChartView的构造方法中解析自定义属性的值并做相应的处理。在构造方法里还初始化了渐变颜色、折线数据的List集合以及初始化画笔等操作代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public LineChartView(Context context , AttributeSet attrs , int defStyleAttr ) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs , R.styleable .LineChartView) ; mAxesColor = typedArray.getColor(R.styleable .LineChartView_axesColor, Color.parseColor ("#CCCCCC" ) ); mAxesWidth = typedArray.getDimension(R.styleable .LineChartView_axesWidth, 1) ; mTextColor = typedArray.getColor(R.styleable .LineChartView_textColor, Color.parseColor ("#ABABAB" ) ); mTextSize = typedArray.getDimension(R.styleable .LineChartView_textSize, 32) ; mLineColor = typedArray.getColor(R.styleable .LineChartView_lineColor, Color.RED) ; typedArray.recycle() ; shadeColors = new int [] { Color . argb(100 , 255 , 86 , 86 ), Color . argb(15 , 255 , 86 , 86 ), Color . argb(0 , 255 , 86 , 86 )}; mValues = new ArrayList<>() ; mMargin10 = DensityUtils . dp2px(context, 10 ); init() ; }
三、初始化画笔和路径。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private void init() { mPaintAxes = new Paint() ; mPaintAxes.setColor(mAxesColor ) ; mPaintAxes.setStrokeWidth(mAxesWidth ) ; mPaintText = new Paint() ; mPaintText.setStyle(Paint.Style.FILL) ; mPaintText.setAntiAlias(true ) ; mPaintText.setTextSize(mTextSize ) ; mPaintText.setColor(mTextColor ) ; mPaintText.setTextAlign(Paint.Align.LEFT) ; mPaintLine = new Paint() ; mPaintLine.setStyle(Paint.Style.STROKE) ; mPaintLine.setAntiAlias(true ) ; mPaintLine.setStrokeWidth(mAxesWidth / 2) ; mPaintLine.setColor(mLineColor ) ; mPath = new Path() ; mPathShader = new Path() ; mPaintShader = new Paint() ; mPaintShader.setAntiAlias(true ) ; mPaintShader.setStrokeWidth(2f) ; }
四、重写onLayout方法。在onLayout方法中获取控件的宽高、初始化原点坐标以及设置控件的背景。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override protected void onLayout(boolean changed , int left , int top , int right , int bottom ) { super.onLayout(changed , left , top , right , bottom ) ; if (changed) { mWidth = getWidth() ; mHeight = getHeight() ; timeWidth = (int ) mPaintText.measureText(startTime ) ; xOrigin = 0 + mMargin10; yOrigin = (mHeight - mTextSize - mMargin10); } }
五、重写onDraw方法。在onDraw方法中完成折线图的绘制。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override protected void onDraw(Canvas canvas ) { super.onDraw(canvas ) ; yInterval = (max - min) / (yOrigin - mMargin10); xInterval = (mWidth - xOrigin) / (mItems.size() - 1 ); drawAxes(canvas ) ; drawText(canvas ) ; drawLine(canvas ) ; drawPath(canvas ) ; }
折线图的绘制可以分三部分:1.绘制坐标轴。2.绘制View上的文字。3.绘制折线。
1.坐标轴绘制的是第一象限,即左下角的点为原点。绘制坐标轴代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 private void drawAxes(Canvas canvas ) { canvas.drawLine(xOrigin , yOrigin , mWidth - mMargin10 , yOrigin , mPaintAxes ) ; canvas.drawLine(xOrigin , yOrigin / 2, mWidth - mMargin10 , yOrigin / 2, mPaintAxes ) ; canvas.drawLine(xOrigin , mMargin10 , mWidth - mMargin10 , mMargin10 , mPaintAxes ) ; canvas.drawLine(xOrigin , yOrigin , xOrigin , mMargin10 , mPaintAxes ) ; canvas.drawLine(mWidth - mMargin10 , mMargin10 , mWidth - mMargin10 , yOrigin , mPaintAxes ) ; }
2.绘制文字,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 private void drawText(Canvas canvas ) { canvas.drawText(String.format ("%.2f" , max * 100 / 100.0) + "%" , xOrigin + 6 , 2 * mMargin10, mPaintText); canvas.drawText(String.format ("%.2f" , min * 100 / 100.0) + "%" , xOrigin + 6 , yOrigin - 6 , mPaintText); canvas.drawText((String.format ("%.2f" , (min + max ) * 100 / 200.0 ) + "%" ), xOrigin + 6 , (yOrigin + mMargin10) / 2 , mPaintText); canvas.drawText(startTime , xOrigin , mHeight - mMargin10 , mPaintText ) ; canvas.drawText(endTime , mWidth - timeWidth - mMargin10 , mHeight - mMargin10 , mPaintText ) ; }
3.绘制折线及渐变填充
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void drawLine(Canvas canvas ) { for (int i = 0 ; i < mValues.size() ; i++) { float x = i * xInterval + xOrigin + mAxesWidth; if (i == 0 ) { mPathShader.moveTo(x , yOrigin - (mValues .get (i ) - min) / yInterval); mPath.moveTo(x , yOrigin - (mValues .get (i ) - min) / yInterval); } else { mPath.lineTo(x - mMargin10 - mAxesWidth , yOrigin - (mValues .get (i ) - min) / yInterval); mPathShader.lineTo(x - mMargin10 - mAxesWidth , yOrigin - (mValues .get (i ) - min) / yInterval); if (i == mValues.size() - 1 ) { mPathShader.lineTo(x - mMargin10 - mAxesWidth , yOrigin ) ; mPathShader.lineTo(xOrigin , yOrigin ) ; mPathShader.close() ; } } } if (null == mShadeColors) { mPaintShader.setColor(Color.argb (0, 0, 0, 0) ); } else { Shader mShader = new LinearGradient(0, 0, 0, getHeight () , mShadeColors, null, Shader.TileMode.CLAMP); mPaintShader.setShader(mShader ) ; } canvas.drawPath(mPathShader , mPaintShader ) ; }
六、折线图添加动画。
1.折线图的动画使用属性动画,首先需要计算动画的进度,因此需要先添加setPercentage方法,当动画开始时,我们可以在该方法中拿到percentage的值
1 2 3 4 5 6 7 8 9 10 11 12 13 public void setPercentage (float percentage) { if (percentage < 0.0f || percentage > 1.0f ) { throw new IllegalArgumentException( "setPercentage not between 0.0f and 1.0f" ); } mProgress = percentage; invalidate(); }
2.通过Path来绘制折线路径,代码如下:
1 2 3 4 5 6 7 8 9 private void drawPath (Canvas canvas) { PathMeasure measure = new PathMeasure(mPath, false ); float pathLength = measure.getLength(); PathEffect effect = new DashPathEffect(new float []{pathLength, pathLength}, pathLength - pathLength * mProgress); mPaintLine.setPathEffect(effect); canvas.drawPath(mPath, mPaintLine); }
3.通过ObjectAnimator 开启动画,注意ObjectAnimator.ofFloat(lineChartView, “percentage”, 0.0f, 1.0f)中第二个参数必须为“percentage”,对应前那边的setPercentage方法,属性动画会根据“percentage”参数通过反射调用setPercentage:
1 2 3 4 5 6 7 8 9 10 public void startAnim(LineChartView lineChartView , long duration ) { ObjectAnimator anim = ObjectAnimator .of Float(lineChartView , "percentage" , 0.0f, 1.0f) ; anim.setDuration(duration ) ; anim.setInterpolator(new LinearInterpolator() ); anim.start() ; }
至此,折线图的绘制已经全部完成。最后还可以添加get() set()方法,暴露出属性接口,以供外部调用。代码就不再贴出来了。
七、使用LineChartView 1.在布局文件中添加LineChartView,可设置折线颜色、字体颜色、等属性,如下:
1 2 3 4 5 6 7 8 9 10 11 <com.example.zhpan.linechartview.LineChartView android:id ="@+id/lcv" android:layout_width ="match_parent" android:layout_height ="200dp" android:layout_marginBottom ="20dp" android:layout_marginLeft ="15dp" android:layout_marginRight ="15dp" android:layout_marginTop ="15dp" app:lineColor ="#FF0000" app:textColor ="#ABABAB" app:textSize ="12dp" />
2.在Activity中为LineChartView设置数据,也可以通过代码为其设置属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private void initData() { List<Float> listValues = new ArrayList<>(); Random random = new Random(); float startValue = random.nextFloat() * 10 ; listValues.add(startValue); for (int i = 0 ; i < 30 ; i++) { startValue += random.nextFloat() * 10 - 5 ; listValues.add(startValue); } List<Integer> listShadeColors = new ArrayList<>(); listShadeColors.add(Color.argb(100 , 255 , 86 , 86 )); listShadeColors.add(Color.argb(15 , 255 , 86 , 86 )); listShadeColors.add(Color.argb(0 , 255 , 86 , 86 )); mLineChartView.setValues(listValues); mLineChartView.setShadeColors(listShadeColors); mLineChartView.setInterpolator(new DecelerateInterpolator()); mLineChartView.setAxisMinValue(-30 ); mLineChartView.setAxisMaxValue(30 ); mLineChartView.setStartTime("2017-03-15" ); mLineChartView.setEndTime("2017-04-14" ); mLineChartView.startAnim(2500 ); }
源码下载