欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Android开发自定义控件之折线图实现方法详解

程序员文章站 2022-04-28 21:01:15
本文实例讲述了android开发自定义控件之折线图实现方法。分享给大家供大家参考,具体如下:前言折线图是android开发中经常会碰到的效果,但由于涉及自定义view的知识,对许多刚入门的小白来说会觉...

本文实例讲述了android开发自定义控件之折线图实现方法。分享给大家供大家参考,具体如下:

前言

折线图是android开发中经常会碰到的效果,但由于涉及自定义view的知识,对许多刚入门的小白来说会觉得很高深。其实不然,接下来我就以尽量通俗的语言来说明下图折线图效果的实现过程。

效果图

Android开发自定义控件之折线图实现方法详解

实现过程

首先,选择自定义控件的方式。

自定义控件的实现有四种方式:

1.继承view,重写ondraw、onmeasure等方法。
2.继承已有的view(比如textview)。
3.继承viewgroup实现自定义布局。
4.继承已有的viewgroup(比如linearlayout)。

由于我们不需要多个控件进行组合,也不需要在原有控件基础上改造,故我们采用第1种方式即继承view来实现。代码如下,新建一个chartview类继承自view,并实现他的几个构造方法,并重写ondraw和onmeasure方法,因为我们要在ondraw方法里面进行绘制工作,并且我希望这个控件的长宽是相等的,所以在onmeasure方法设置宽高相等。设置长宽相等的方式很简单,我们不需要自己去测量实现,只需要调用父类的onmeasure方法,传参数(宽高值)时将都传入宽度(或者高度)即可。

public class chartview extends view {

  public chartview(context context) {
    super(context);
  }

  public chartview(context context, @nullable attributeset attrs) {
    super(context, attrs);
  }

  public chartview(context context, @nullable attributeset attrs, int defstyleattr) {
    super(context, attrs, defstyleattr);
  }

  @override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    super.onmeasure(widthmeasurespec, widthmeasurespec);
  }

  @override
  protected void ondraw(canvas canvas) {
    super.ondraw(canvas);
  }
}

其次,绘制简单图形并显示出来。

在进行绘制之前,我们要进行若干初始化工作,其中就包括画笔的初始化。然后就可以进行绘制了,我们先绘制一个简单的圆圈,然后将控件放到布局文件中,运行看看效果。

chartview代码

public class chartview extends view {

  // 画笔
  private paint paint;

  /**
  * 构造函数
  */
  public chartview(context context) {
    super(context);
    initwork();
  }

  /**
  * 构造函数
  */
  public chartview(context context, @nullable attributeset attrs) {
    super(context, attrs);
    initwork();
  }

  /**
  * 构造函数
  */
  public chartview(context context, @nullable attributeset attrs, int defstyleattr) {
    super(context, attrs, defstyleattr);
    initwork();
  }

  /**
  * 初始化工作
  */
  private void initwork() {
    initpaint();
  }

  /**
  * 画笔设置
  */
  private void initpaint() {
    paint = new paint(paint.anti_alias_flag);
    // 画笔样式为填充
    paint.setstyle(paint.style.fill);
    // 颜色设为红色
    paint.setcolor(color.red);
    // 宽度为3像素
    paint.setstrokewidth(3);
  }

  @override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    super.onmeasure(widthmeasurespec, widthmeasurespec);
  }

  @override
  protected void ondraw(canvas canvas) {
    super.ondraw(canvas);
    // 画圆
    canvas.drawcircle(300,300,100,paint);
  }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.constraintlayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"

  <com.toprs.linechart.chartview
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

</android.support.constraint.constraintlayout>

效果:

Android开发自定义控件之折线图实现方法详解

然后,绘制图表。

到目前为止,已经实现了最简单的一个自定义控件,虽然它什么功能都没有,只是简单显示一个红色圆圈,但本质都是一样的。接下来就开始图表的绘制。

1.初始化一些需要使用的值。

  // 刻度之间的距离
  private int degreespace;

  @override
  protected void ondraw(canvas canvas) {
    super.ondraw(canvas);
    // 控件上下左右边界四至及控件的宽度(同时也是高度!)
    int left = getleft();
    int right = getright();
    int top = gettop();
    int bottom = getbottom();
    int w = getwidth();

    // 图表距离控件边缘的距离
    int graphpadding = w / 10;
    // 图表上下左右四至
    int graphleft = left + graphpadding;
    int graphbottom = bottom - graphpadding;
    int graphright = right - graphpadding;
    int graphtop = top + graphpadding;
    // 图表宽度(也等同高度奥~)
    int graphw = graphright - graphleft;
    // 刻度之间的距离
    degreespace = graphw / 8;
  }

2.灰色背景

  // 背景
  canvas.drawcolor(color.ltgray);

3.坐标系

  // 画笔设置样式为stroke样式,即只划线不填充
  paint.setstyle(paint.style.stroke);

  // 坐标系绘制
  path pivotpath = new path();
  //y轴
  pivotpath.moveto(graphleft, graphbottom);
  pivotpath.lineto(graphleft, graphtop);
  //y轴箭头
  pivotpath.lineto(graphleft - 12, graphtop + 20);
  pivotpath.moveto(graphleft, graphtop);
  pivotpath.lineto(graphleft + 12, graphtop + 20);
  //x轴
  pivotpath.moveto(graphleft, graphbottom);
  pivotpath.lineto(graphright, graphbottom);
  //x轴箭头
  pivotpath.lineto(graphright - 20, graphbottom + 12);
  pivotpath.moveto(graphright, graphbottom);
  pivotpath.lineto(graphright - 20, graphbottom - 12);
  canvas.drawpath(pivotpath, paint);

4.刻度虚线及数字

  // y轴刻度虚线
  for (int i = 1; i < 8; i++) {
    path ykedupath = new path();
    // 线
    paint.setcolor(color.white);
    paint.setstrokewidth(1);
    paint.setstyle(paint.style.stroke);
    paint.setpatheffect(new dashpatheffect(new float[]{5,5},0));
    ykedupath.moveto(graphleft, graphbottom - i * degreespace);
    ykedupath.lineto(graphright, graphbottom - i * degreespace);
    canvas.drawpath(ykedupath, paint);
    // 数字
    paint.setcolor(color.black);
    paint.setstyle(paint.style.fill);
    paint.settextsize(25);
    paint.setpatheffect(null);
    canvas.drawtext(i + "", graphpadding / 2, graphbottom - i * degreespace, paint);
  }
  // x轴刻度虚线
  for (int i = 1; i < 8; i++) {
    path xkedupath = new path();
    // 线
    paint.setcolor(color.white);
    paint.setstyle(paint.style.stroke);
    paint.setstrokewidth(1);
    paint.setpatheffect(new dashpatheffect(new float[]{5,5},0));
    xkedupath.moveto(graphleft + i * degreespace, graphbottom);
    xkedupath.lineto(graphleft + i * degreespace, graphtop);
    canvas.drawpath(xkedupath, paint);
    // 数字
    paint.setcolor(color.black);
    paint.setstyle(paint.style.fill);
    paint.settextsize(25);
    paint.setpatheffect(null);
    canvas.drawtext(i + "", graphleft + i * degreespace, graphbottom + graphpadding / 2, paint);
  }

5.折线

在绘制折线之前,我们先要初始化几个参数。

  // 模拟数据
  private float[] data = {3.2f, 4.3f, 2.5f, 3.2f, 3.8f, 7.1f, 1.3f, 5.6f};
  // 当前显示的数据数量
  private int shownum=1;

  // 折线
  path linepath = new path();
  for (int i = 0; i < shownum; i++) {
    int topointx = graphleft + i * degreespace;
    int topointy = graphbottom - ((int) (data[i] * degreespace));
    paint.setcolor(color.yellow);
    paint.setstyle(paint.style.stroke);
    if (i==0){
      linepath.moveto(topointx,topointy);
    }else {
      linepath.lineto(topointx, topointy);
    }
    // 节点圆圈
    canvas.drawcircle(topointx, topointy,10,paint);
    paint.setcolor(color.white);
    paint.setstyle(paint.style.fill);
    canvas.drawcircle(topointx,topointy,7,paint);
  }
  paint.setcolor(color.yellow);
  paint.setstyle(paint.style.stroke);
  paint.setstrokewidth(3);
  canvas.drawpath(linepath, paint);

6.让图表动起来

为了实现数据依次显现的动画,我们开启一个线程是当前显示的数据数量即shownum变量不断加一,并间隔时间0.5秒。然后postinvalidate()重绘即可。

  private void initwork() {
    initpaint(); 
    // 开启线程,没隔0.5秒shownum加一
    new thread(new runnable() {
      @override
      public void run() {
        while (true){
          if (shownum<data.length){
            shownum++;
          }else {
            shownum=1;
          }
          // 重绘
          postinvalidate();
          // 休眠0.5秒
          try {
            thread.sleep(500);
          } catch (interruptedexception e) {
            e.printstacktrace();
          }
        }
      }
    }).start();
  }

好了,运行一下,便会实现上面的效果了。如果你觉得效果不够炫酷或者功能太少,那就自己完善吧~~

结语

由于自定义控件是android进阶路上必然要碰到的知识,所以希望大家重视。其实自定义控件说难也难说简单也简单。实现一些普通的效果还是很方便的,像这次举的例子,但如果要实现各种炫酷效果并且要完善各种功能的话,就需要各种知识的配合了,包括数学、物理、绘图等知识。所以还是需要平时不断积累的,看到别人的控件很棒的时候自己可以试着去实现一下,对自己的知识库不断进行补充,自然会娴熟的运用。本人也是菜鸟一枚,望共勉!!