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

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

程序员文章站 2023-11-17 12:41:22
继续练习自定义view,这次带来的是简易折线图,支持坐标点点击监听,效果如下: 画坐标轴、画刻度、画点、连线。。x、y轴的数据范围是写死的 1 <= x <...

继续练习自定义view,这次带来的是简易折线图,支持坐标点点击监听,效果如下:

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

画坐标轴、画刻度、画点、连线。。x、y轴的数据范围是写死的 1 <= x <= 7 ,1 <= y <= 70 。。写活的话涉及到坐标轴刻度的动态计算、坐标点的坐标修改,想想就头大,这里只练习自定义view。

1、在res/values文件夹下新建attrs.xml文件,编写自定义属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="linechartview">
 <attr name="textcolor" format="color" />
 <attr name="linecolor" format="color" />
 <attr name="pointcolor" format="color" />
 </declare-styleable>
</resources>

2、新建linechartview继承view,重写构造方法:

 public linechartview(context context) {
  this(context, null);
 }

 public linechartview(context context, attributeset attrs) {
  this(context, attrs, 0);
 }

 public linechartview(context context, attributeset attrs, int defstyleattr) {
  super(context, attrs, defstyleattr);
 }

3、在第三个构造方法中获取自定义属性的值:

 typedarray ta = context.obtainstyledattributes(attrs, r.styleable.linechartview, defstyleattr, 0);
 mtextcolor = ta.getcolor(r.styleable.linechartview_textcolor, 0xff381a59);
 mlinecolor = ta.getcolor(r.styleable.linechartview_linecolor, 0xff8e29fa);
 mpointcolor = ta.getcolor(r.styleable.linechartview_pointcolor, 0xffff5100);
 mpointradius = densityutils.dp2px(context, 3);
 ta.recycle();

4、创建画图所使用的对象,如paint、path:

 mtextpaint = new paint(paint.anti_alias_flag);
 mtextpaint.setstyle(paint.style.fill);
 mtextpaint.setcolor(mtextcolor);
 mtextpaint.settextsize(40);

 mlinepaint = new paint(paint.anti_alias_flag);
 mlinepaint.setstyle(paint.style.stroke);
 mlinepaint.setcolor(mlinecolor);
 mlinepaint.setstrokewidth(densityutils.dp2px(context, 2));
 mlinepaint.setstrokecap(paint.cap.round);
 mxypath = new path();

 mpointpaint = new paint(paint.anti_alias_flag);
 mpointpaint.setstyle(paint.style.fill);
 mpointpaint.setcolor(mpointcolor);
 mpointcirclepaint = new paint(paint.anti_alias_flag);
 mpointcirclepaint.setstyle(paint.style.stroke);
 mpointcirclepaint.setstrokewidth(densityutils.dp2px(context, 2));
 mpointcirclepaint.setcolor(mlinecolor);

5、重写onmeasure()方法,计算自定义view的宽高:

 @override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
  setmeasureddimension(measureddimension(widthmeasurespec), measureddimension(heightmeasurespec));
 }

 private int measureddimension(int measurespec) {
  int result;
  int mode = measurespec.getmode(measurespec);
  int size = measurespec.getsize(measurespec);
  if (mode == measurespec.exactly) {
   result = size;
  } else {
   result = 500;
   if (mode == measurespec.at_most) {
    result = math.min(result, size);
   }
  }
  return result;
 }

6、暴露一个设置x、y数据集合的方法:

 /**
  * 设置数据
  *
  * @param xlist x轴数据集合
  * @param ylist y轴数据集合
  */
 public void setdatalist(list<integer> xlist, list<integer> ylist) {
  if (xlist == null || ylist == null || xlist.size() == 0 || ylist.size() == 0) {
   throw new illegalargumentexception("没有数据");
  }
  if (xlist.size() != ylist.size()) {
   throw new illegalargumentexception("x、y轴数据长度不一致");
  }
  setpointdata(xlist, ylist);
  setpointanimator();
 }

 /**
  * 设置坐标点的数据、坐标
  *
  * @param xlist x轴数据集合
  * @param ylist y轴数据集合
  */
 private void setpointdata(list<integer> xlist, list<integer> ylist) {
  mpointlist = new arraylist<>();
  for (int i = 0; i < xlist.size(); i++) {
   chartpoint point = new chartpoint();
   //设置坐标点的xy数据
   point.setxdata(xlist.get(i));
   point.setydata(ylist.get(i));
   //计算坐标点的横纵坐标
   point.setx(xymargin + xlist.get(i) * (getwidth() - 2 * xymargin) / maxx);
   point.sety(getheight() - xymargin - (getheight() - 2 * xymargin) * ylist.get(i) / maxy);
   mpointlist.add(point);
  }
 }

 /**
  * 设置坐标点移动的动画
  */
 private void setpointanimator() {
  for (int i = 0; i < mpointlist.size(); i++) {
   final chartpoint point = mpointlist.get(i);
   valueanimator anim;
   if (mlastpointlist != null && mlastpointlist.size() > 0) {
    anim = valueanimator.ofint(mlastpointlist.get(i).gety(), point.gety());
   } else {
    anim = valueanimator.ofint(getheight() - xymargin, point.gety());
   }
   anim.setduration(500);
   anim.addupdatelistener(new valueanimator.animatorupdatelistener() {
    @override
    public void onanimationupdate(valueanimator animation) {
     int value = (int) animation.getanimatedvalue();
     point.sety(value);
     invalidate();
    }
   });
   anim.start();
  }
  //储存坐标点集合
  mlastpointlist = mpointlist;
 }

7、重写ondraw()方法,绘制坐标轴、刻度,画点连线,注意坐标的计算:

 @override
 protected void ondraw(canvas canvas) {
  super.ondraw(canvas);
  if (mpointlist == null || mpointlist.size() == 0) {
   return;
  }

  mxypath.reset();
  mxypath.moveto(xymargin, 0);
  mxypath.lineto(xymargin, getheight() - xymargin);
  mxypath.lineto(getwidth(), getheight() - xymargin);
  canvas.drawpath(mxypath, mlinepaint);//画x、y坐标轴

  for (int i = 0; i < mpointlist.size(); i++) {
   //画x轴刻度线
   int x = xymargin + (i + 1) * (getwidth() - 2 * xymargin) / mpointlist.size();
   canvas.drawline(x, getheight() - xymargin - graduatedlinelength, x, getheight() - xymargin, mlinepaint);
   //画y轴刻度线
   int y = getheight() - xymargin - (i + 1) * (getheight() - 2 * xymargin) / mpointlist.size();
   canvas.drawline(xymargin, y, xymargin + graduatedlinelength, y, mlinepaint);
   //画坐标轴刻度文本
   canvas.drawtext(string.valueof(mpointlist.get(i).getxdata()), x, getheight() - mtextpaint.gettextsize() / 4, mtextpaint);
   canvas.drawtext(string.valueof((i + 1) * 10), 0, y + mtextpaint.gettextsize() / 2, mtextpaint);
  }
  //画连接线
  for (int i = 0; i < mpointlist.size(); i++) {
   if (i != mpointlist.size() - 1) {
    chartpoint lastp = mpointlist.get(i);
    chartpoint nextp = mpointlist.get(i + 1);
    canvas.drawline(lastp.getx(), lastp.gety(), nextp.getx(), nextp.gety(), mlinepaint);
   }
  }
  //画坐标点
  for (int i = 0; i < mpointlist.size(); i++) {
   chartpoint point = mpointlist.get(i);
   canvas.drawcircle(point.getx(), point.gety(), mpointradius, mpointpaint);
   canvas.drawcircle(point.getx(), point.gety(), mpointradius, mpointcirclepaint);
  }
 }

8、设置坐标点点击事件:

 private onpointclicklistener monpointclicklistener;

 /**
  * 坐标点点击监听
  */
 public interface onpointclicklistener {
  /**
   * @param index 当前坐标点在数据集中的下标
   * @param point 当前坐标点对象
   */
  void onpointclick(int index, chartpoint point);
 }

 public void setonpointclicklistener(onpointclicklistener onpointclicklistener) {
  monpointclicklistener = onpointclicklistener;
 }

9、重写ontouchevent()方法,判断当前点击的点是不是在坐标点范围内:

 @override
 public boolean ontouchevent(motionevent event) {
  switch (event.getaction()) {
   case motionevent.action_down:
    //判断当前点击的点是否在坐标点范围内
    int curx = (int) event.getx();
    int cury = (int) event.gety();
    for (int i = 0; i < mpointlist.size(); i++) {
     chartpoint point = mpointlist.get(i);
     double d1 = math.pow(curx - point.getx(), 2);
     double d2 = math.pow(cury - point.gety(), 2);
     //√ ̄(curx - cx)² + (cury - cy)² < r
     if (math.sqrt(d1 + d2) < mpointradius + 10) {//为了方便点击,把坐标点范围增大了10像素
      if (monpointclicklistener != null) {
       monpointclicklistener.onpointclick(i, point);
      }
     }
    }
    break;
  }
  return super.ontouchevent(event);
 }

10、在activity_main.xml布局文件中使用该view:

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:lcv="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:gravity="center_horizontal"
 android:orientation="vertical"
 tools:context=".mainactivity">

 <com.monkey.linechartview.linechartview
  android:id="@+id/chartview"
  android:layout_width="250dp"
  android:layout_height="250dp"
  android:layout_margintop="@dimen/activity_vertical_margin"
  lcv:linecolor="#8e29fa"
  lcv:pointcolor="#ff5100"
  lcv:textcolor="#000000" />

 <button
  android:id="@+id/btn"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_margintop="@dimen/activity_vertical_margin"
  android:text="set data"
  android:textallcaps="false" />
</linearlayout>

11、在mainactivity.java中传入数据集合,并设置坐标点点击监听:

 btn.setonclicklistener(new view.onclicklistener() {
  @override
  public void onclick(view v) {
   list<integer> xlist = new arraylist<>();
   list<integer> ylist = new arraylist<>();
   for (int i = 0; i < 7; i++) {
    xlist.add(i + 1);
    int y = (int) (math.random() * 70 + 1);
    ylist.add(y);
   }
   chartview.setdatalist(xlist, ylist);
  }
 });


 chartview.setonpointclicklistener(new linechartview.onpointclicklistener() {
  @override
  public void onpointclick(int position, chartpoint point) {
   tv.settext("position:" + position + "\nx:" + point.getxdata() + "\ny:" + point.getydata());
  }
});

致此大致步骤完成了,发现和步骤差不多。。代码已上传github:
https://github.com/monkeymushroom/linechartview/tree/master

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