Android绘制动态折线图
作者:Knick_Zhang 发布时间:2021-12-28 12:59:50
标签:Android,折线图
所谓动态折线图,就是折线图能随着手指的滑动进行动态绘制,这里很定会产生动画效果。基于这个效果,这里使用SurfaceView进行制图。
实现步奏如下:
(1): 这里新建一个绘图ChartView,继承SurfaceView并实现SurfaceHolder.Callback , Runnable接口,主要绘图工作在子线程中完成。
(2):现实 SurfaceHolder.Callback接口的三个方法,并在 surfaceCreated中开启子线程进行绘图。
(3):重写onTouchEvent方法,在Move事件中,根据手指的滑动距离计算偏移量,具体实现请看代码。
(4): 这里的折线图的坐标值是随意添加的,可以在实际项目中根据需求自己添加。
(5):此例中有大量从集合中添加和删除元素,建议使用LinkedList来进行保存数据。
自定义ChartView:
public class ChartView extends SurfaceView implements SurfaceHolder.Callback , Runnable
{
private Context mContext;
private Paint mPaint;
private Resources res;
private DisplayMetrics dm;
private int canvasHeight;
private int canvasWidth;
private int bHeight = 0;
private int bWidth;
private boolean isMeasure = true;
private boolean canScrollRight = true;
private boolean canScrollLeft = true;
//y轴最大值
private int maxValue;
//y轴间隔值
private int averageValue;
private int marginTop = 20;
private int marginBottom = 80;
//曲线上的总点数
private Point[] mPoints;
//纵坐标值
private LinkedList<Double> yRawData;
//横坐标值
private LinkedList<String> xRawData;
//根据间隔计算出的每个X的值
private LinkedList<Integer> xList = new LinkedList<>();
private LinkedList<String> xPreData = new LinkedList<>();
private LinkedList<Double> yPreData = new LinkedList<>();
private LinkedList<String> xLastData = new LinkedList<>();
private LinkedList<Double> yLastData = new LinkedList<>();
private int spacingHeight;
private SurfaceHolder holder;
private boolean isRunning = true;
private int lastX;
private int offSet;
private Rect mRect;
private int xAverageValue = 0;
public ChartView(Context context)
{
this(context , null);
}
public ChartView(Context context , AttributeSet attrs)
{
super(context, attrs);
this.mContext = context;
initView();
}
private void initView()
{
this.res = mContext.getResources();
this.mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dm = new DisplayMetrics();
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
xPreData.add("05-18");
xPreData.add("05-17");
xPreData.add("05-16");
xPreData.add("05-15");
xPreData.add("05-14");
xPreData.add("05-13");
yPreData.add(4.53);
yPreData.add(3.45);
yPreData.add(6.78);
yPreData.add(5.21);
yPreData.add(2.34);
yPreData.add(6.32);
xLastData.add("05-26");
xLastData.add("05-27");
xLastData.add("05-28");
xLastData.add("05-29");
xLastData.add("05-30");
xLastData.add("05-31");
yLastData.add(2.35);
yLastData.add(5.43);
yLastData.add(6.23);
yLastData.add(7.33);
yLastData.add(3.45);
yLastData.add(2.45);
holder = this.getHolder();
holder.addCallback(this);
}
@Override
protected void onSizeChanged(int w , int h , int oldW , int oldH)
{
if (isMeasure)
{
this.canvasHeight = getHeight();
this.canvasWidth = getWidth();
if (bHeight == 0)
{
bHeight = canvasHeight - marginBottom;
}
bWidth = dip2px(30);
xAverageValue = (canvasWidth - bWidth) / 7;
isMeasure = false;
}
}
@Override
public void run()
{
while (isRunning)
{
drawView();
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
private void drawView()
{
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.WHITE);
mPaint.setColor(res.getColor(R.color.color_f2f2f2));
drawAllXLine(canvas);
mRect = new Rect(bWidth - 3, marginTop - 5 ,
bWidth + (canvasWidth - bWidth) / yRawData.size() * (yRawData.size() - 1) + 3, bHeight + marginTop + marginBottom);
//锁定画图区域
canvas.clipRect(mRect);
drawAllYLine(canvas);
mPoints = getPoints();
mPaint.setColor(res.getColor(R.color.color_ff4631));
mPaint.setStrokeWidth(dip2px(2.5f));
mPaint.setStyle(Paint.Style.STROKE);
drawLine(canvas);
mPaint.setStyle(Paint.Style.FILL);
for (int i = 0 ; i < mPoints.length ; i++)
{
canvas.drawCircle(mPoints[i].x , mPoints[i].y , 5 , mPaint);
}
holder.unlockCanvasAndPost(canvas);
}
//绘制折线图
private void drawLine(Canvas canvas)
{
Point startP = null;
Point endP = null;
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 drawAllYLine(Canvas canvas)
{
for (int i = 0 ; i < yRawData.size() ; i++)
{
if (i == 0)
{
canvas.drawLine(bWidth, marginTop , bWidth, bHeight + marginTop , mPaint);
}
if (i == yRawData.size() - 1)
{
canvas.drawLine(bWidth + xAverageValue * i, marginTop , bWidth + xAverageValue * i , bHeight + marginTop , mPaint);
}
xList.add(bWidth + xAverageValue * i);
canvas.drawLine(bWidth + xAverageValue * i + offSet, marginTop , bWidth + xAverageValue * i + offSet , bHeight + marginTop , mPaint);
drawText(xRawData.get(i) , bWidth + xAverageValue * i - 30 + offSet, bHeight + dip2px(26) , canvas);
}
}
//绘制所有的横向分割线
private void drawAllXLine(Canvas canvas)
{
for (int i = 0 ; i < spacingHeight + 1 ; i++)
{
canvas.drawLine(bWidth , bHeight - (bHeight / spacingHeight) * i + marginTop ,
bWidth + xAverageValue * (yRawData.size() - 1) , bHeight - (bHeight / spacingHeight) * i + marginTop , mPaint);
drawText(String.valueOf(averageValue * i) , bWidth / 2 , bHeight - (bHeight / spacingHeight) * i + marginTop, canvas);
}
}
//绘制坐标值
private void drawText(String text , int x , int y , Canvas canvas)
{
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setTextSize(dip2px(12));
p.setColor(res.getColor(R.color.color_999999));
p.setTextAlign(Paint.Align.LEFT);
canvas.drawText(text , x , y , p);
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder)
{
new Thread(this).start();
Log.d("OOK" , "Created");
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2)
{
Log.d("OOK" , "Changed");
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder)
{
isRunning = false;
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction();
int rawX = (int) event.getX();
switch (action)
{
case MotionEvent.ACTION_DOWN:
lastX = rawX;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = rawX - lastX;
if (xPreData.size() == 0 && offSet > 0)
{
offSet = 0;
canScrollRight = false;
}
if (xLastData.size() == 0 && offSet < 0)
{
offSet = 0;
canScrollLeft = false;
}
offSet = offSet + offsetX;
if (offSet > xAverageValue && canScrollRight)
{
offSet = offSet % xAverageValue;
xRawData.addFirst(xPreData.pollFirst());
yRawData.addFirst(yPreData.pollFirst());
xLastData.addFirst(xRawData.removeLast());
yLastData.addFirst(yRawData.removeLast());
canScrollLeft = true;
}
if (offSet < -xAverageValue && canScrollLeft)
{
offSet = offSet % xAverageValue;
xRawData.addLast(xLastData.pollFirst());
yRawData.addLast(yLastData.pollFirst());
xPreData.addFirst(xRawData.removeFirst());
yPreData.addFirst(yRawData.removeFirst());
canScrollRight = true;
}
lastX = rawX;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
private Point[] getPoints()
{
Point[] points = new Point[yRawData.size()];
for (int i = 0 ; i < yRawData.size() ; i++)
{
int ph = bHeight - (int)(bHeight * (yRawData.get(i) / maxValue));
points[i] = new Point(xList.get(i) + offSet , ph + marginTop);
}
return points;
}
public void setData(LinkedList<Double> yRawData , LinkedList<String> xRawData , int maxValue , int averageValue)
{
this.maxValue = maxValue;
this.averageValue = averageValue;
this.mPoints = new Point[yRawData.size()];
this.yRawData = yRawData;
this.xRawData = xRawData;
this.spacingHeight = maxValue / averageValue;
}
private int dip2px(float dpValue)
{
return (int) (dpValue * dm.density + 0.5f);
}
}
MainActivity代码:
public class MainActivity extends Activity
{
LinkedList<Double> yList;
LinkedList<String> xRawData;
ChartView chartView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
chartView = (ChartView) findViewById(R.id.chartView);
yList = new LinkedList<>();
yList.add(2.203);
yList.add(4.05);
yList.add(6.60);
yList.add(3.08);
yList.add(4.32);
yList.add(2.0);
yList.add(5.0);
xRawData = new LinkedList<>();
xRawData.add("05-19");
xRawData.add("05-20");
xRawData.add("05-21");
xRawData.add("05-22");
xRawData.add("05-23");
xRawData.add("05-24");
xRawData.add("05-25");
chartView.setData(yList , xRawData , 8 , 2);
}
}
此例页面布局比较简单,就是在主页面布局中添加一个自定义的ChartView即可,这里不再贴出。可能写得有点仓促,如果不妥之处,请大家批评指正,谢谢!
来源:https://blog.csdn.net/qq_33022345/article/details/52459232


猜你喜欢
- Spring简介和配置学习目标【应用】能够独立完成springIOC的快速入门【应用】能够掌握spring的bean标签的配置【应用】能够独
- 作为一个初级GIS程序员,关于封装那些宏观的概念暂且不提,编程经常面对的就是“字段,属性,方法”,这也是面向对象的基本概念之一。1.字段通常
- 自定义控件(类似按钮等)的使用,自定义一个SurfaceView。 如某一块的动态图(自定义相应),或者类似UC浏览器下面的工具栏。 如下图
- RabbitMQ主要有六种种工作模式,本文整合SpringBoot分别介绍工作模式的实现。前提概念生产者消息生产者或者发送者,使用P表示:队
- 先上结论:不要直接用double变量作为构造BigDecimal的参数。线上有这么一段Java代码逻辑:1,接口传来一个JSON串,里面有个
- 简介本文用示例介绍java的Duration的用法。Duration和Period说明Duration类通过秒和纳秒相结合来描述一个时间量,
- 本文实例讲述了java实现图片裁切的工具类。分享给大家供大家参考,具体如下:package com.yanek.util;import ja
- 前言写Android:如何编写“万能”的Activity的这篇文章到现在已经好久了,但是由于最近事情较多,写重构篇的计划就一直被无情的耽搁下
- springboot service内组件加载顺序先加载自身构造器,所以在构造器中初始化时若使用需要注入的(即@Autowired注解的)组
- 主要是这个方 * ist<string> GetAllFileNames(string path,string pattern=&
- 在Android中偶尔会用到开关,Switch就是一个简单易使用的不错的控件。首先,在布局中添加上Switch控件:<Switch &
- 本文实例为大家分享了Android点击获取验证码倒计时的具体代码,供大家参考,具体内容如下package com.loaderman.cou
- 本文以实例描述了C#实现让窗体永远在窗体最前面显示的方法,具体步骤如下:1、新建一个窗体程序,添加一个Timer以及设置它可用并绑定事件。2
- 示例代码如下:launch(Dispatchers.Main) { // 第一部分 fl
- ArrayBlockingQueue介绍ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。线程安全是指,ArrayB
- QTableView是Qt中用来把数据集以表格形式提供给用户的一个控件QTableView类实现表格视图,QTableView的数据由继承Q
- 多数据源配置首先是配置文件这里采用yml配置文件,其他类型配置文件同理我配置了两个数据源,一个名字叫ds1数据源,一个名字叫ds2数据源,如
- 本文实例讲述了Android之日期及时间选择对话框用法。分享给大家供大家参考。具体如下:清单文件:<?xml version=&quo
- 本文实例讲述了C#使用WebClient登录网站并抓取登录后的网页信息实现方法。分享给大家供大家参考,具体如下:C#登录网站实际上就是模拟浏
- 本文实例讲述了WinForm生成验证码图片的方法。分享给大家供大家参考,具体如下:1、创建ValidCode类:public class V