Android 进阶自定义View(4)图表统计LineChartView曲线图的实现
接着上篇,今天介绍一下曲线图 / 折线图的实现方法,先上效果图:
曲线图很简单了,坐标轴跟刻度线跟上篇柱状图的绘制一样一样滴,绘制曲线图,关键就是确定好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绘制贝塞尔曲线。
/**
* 绘制曲线-曲线图
*
* @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();
}
}

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
手把手教你使用Android studio生成正式签名的APK文件
首先,成功编译和运行程序: 项目code 运行效果图 点击Android studio 右侧工具栏的Gradle → 项目名 → :app → Tasks →android, 双击signingReport: 结果如图所示: 也就是说,我们所有通过Android studio来运行的程序都是使用了这个debug.keystore文件来进行签名的: 不过这仅仅适用于开发阶段而已,现在如果是要发布app,便需使用一个正式的keystore文件来进行签名才行。 使用Android studio生成正式签名的APK文件: 点击Android studio导航栏上的Build → Generate Signed APK,首次点击可能会提示我们输入操作系统的密码, 输入密码后点击OK,随后弹出如下图所示的创建签名APK对话框: 由于我们还没有一个正式的keystore文件,所以应该点击Create new 按钮,然后会弹出一个新的对话框来让我们填写创建keystore文件所必要的信息。根据自己的实际情况进行填写就行了: 其中: 1.第一行这里,点击这个按钮之后: 会弹出一个框,这里需要选择好路径,...
-
下一篇
安卓日常开发记录-键盘的相关处理方式
点击EditText之外隐藏键盘的实现方式 重写事件分发dispatchTouchEvent,注意不要在onTouchEvent中操作,因为onTouchEvent并非任何情况下都会被调用。通过计算EditText在布局中的位置,进行键盘的显示和隐藏处理 /** * 点击区域在输入框之外都隐藏掉键盘 * @param ev * @return */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { View v = getCurrentFocus(); if (isShouldHideInput(v, ev)) { hideSoftKeyboard(v); } return super.dispatchTouchEvent(ev); } // 必不可少,否则所有的组件都不会有TouchEvent了 if (getWindow().superDispatchTouchEvent(ev)) { return true; ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- 面试大杂烩
- MySQL数据库在高并发下的优化方案
- CentOS8编译安装MySQL8.0.19
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS关闭SELinux安全模块
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果