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即可,这里不再贴出。可能写得有点仓促,如果不妥之处,请大家批评指正,谢谢!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android HelloChart开源库图表之折线图的实例代码

    前面我们介绍了开源图表库MPAndroidChart,请参考: Android MPAndroidChart开源库图表之折线图的实例代码 我们今天介绍的将是一个更为优秀的图表库,比MPAndroidChart性能更好,功能更完善,UI风格更美观,坐标轴更精细. 支持缩放.滑动以及平移.Zoom(pinch to zoom, double tap zoom), scroll and fling 支持自定义坐标轴(比如坐标轴位置:上下左右内部),支持自动生成坐标轴.Custom and auto-g

  • 详解Android图表 MPAndroidChart折线图

    1.介绍 MPAndroidChart GitHub地址 MPAndroidChart的强大之处就不在多说了,目前最新的版本是3.0.1,在新版本中很多方法都被弃用了,这个要注意一下,在网上查到的大多数资料都是关于旧版本的,今天来实现一下折线图,把过程记录下来,分享给大家. 效果图: 2.引入开源库 在项目根目录的build.gradle文件中加入如下代码 allprojects { repositories { maven { url "https://jitpack.io" } }

  • Android自定义可左右滑动和点击的折线图

    前言 前几天有小盆友让我写一个折线图,可以点击,可以左右滑动.对于折线肯定有很多项目都使用过,所以网上肯定也有很多demo,像AndroidChart.HelloChart之类的,功能相当丰富,效果也很赞,但是太重了,其他的小demo又不符合要求,当然了,我写的自定义折线图的思想也有来自这些小demo,对他们表示感谢. 效果图 废话不多说,先上效果图: 效果是不是很赞,如果上图满足你的需求,那就继续往下看. 自定义折线图的步骤: 1.自定义view所需要的属性 确定所需要的自定义view的属性,

  • Android自定义控件实现折线图

    本文实例实现一个如下图所示的Android折线图,供大家参考,具体内容如下 首先是控件绘图区域的划分,控件左边取一小部分(控件总宽度的八分之一)绘制表头,右边剩余的部分绘制表格 确定表格的行列数,首先绘制一个三行八列的网格,设置好行列的坐标后开始绘制 /*绘制三条横线*/ for(int i=0;i<3;i++){ canvas.drawLine(textWide, mLineYs[i], totalWidth, mLineYs[i], mPaintLine); } /*绘制八条竖线*/ for

  • Android开发之天气趋势折线图

    先来看下效果: 控件内容比较简单,就是一个普通的折线图,上下分别带有数字,点击的时候显示当天温度的差值. 创建一个类继承自View,并添加两个构造方法: public class TrendGraph extends View { public TrendGraph(Context context) { // 在java代码中创建调用 super(context); } public TrendGraph(Context context, AttributeSet attrs) { // 在xm

  • Android开发实现绘制淘宝收益图折线效果示例

    本文实例讲述了Android开发实现绘制淘宝收益图折线效果.分享给大家供大家参考,具体如下: 实现的效果我一会贴上,我先说下原理,我们知道要实现在canvas上画线,不就是要搞一个paint嘛,然后首先肯定要设置下paint的属性,那么画文字呢,不就是Textpaint吗,对,就是这么简单,接下来怎么画,折线图主要分为X轴和y轴,x轴表示日期,y表示收益,好,说道这里,大家应该知道怎么去做了,下面直接贴代码 这个方法是,画x,y坐标系的,以及上面的日期和收益了 private void draw

  • Android MPAndroidChart开源库图表之折线图的实例代码

    本文讲述了Android MPAndroidChart开源库图表之折线图的实例代码.分享给大家供大家参考,具体如下: 承接上一篇文章,请参考Android HelloChart开源库图表之折线图的实例代码 1. 将mpandroidchartlibrary-2-0-8.jar包copy到项目的libs中: 2. 定义xml文件. 3.  主要Java逻辑代码如下,注释已经都添加上了. package com.example.mpandroidlinechart; import java.util

  • Android自定义View实现折线图效果

    下面就是结果图(每种状态用一个表情图片表示): 一.主页面的布局文件如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height=&quo

  • Android自定义View简易折线图控件(二)

    继续练习自定义View,这次带来的是简易折线图,支持坐标点点击监听,效果如下: 画坐标轴.画刻度.画点.连线..x.y轴的数据范围是写死的 1 <= x <= 7 ,1 <= y <= 70 ..写活的话涉及到坐标轴刻度的动态计算.坐标点的坐标修改,想想就头大,这里只练习自定义View. 1.在res/values文件夹下新建attrs.xml文件,编写自定义属性: <?xml version="1.0" encoding="utf-8"

  • Android实现折线走势图

    本文实例为大家分享了Android折线走势图的具体代码,供大家参考,具体内容如下 先来看看效果图 可以根据球的数量动态的改变自己的球半径,以及线宽 代码实现也是超级简单 //获取自定义属性 private void obtainStyledAttrs(AttributeSet attrs) { TypedArray typedArray = getContext().obtainStyledAttributes(attrs,R.styleable.High_LowChartView); mTex

  • MPAndroidChart开源图表库的使用介绍之饼状图、折线图和柱状图

    MPAndroidChart开源图表库之饼状图 为大家介绍一款图标开源库MPAndroidChart,它不仅可以在Android设备上绘制各种统计图表,而且可以对图表进行拖动和缩放操作,用起来非常灵活.MPAndroidChart同样拥有常用的图表类型:线型图.饼图.柱状图和散点图. mpandroidchartlibrary.jar包下载地址: https://github.com/PhilJay/MPAndroidChart/releases 下面主要实现以下饼状图: 1.从上面的地址中下载

随机推荐