接著上篇,今天介紹一下曲線圖 / 折線圖的實現方法可训,先上效果圖:
image.png
曲線圖很簡單了昌妹,坐標軸跟刻度線跟上篇柱狀圖的繪制一樣一樣滴,繪制曲線圖握截,關鍵就是確定好Y軸的每個點飞崖,然后用繪制曲線的方法,把點連起來就OK了谨胞。
(1)確定數據在坐標軸上對應的每個點
/**
* 根據傳入的數據固歪,確定繪制的點
*
* @return
*/
private Point[] initPoint() {
Point[] points = new Point[mDatas.size()];
for (int i = 0; i < mDatas.size(); i++) {
Integer ybean = mDatas.get(i);
int drawHeight = (int) (startY * 1.0 - (ybean * yAxisSpace * 1.0 / yIncreaseValue));
int startx = startX + xAxisSpace * i;
points[i] = new Point(startx, drawHeight);
}
Log.e("TAG", "startX=" + startX + "---startY=" + startY);
return points;
}
(2)將點連接起來,這里使用cubicTo繪制貝塞爾曲線胯努。
image.png
/**
* 繪制曲線-曲線圖
*
* @param canvas
*/
private void drawScrollLine(Canvas canvas) {
Point startp;
Point endp;
for (int i = 0; i < mPoints.length - 1; i++) {
startp = mPoints[i];
endp = mPoints[i + 1];
int wt = (startp.x + endp.x) / 2;
Point p3 = new Point();
Point p4 = new Point();
p3.y = startp.y;
p3.x = wt;
p4.y = endp.y;
p4.x = wt;
Path path = new Path();
path.moveTo(startp.x, startp.y);
path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y);
canvas.drawPath(path, mPaint);
}
}
/**
* 繪制直線-折線圖
*
* @param canvas
*/
private void drawLine(Canvas canvas) {
Point startp;
Point endp;
for (int i = 0; i < mPoints.length - 1; i++) {
startp = mPoints[i];
endp = mPoints[i + 1];
canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mPaint);
}
}
完整代碼:
package com.example.jojo.learn.customview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.example.jojo.learn.R;
import com.example.jojo.learn.utils.DP2PX;
import java.util.ArrayList;
import java.util.List;
/**
* Created by JoJo on 2018/8/3.
* wechat:18510829974
* description: 曲線圖/折線圖
*/
public class LineChartView extends View {
private Context mContext;
//繪制坐標軸的畫筆
private Paint mAxisPaint;
//繪制曲線的畫筆
private Paint mPaint;
//繪制X軸上方的畫筆
private Paint mXAxisLinePaint;
private Paint mPaintText;
//向上的曲線圖的繪制起點(px)
private int startX;
private int startY;
//向下的曲線圖的繪制起點(px)
private int downStartX;
private int downStartY;
//上方Y軸每單位刻度所占的像素值
private float YAxisUpUnitValue;
//下方Y軸每單位刻度所占的像素值
private float YAxisDownUnitValue;
//根據具體傳入的數據牢裳,在坐標軸上繪制點
private Point[] mPoints;
//傳入的數據,決定繪制的縱坐標值
private List<Integer> mDatas = new ArrayList<>();
//Y軸刻度集合
private List<Integer> mYAxisList = new ArrayList<>();
//X軸刻度集合
private List<String> mXAxisList = new ArrayList<>();
//X軸的繪制距離
private int mXAxisMaxValue;
//Y軸的繪制距離
private int mYAxisMaxValue;
//Y軸刻度間距(px)
private int yAxisSpace = 120;
//X軸刻度間距(px)
private int xAxisSpace = 200;
//Y軸刻度線寬度
private int mKeduWidth = 20;
private float keduTextSize = 20;
//刻度值距離坐標的padding距離
private int textPadinng = 10;
//Y軸遞增的實際值
private int yIncreaseValue;
//true:繪制曲線 false:折線
private boolean isCurve = true;
private Rect mYMaxTextRect;
private Rect mXMaxTextRect;
private int mMaxTextHeight;
private int mMaxTextWidth;
public LineChartView(Context context) {
this(context, null);
}
public LineChartView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initData();
initView();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = (mYAxisList.size() - 1) * yAxisSpace + mMaxTextHeight * 2 + textPadinng * 2;
}
if (widthMode == MeasureSpec.AT_MOST) {
widthSize = startX + (mDatas.size() - 1) * xAxisSpace + mMaxTextWidth;
}
//保存測量結果
setMeasuredDimension(widthSize, heightSize);
}
private void initView() {
//初始化畫筆
mPaint = new Paint();
mPaint.setColor(ContextCompat.getColor(mContext, R.color.color_efaf34));
mPaint.setStrokeWidth(2);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
//繪制X,Y軸坐標的畫筆
mAxisPaint = new Paint();
mAxisPaint.setColor(ContextCompat.getColor(mContext, R.color.color_274782));
mAxisPaint.setStrokeWidth(2);
mAxisPaint.setAntiAlias(true);
mAxisPaint.setStyle(Paint.Style.STROKE);
//繪制坐標軸上方的橫線的畫筆
mXAxisLinePaint = new Paint();
mXAxisLinePaint.setColor(ContextCompat.getColor(mContext, R.color.color_274782));
mXAxisLinePaint.setStrokeWidth(1);
mXAxisLinePaint.setAntiAlias(true);
mXAxisLinePaint.setStyle(Paint.Style.STROKE);
//繪制刻度值文字的畫筆
mPaintText = new Paint();
mPaintText.setTextSize(keduTextSize);
mPaintText.setColor(ContextCompat.getColor(mContext, R.color.color_a9c6d6));
mPaintText.setAntiAlias(true);
mPaintText.setStrokeWidth(1);
mYMaxTextRect = new Rect();
mXMaxTextRect = new Rect();
mPaintText.getTextBounds(Integer.toString(mYAxisList.get(mYAxisList.size() - 1)), 0, Integer.toString(mYAxisList.get(mYAxisList.size() - 1)).length(), mYMaxTextRect);
mPaintText.getTextBounds(mXAxisList.get(mXAxisList.size() - 1), 0, mXAxisList.get(mXAxisList.size() - 1).length(), mXMaxTextRect);
//繪制的刻度文字的最大值所占的寬高
mMaxTextWidth = mYMaxTextRect.width() > mXMaxTextRect.width() ? mYMaxTextRect.width() : mXMaxTextRect.width();
mMaxTextHeight = mYMaxTextRect.height() > mXMaxTextRect.height() ? mYMaxTextRect.height() : mXMaxTextRect.height();
//指定繪制的起始位置
startX = mMaxTextWidth + textPadinng + mKeduWidth;
//坐標原點Y的位置(+1的原因:X軸畫筆的寬度為2 ; +DP2PX.dip2px(mContext, 5)原因:為刻度文字所占的超出的高度 )——>解決曲線畫到最大刻度值時叶沛,顯示高度不夠蒲讯,曲線顯示扁扁的問題
startY = yAxisSpace * (mYAxisList.size() - 1) + mMaxTextHeight;
if (mYAxisList.size() >= 2) {
yIncreaseValue = mYAxisList.get(1) - mYAxisList.get(0);
}
//X軸繪制距離
mXAxisMaxValue = (mDatas.size() - 1) * xAxisSpace;
//Y軸繪制距離
mYAxisMaxValue = (mYAxisList.size() - 1) * yAxisSpace;
//坐標起始點Y軸高度=(startY+mKeduWidth) 下方文字所占高度= DP2PX.dip2px(mContext, keduTextSize)
int viewHeight = startY + 2 * mKeduWidth + DP2PX.dip2px(mContext, keduTextSize);
//viewHeight=121
Log.e("TAG", "viewHeight=" + viewHeight);
}
/**
* 根據傳入的數據,確定繪制的點
*
* @return
*/
private Point[] initPoint() {
Point[] points = new Point[mDatas.size()];
for (int i = 0; i < mDatas.size(); i++) {
Integer ybean = mDatas.get(i);
int drawHeight = (int) (startY * 1.0 - (ybean * yAxisSpace * 1.0 / yIncreaseValue));
int startx = startX + xAxisSpace * i;
points[i] = new Point(startx, drawHeight);
}
Log.e("TAG", "startX=" + startX + "---startY=" + startY);
return points;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPoints = initPoint();
for (int i = 0; i < mYAxisList.size(); i++) {
//Y軸方向遞增的高度
int yAxisHeight = startY - yAxisSpace * i;
//繪制X軸和上方橫線
canvas.drawLine(startX - mKeduWidth, yAxisHeight, startX + (mDatas.size() - 1) * xAxisSpace, yAxisHeight, mXAxisLinePaint);
//繪制左邊Y軸刻度線
// canvas.drawLine(startX, yAxisHeight, startX - mKeduWidth, yAxisHeight, mAxisPaint);
//繪制文字時,Y軸方向遞增的高度
int yTextHeight = startY - yAxisSpace * i;
//繪制Y軸刻度旁邊的刻度文字值,10為刻度線與文字的間距
mPaintText.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(mYAxisList.get(i) + "", (startX - mKeduWidth) - textPadinng, yTextHeight, mPaintText);
}
//繪制Y軸
canvas.drawLine(startX, startY, startX, startY - mYAxisMaxValue, mAxisPaint);
//繪制X軸下面顯示的文字
for (int i = 0; i < mXAxisList.size(); i++) {
int xTextWidth = startX + xAxisSpace * i - mKeduWidth;
//設置從起點位置的左邊對齊繪制文字
mPaintText.setTextAlign(Paint.Align.LEFT);
Rect rect = new Rect();
mPaintText.getTextBounds(mXAxisList.get(i), 0, mXAxisList.get(i).length(), rect);
canvas.drawText(mXAxisList.get(i), startX - rect.width() / 2 + xAxisSpace * i, startY + rect.height() + textPadinng, mPaintText);
}
//連接所有的數據點,畫曲線
if (isCurve) {
//畫曲線
drawScrollLine(canvas);
} else {
//畫折線
drawLine(canvas);
}
}
/**
* 繪制曲線-曲線圖
*
* @param canvas
*/
private void drawScrollLine(Canvas canvas) {
Point startp;
Point endp;
for (int i = 0; i < mPoints.length - 1; i++) {
startp = mPoints[i];
endp = mPoints[i + 1];
int wt = (startp.x + endp.x) / 2;
Point p3 = new Point();
Point p4 = new Point();
p3.y = startp.y;
p3.x = wt;
p4.y = endp.y;
p4.x = wt;
Path path = new Path();
path.moveTo(startp.x, startp.y);
path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y);
canvas.drawPath(path, mPaint);
}
}
/**
* 繪制直線-折線圖
*
* @param canvas
*/
private void drawLine(Canvas canvas) {
Point startp;
Point endp;
for (int i = 0; i < mPoints.length - 1; i++) {
startp = mPoints[i];
endp = mPoints[i + 1];
canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mPaint);
}
}
private void initData() {
//外界傳入的數據灰署,即為繪制曲線的每個點
mDatas.add(0);
mDatas.add(10);
mDatas.add(5);
mDatas.add(20);
mDatas.add(15);
int[] mYAxisData = new int[]{0, 10, 20, 30, 40};
for (int i = 0; i < mYAxisData.length; i++) {
mYAxisList.add(mYAxisData[i]);
}
//X軸數據
mXAxisList.add("01月");
mXAxisList.add("02月");
mXAxisList.add("03月");
mXAxisList.add("04月");
mXAxisList.add("05月");
}
/**
* 傳入數據判帮,重新繪制圖表
*
* @param datas
* @param yAxisData
*/
public void updateData(List<Integer> datas, List<String> xAxisData, List<Integer> yAxisData) {
this.mDatas = datas;
this.mXAxisList = xAxisData;
this.mYAxisList = yAxisData;
initView();
postInvalidate();
}
}