Android自定义控件实现折线图
作者:王世晖 发布时间:2023-02-09 18:33:55
标签:Android,折线图
本文实例实现一个如下图所示的Android折线图,供大家参考,具体内容如下
首先是控件绘图区域的划分,控件左边取一小部分(控件总宽度的八分之一)绘制表头,右边剩余的部分绘制表格
确定表格的行列数,首先绘制一个三行八列的网格,设置好行列的坐标后开始绘制
/*绘制三条横线*/
for(int i=0;i<3;i++){
canvas.drawLine(textWide, mLineYs[i], totalWidth, mLineYs[i], mPaintLine);
}
/*绘制八条竖线*/
for(int i=0;i<8;i++){
canvas.drawLine(mLineXs[i], 0, mLineXs[i], totalHeight, mPaintLine);
}
网格绘制完成后,开始绘制折线图
根据输入的节点数据,分别绘制两条折线
通过canvas的drawLine方法依次连接两点即可
在每个数据节点处绘制一个小圆,突出显示
/*绘制第一条折线的路径*/
for (int i = 0; i < mPerformance_1.length - 1; i++) {
/*折线图的折线的画笔设置粗一点*/
mPaintLine.setStrokeWidth(5);
/*计算当前节点的坐标值*/
float prePointX =mLineXs[i];
float prePointY =mLineYs[2] - (mLineYs[2] - mLineYs[mPerformance_1[i].type]) * animCurrentValue;
/*计算下一个节点的坐标值*/
float nextPointX=mLineXs[i + 1];
float nextPointY=mLineYs[2] - (mLineYs[2] - mLineYs[mPerformance_1[i + 1].type]) * animCurrentValue;
/*连接当前坐标和下一个坐标,绘制线段*/
canvas.drawLine(prePointX, prePointY, nextPointX, nextPointY, mPaintLine1);
/*当前节点坐标处绘制小圆*/
canvas.drawCircle(prePointX, prePointY, mSmallDotRadius, mPointPaint);
}
两条折线重合的地方,需要特殊考虑,比如希望两条折线重合的地方折线变为白色
设置下两条折线的画笔即可
mPaintLine2.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
mPaintLine1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
测试代码及效果;
final Random random=new Random();
final LineChartView myView=(LineChartView)findViewById(R.id.custom_view);
final LineChartView.Performance[] performances1=new LineChartView.Performance[8];
final LineChartView.Performance[] performances2=new LineChartView.Performance[8];
myView.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
for(int i=0;i<performances1.length;i++){
switch (random.nextInt(2016)%3){
case 0:
performances1[i]= LineChartView.Performance.WIN;
break;
case 1:
performances1[i]= LineChartView.Performance.DRAW;
break;
case 2:
performances1[i]= LineChartView.Performance.LOSE;
break;
default:
performances1[i]= LineChartView.Performance.LOSE;
break;
}
switch (random.nextInt(2016)%3){
case 0:
performances2[i]= LineChartView.Performance.WIN;
break;
case 1:
performances2[i]= LineChartView.Performance.DRAW;
break;
case 2:
performances2[i]= LineChartView.Performance.LOSE;
break;
default:
performances1[i]= LineChartView.Performance.LOSE;
break;
}
}
myView.setPerformances(performances1,performances2);
}
});
完整代码如下:
public class LineChartView extends View {
private Context context;
/*动画插值器*/
DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
/*动画刷新的次数*/
private int mDuration = 10;
/*当前动画进度值*/
private int mCurrentTime = 0;
private Performance[] mPerformance_1, mPerformance_2;
/*两条折线的颜色*/
private int mLineColor1, mLineColor2;
/*绘制表头文字画笔*/
private Paint mPaintText = new Paint();
/*绘制表格的画笔*/
private Paint mPaintLine = new Paint();
/*第一条折线的画笔*/
private Paint mPaintLine1 =new Paint();
/*第二条折线的画笔*/
private Paint mPaintLine2 =new Paint();
/*坐标点的小圆点画笔*/
private Paint mPointPaint = new Paint();
private float mSmallDotRadius = 4;
private TypedValue typedValue;
private int mPaintClolor;
/*Handler刷新界面产生动画效果*/
private Handler mHandler = new Handler();
private Runnable mAnimation = new Runnable() {
@Override
public void run() {
if (mCurrentTime < mDuration) {
mCurrentTime++;
LineChartView.this.invalidate();
}
}
};
public LineChartView(Context context) {
super(context);
this.context=context;
init();
}
public LineChartView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context=context;
init();
}
public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context=context;
init();
}
public enum Performance {
WIN(0),
DRAW(1),
LOSE(2);
public int type;
Performance(int type) {
this.type = type;
}
}
public void setPerformances(Performance[] performance1, Performance[] performance2) {
if (performance1 == null) {
performance1 = new Performance[0];
}
if (performance2 == null) {
performance2 = new Performance[0];
}
mPerformance_1 = Arrays.copyOf(performance1, performance1.length > 8 ? 8 : performance1.length);
mPerformance_2 = Arrays.copyOf(performance2, performance2.length > 8 ? 8 : performance2.length);
if (isShown()) {
mCurrentTime = 0;
this.invalidate();
}
}
/**
* 设置折线1的颜色
*
* @param mLineColor1
*/
public void setLineColor1(int mLineColor1) {
this.mLineColor1 = mLineColor1;
}
/**
* 设置折线2的颜色
*
* @param mLineColor2
*/
public void setLineColor2(int mLineColor2) {
this.mLineColor2 = mLineColor2;
}
private void init() {
mLineColor1=Color.BLUE;
mLineColor2 = Color.GREEN;
typedValue=new TypedValue();
context.getTheme().resolveAttribute(R.attr.title_bar,typedValue,true);
mPaintClolor =getResources().getColor(typedValue.resourceId);
final LineChartView.Performance[] performances1=new LineChartView.Performance[8];
final LineChartView.Performance[] performances2=new LineChartView.Performance[8];
final Random random=new Random();
for(int i=0;i<performances1.length;i++){
switch (random.nextInt(2016)%3){
case 0:
performances1[i]= LineChartView.Performance.WIN;
break;
case 1:
performances1[i]= LineChartView.Performance.DRAW;
break;
case 2:
performances1[i]= LineChartView.Performance.LOSE;
break;
default:
performances1[i]= LineChartView.Performance.LOSE;
break;
}
switch (random.nextInt(2016)%3){
case 0:
performances2[i]= LineChartView.Performance.WIN;
break;
case 1:
performances2[i]= LineChartView.Performance.DRAW;
break;
case 2:
performances2[i]= LineChartView.Performance.LOSE;
break;
default:
performances1[i]= LineChartView.Performance.LOSE;
break;
}
}
setPerformances(performances1,performances2);
}
/**
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/*获取控件总宽高*/
float totalWidth = getWidth();
float totalHeight = getHeight();
/*左边取总宽度的八分之一绘制表格头部*/
float textWide = totalWidth / 8;
/*左边留一点空白*/
float left_offset = 10;
/*折线图的总宽度等于控件的总宽度减去表头和留白*/
float chartWide = totalWidth - textWide - left_offset;
/*一共三行,设置每一行的垂直坐标*/
float[] mLineYs = new float[]{totalHeight / 8, totalHeight / 2, totalHeight * 7 / 8};
/*一共八列,设置每一列的水平坐标*/
float[] mLineXs = new float[]{
textWide + left_offset + chartWide * 0 / 8,
textWide + left_offset + chartWide * 1 / 8,
textWide + left_offset + chartWide * 2 / 8,
textWide + left_offset + chartWide * 3 / 8,
textWide + left_offset + chartWide * 4 / 8,
textWide + left_offset + chartWide * 5 / 8,
textWide + left_offset + chartWide * 6 / 8,
textWide + left_offset + chartWide * 7 / 8,
};
/*绘制表头文字*/
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setColor(mPaintClolor);
mPaintText.setAlpha(226);
mPaintText.setTextSize(28);
/*从中间开始绘制*/
mPaintText.setTextAlign(Paint.Align.CENTER);
/*测量文字大小,并计算偏移量*/
Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
float textBaseLineOffset = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
canvas.drawText("胜场", textWide / 2, mLineYs[0] + textBaseLineOffset, mPaintText);
canvas.drawText("平局", textWide / 2, mLineYs[1] + textBaseLineOffset, mPaintText);
canvas.drawText("负场", textWide / 2, mLineYs[2] + textBaseLineOffset, mPaintText);
/*绘制表格画笔设置*/
mPaintLine.setStyle(Paint.Style.STROKE);
mPaintLine.setAntiAlias(true);
mPaintLine.setColor(mPaintClolor);
mPaintLine.setAlpha(80);
mPaintLine.setStrokeWidth(1);
/*开始绘制表格*/
/*绘制三条横线*/
for(int i=0;i<3;i++){
canvas.drawLine(textWide, mLineYs[i], totalWidth, mLineYs[i], mPaintLine);
}
/*绘制八条竖线*/
for(int i=0;i<8;i++){
canvas.drawLine(mLineXs[i], 0, mLineXs[i], totalHeight, mPaintLine);
}
/*折线图画笔设置*/
mPaintLine1.setStyle(Paint.Style.STROKE);
/*设置透明度,取值范围为0~255,数值越小越透明,0表示完全透明*/
mPaintLine1.setAlpha(0);
mPaintLine1.setAntiAlias(true);
mPaintLine1.setColor(mLineColor1);
mPaintLine1.setStrokeWidth(5);
mPaintLine2.setStyle(Paint.Style.STROKE);
/*设置透明度,取值范围为0~255,数值越小越透明,0表示完全透明*/
mPaintLine2.setAlpha(0);
mPaintLine2.setAntiAlias(true);
mPaintLine2.setColor(mLineColor2);
mPaintLine2.setStrokeWidth(5);
if (typedValue.resourceId==R.color.white){
/*PorterDuff.Mode.SCREEN 上下层都显示。*/
mPaintLine2.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
mPaintLine1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
}else {
/*PorterDuff.Mode.DARKEN 上下层都显示。变暗*/
mPaintLine2.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
mPaintLine1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
}
/*画节点处的小圆点的画笔设置*/
mPointPaint.setStyle(Paint.Style.STROKE);
mPointPaint.setAntiAlias(true);
mPointPaint.setColor(mPaintClolor);
/*计算当前动画进度对应的数值*/
float animCurrentValue = mDecelerateInterpolator.getInterpolation(1.0f * mCurrentTime / mDuration);
mPaintLine.setColor(mLineColor1);
/*绘制第一条折线的路径*/
for (int i = 0; i < mPerformance_1.length - 1; i++) {
/*折线图的折线的画笔设置粗一点*/
mPaintLine.setStrokeWidth(5);
/*计算当前节点的坐标值*/
float prePointX =mLineXs[i];
float prePointY =mLineYs[2] - (mLineYs[2] - mLineYs[mPerformance_1[i].type]) * animCurrentValue;
/*计算下一个节点的坐标值*/
float nextPointX=mLineXs[i + 1];
float nextPointY=mLineYs[2] - (mLineYs[2] - mLineYs[mPerformance_1[i + 1].type]) * animCurrentValue;
/*连接当前坐标和下一个坐标,绘制线段*/
canvas.drawLine(prePointX, prePointY, nextPointX, nextPointY, mPaintLine1);
/*当前节点坐标处绘制小圆*/
canvas.drawCircle(prePointX, prePointY, mSmallDotRadius, mPointPaint);
}
/*第一个折线图的最后一个节点的坐标*/
float lastPointX=mLineXs[mPerformance_1.length - 1];
float lastPointY= mLineYs[2] - (mLineYs[2] - mLineYs[mPerformance_1[mPerformance_1.length - 1].type]) * animCurrentValue;
/*绘制最后一个节点的外围小圆*/
canvas.drawCircle(lastPointX,lastPointY ,mSmallDotRadius, mPointPaint);
/*绘制第二条折线*/
mPaintLine.setColor(mLineColor2);
for (int i = 0; i < mPerformance_2.length - 1; i++) {
/*折线图的折线的画笔设置粗一点*/
mPaintLine.setStrokeWidth(5);
/*计算当前节点的坐标值*/
float prePointX =mLineXs[i];
float prePointY =mLineYs[2] - (mLineYs[2] - mLineYs[mPerformance_2[i].type]) * animCurrentValue;
/*计算下一个节点的坐标值*/
float nextPointX=mLineXs[i + 1];
float nextPointY=mLineYs[2] - (mLineYs[2] - mLineYs[mPerformance_2[i + 1].type]) * animCurrentValue;
/*连接当前坐标和下一个坐标,绘制线段*/
canvas.drawLine(prePointX, prePointY, nextPointX, nextPointY, mPaintLine2);
/*当前节点坐标处绘制小圆*/
canvas.drawCircle(prePointX, prePointY, mSmallDotRadius, mPointPaint);
}
/*第一个折线图的最后一个节点的坐标*/
lastPointX=mLineXs[mPerformance_2.length - 1];
lastPointY= mLineYs[2] - (mLineYs[2] - mLineYs[mPerformance_2[mPerformance_2.length - 1].type]) * animCurrentValue;
/*绘制最后一个节点的外围小圆*/
canvas.drawCircle(lastPointX,lastPointY ,mSmallDotRadius, mPointPaint);
mHandler.postDelayed(mAnimation, 20);
}
}
来源:https://blog.csdn.net/wangshihui512/article/details/51445506


猜你喜欢
- 混淆studio 使用Proguard进行混淆,其是一个压缩、优化和混淆java字节码文件的一个工具。功能:Shrinking(压缩)、Op
- 1.引言在实习期间,感受到在vs code上编程的优势(实习期间主要写的lua脚本),因此想把C++和python的开发也迁移到vs cod
- 今天在APP中增加一个添加项目的功能,项目的主键为整数,要让它自增长。既然要自增长,那么在代码里面就不用给id字段赋值。但是调试的时候发现不
- 本文实例讲述了Android编程实现自定义ProgressBar样式。分享给大家供大家参考,具体如下:效果图如下,本例中设置了第一级进度条和
- 下载:1.在spring-mvc中配置(用于100M以下的文件下载)<bean class="org.springframe
- 线性表是其组成元素间具有线性关系的一种数据结构,对线性表的基本操作主要有,获取元素,设置元素值,遍历,插入,删除,查找,替换,排序等。而线性
- 实现跨服务的远程调用(RestTemplate)业务场景:在返回订单信息数据中显示用户信息实现思路:基于RestTemplate发起的htt
- 前言最近在工作中需要编译android下的动态库,本以为是一件简单的事,没想到因为工具,以及google本身被墙的原因,折腾了好久。在win
- 本文研究的主要是JVM中的flag设置详解的相关内容,具体介绍如下。一、堆大小设置-Xmx3550m:设置JVM最大可用内存为3550M。-
- 近期做简单的新闻客户端界面使用到了Jsoup获取,使用起来特别方便,这也是被我一个学长称为学android网络必学的一个东西,在此也是分享一
- 在Android应用中,图片裁剪也是一个经常用到的功能。Android系统中可以用隐式意图调用系统应用进行裁剪,但是这样做在不同的手机可能表
- 本文实例讲述了C# Winform实现捕获窗体最小化、最大化、关闭按钮事件的方法,主要是通过重写WndProc来实现的。分享给大家供大家参考
- 一、 简介Opitonal是java8引入的一个新类,目的是为了解决空指针异常问题。本质上,这是一个包含有可选值的包装类,这意味着 Opti
- 一、背景当我们在drools中编写规则时,有些时候存在重复的代码,那么我们是否可以将这些重复代码抽取出来,封装成一个function来调用呢
- 前言目前主流的锁有两种,一种是synchronized,另一种就是ReentrantLock,JDK优化到现在目前为止synchronize
- 事务介绍一个事务要么同时成功,要么同时失败特性Atomic原子性 事务是由一个或多个活动组成的一个工作单元。原子性确保事务中的所有操作全部发
- 案例:当你输入信用卡号码的时候,有没有担心输错了而造成损失呢?其实可以不必这么担心,因为并不是一个随便的信用卡号码都是合法的,它必须通过Lu
- 本文实例为大家分享了winform实现五子棋游戏的具体代码,供大家参考,具体内容如下利用数组,根据新旧数组值的不同,获取那个点是什么棋子;说
- 在一些需要经常更新页面数据的网站中,一般访问量不是很大的都直接发布的是带后台代码,每次访问都是有数据库交互的。但是一旦访问量增加了,那么这些
- 前言如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,