在Android中如何绘制光滑曲线(二)
16lz
2021-01-23
上一篇主要介绍了绘制经过每个点的光滑曲线的原理,本文会重点介绍一下在Android中如何从零开始使用贝塞尔方法编写一个光滑曲线图控件。程序的设计图如下:
一、样式控制类ChartStyle
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 | /** 网格线颜色 */ private int gridColor ; /** 坐标轴分隔线宽度 */ private int axisLineWidth ; /** 横坐标文本大小 */ private float horizontalLabelTextSize ; /** 横坐标文本颜色 */ private int horizontalLabelTextColor ; /** 横坐标标题文本大小 */ private float horizontalTitleTextSize ; /** 横坐标标题文本颜色 */ private int horizontalTitleTextColor ; /** 横坐标标题文本左间距 */ private int horizontalTitlePaddingLeft ; /** 横坐标标题文本右间距 */ private int horizontalTitlePaddingRight ; /** 纵坐标文本大小 */ private float verticalLabelTextSize ; /** 纵坐标文本上下间距 */ private int verticalLabelTextPadding ; /** 纵坐标文本左右间距相对文本的比例 */ private float verticalLabelTextPaddingRate ; /** 纵坐标文本颜色 */ private int verticalLabelTextColor ; |
二、基础数据集合ChartData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | private Marker marker ; private List |
2.1、坐标轴标签Label
1 2 3 4 5 6 7 8 9 10 | /**文本对应的坐标X*/ public float x ; /**文本对应的坐标Y*/ public float y ; /** 文本对应的绘制坐标Y */ public float drawingY ; /**文本对应的实际数值*/ public int value ; /**文本*/ public String text ; |
2.2、时间序列Series
1 2 3 4 5 6 7 8 | /** 序列曲线的标题 */ private Title title ; /** 序列曲线的颜色 */ private int color ; /** 序列点集合 */ private List |
2.3、横向标题Title
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /**文本对应的坐标X*/ public float textX ; /**文本对应的坐标Y*/ public float textY ; /**文本*/ public String text ; /**圆点对应的坐标X*/ public float circleX ; /**圆点对应的坐标Y*/ public float circleY ; /**颜色*/ public int color ; /**圆点的半径*/ public int radius ; /**图形标注与文本的间距*/ public int circleTextPadding ; /**文本区域*/ public Rect textRect = new Rect ( ) ; |
2.4、数据结点Point
1 2 3 4 5 6 7 8 9 10 | /**是否在图形中绘制出此结点*/ public boolean willDrawing ; /** 在canvas中的X坐标 */ public float x ; /** 在canvas中的Y坐标 */ public float y ; /** 实际的X数值 */ public int valueX ; /** 实际的Y数值 */ public int valueY ; |
三、光滑曲线图BesselChartView
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 Paint paint ; /** 曲线的路径,用于绘制曲线 */ private Path curvePath ; /** 曲线图绘制的计算信息 */ private BesselCalculator calculator ; /** 曲线图的样式 */ private ChartStyle style ; /** 曲线图的数据 */ private ChartData data ; /** 手势解析 */ private GestureDetector detector ; /** 是否绘制全部贝塞尔结点 */ private boolean drawBesselPoint ; /** 滚动计算器 */ private Scroller scroller ; @Override protected void onDraw ( Canvas canvas ) { if ( data . getSeriesList ( ) . size ( ) == 0 ) return ; calculator . ensureTranslation ( ) ; canvas . translate ( calculator . getTranslateX ( ) , 0 ) ; drawGrid ( canvas ) ; drawCurveAndPoints ( canvas ) ; drawMarker ( canvas ) ; drawHorLabels ( canvas ) ; } |
四、核心类BesselCalculator
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /** 纵坐标文本矩形 */ public Rect verticalTextRect ; /** 横坐标文本矩形 */ public Rect horizontalTextRect ; /** 横坐标标题文本矩形 */ public Rect horizontalTitleRect ; /** 图形的高度 */ public int height ; /** 图形的宽度 */ public int width ; /** 纵轴的宽度 */ public int yAxisWidth ; /** 纵轴的高度 */ public int yAxisHeight ; /** 横轴的高度 */ public int xAxisHeight ; /** 横轴的标题的高度 */ public int xTitleHeight ; /** 横轴的长度 */ public int xAxisWidth ; /** 灰色竖线顶点 */ public Point [ ] gridPoints ; /** 画布X轴的平移,用于实现曲线图的滚动效果 */ private float translateX ; /** 用于测量文本区域长宽的画笔 */ private Paint paint ; private ChartStyle style ; private ChartData data ; /** 光滑因子 */ private float smoothness ; /** * 计算图形绘制的参数信息 * * @param width 曲线图区域的宽度 */ public void compute ( int width ) { this . width = width ; this . translateX = 0 ; computeVertcalAxisInfo ( ) ; // 计算纵轴参数 computeHorizontalAxisInfo ( ) ; // 计算横轴参数 computeTitlesInfo ( ) ; // 计算标题参数 computeSeriesCoordinate ( ) ; // 计算纵轴参数 computeBesselPoints ( ) ; // 计算贝塞尔结点 computeGridPoints ( ) ; // 计算网格顶点 } |
五、核心代码:
5.1 计算光滑曲线的贝塞尔控制点
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | /** 计算贝塞尔结点 */ private void computeBesselPoints ( ) { for ( Series series : data . getSeriesList ( ) ) { List |
5.2、坐标变换。由于手机屏幕的坐标是朝右下方的,而我们实际显示的时候是朝左上方的,所以需要进行坐标变换,代码:
1 2 | float ratio = ( point . valueY - data . getMinValueY ( ) ) / ( float ) ( data . getMaxValueY ( ) - data . getMinValueY ( ) ) ; point . y = maxCoordinateY - ( maxCoordinateY - minCoordinateY ) * ratio ; |
5.3、实现拖动
1 2 3 4 | @Override public boolean onTouchEvent ( MotionEvent event ) { return detector . onTouchEvent ( event ) ; } |
实现OnGestureListener的OnScroll方法
1 2 3 4 5 6 7 8 9 10 | @Override public boolean onScroll ( MotionEvent e1 , MotionEvent e2 , float distanceX , float distanceY ) { if ( Math . abs ( distanceX / distanceY ) > 1 ) { getParent ( ) . requestDisallowInterceptTouchEvent ( true ) ; BesselChartView . this . calculator . move ( distanceX ) ; invalidate ( ) ; return true ; } return false ; } |
在BesselChartView的onDraw方法中调用如下代码来平移画布实现拖动
1 | canvas . translate ( calculator . getTranslateX ( ) , 0 ) ; |
5.4、实现滑动
只实现拖动会让人有一种不流畅的感觉,所以还需要实现滑动,考虑到应用要支持api level 8,可以使用Scroller来实现(api level 9以后google推荐使用OverScroller来实现,OverScroller允许滚动超出边界,可以实现回弹效果), OnGestureListener的onFling和onDown方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Override public boolean onFling ( MotionEvent e1 , MotionEvent e2 , float velocityX , float velocityY ) { scroller . fling ( ( int ) BesselChartView . this . calculator . getTranslateX ( ) , 0 , ( int ) velocityX , 0 , - getWidth ( ) , 0 , 0 , 0 ) ; ViewCompat . postInvalidateOnAnimation ( BesselChartView . this ) ; return true ; } @Override public boolean onDown ( MotionEvent e ) { scroller . forceFinished ( true ) ; ViewCompat . postInvalidateOnAnimation ( BesselChartView . this ) ; return true ; } |
获取scroller计算的偏移,同时刷新UI,computeScroll()会在View的onDraw方法之前执行
Java1 2 3 4 5 6 7 | @Override public void computeScroll ( ) { if ( scroller . computeScrollOffset ( ) ) { calculator . moveTo ( scroller . getCurrX ( ) ) ; ViewCompat . postInvalidateOnAnimation ( this ) ; } } |
5.5、实现滚动动画
1 | scroller . startScroll ( 0 , 0 , - calculator . xAxisWidth / 2 , 0 , 7000 ) ; |
六、使用到的绘图相关的api
6.1 Canvas 画布
1 2 3 4 5 6 | translate ( float dx , float dy ) drawLine ( float startX , float startY , float stopX , float stopY , Paint paint ) drawBitmap ( Bitmap bitmap , Rect src , Rect dst , |
更多相关文章
- 【转载】Android绘图系列(五)——绘制文本
- Android 的坐标系及矩阵变换
- android实现文本朗读,TextToSpeech
- Android进阶之光读书笔记:View体系(一) View与 ViewGroup、View坐标
- Android开发:文本实现两端对齐
- 获取组件坐标系