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

继续练习自定义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

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

(0)

相关推荐

  • Android自定义View实现广告信息上下滚动效果

    先看看效果: 实现代码: public class ScrollBanner extends LinearLayout { private TextView mBannerTV1; private TextView mBannerTV2; private Handler handler; private boolean isShow; private int startY1, endY1, startY2, endY2; private Runnable runnable; private Li

  • Android自定义View实现多片叶子旋转滑动(五)

    上一篇<Android 自定义View(四) 叶子飘动+旋转效果>实现了单片叶子的滑动及旋转,下面实现多片叶子的滑动旋转功能 实现思路比较简单,就是添加一个叶子Leaf类,储存每片叶子的信息, 然后随机产生叶子的坐标及旋转角度,最后实时获取每片叶子信息,添加到画布中 1.Leaf.java 叶子类 private class Leaf { // 叶子的坐标 float x, y; // 旋转角度 int rotateAngle; // 起始时间(ms) long startTime; } 2.

  • Android自定义View叶子旋转完整版(六)

    上一篇实现多叶子飘动旋转,今天完成最后的功能. 1.添加右侧旋转枫叶 2.添加滑动条效果,显示百分比 3.修复叶子飘出边框问题 1.添加右侧旋转叶子 Bitmap turnBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.fengshan, null)).getBitmap(); int turnLeafAngle = 0; private void setTurnLeaf(Canvas canvas) { Matrix m

  • Android自定义View实现叶子飘动旋转效果(四)

    上一篇实现了叶子飘动功能,<Android自定义叶子飘动> 现在实现旋转效果 要实现这个效果,要在之前的功能上添加2个功能 1.通过matrix.postTranslate(int x, int y)在添加在Y轴上滑动 2.通过matrix.postRotate(float degrees, float px, float py)实现叶子旋转 代码实现 1.获取Y坐标 private float getMatrixY() { float w = (float) ((float) 2 * Mat

  • Android自定义View实现飘动的叶子效果(三)

    上一篇对自定义View及一些方法有所了解,下面做一个简单的叶子飘动的例子 主要技术点 1.添加背景图片canvas.drawBitmap() 2.Matrix动画类 3.Matrix添加到画布上 步骤 1.添加黄色背景颜色 public LeafView(Context context, AttributeSet attrs) { super(context, attrs); bgPaint = new Paint(); bgPaint.setColor(mResources.getColor(

  • Android自定义View绘制的方法及过程(二)

    上一篇<Android 自定义View(一) Paint.Rect.Canvas介绍>讲了最基础的如何自定义一个View,以及View用到的一些工具类.下面讲下View绘制的方法及过程 public class MyView extends View { private String TAG = "--------MyView"; private int width, height; public MyView(Context context, AttributeSet a

  • Android自定义View圆形进度条控件(三)

    继续练习自定义View,这次带来的圆形进度条控件与之前的圆形百分比控件大同小异,这次涉及到了渐变渲染以及画布旋转等知识点,效果如下: 虽然步骤类似,但是我还是要写,毕竟基础的东西就是要多练 1.在res/values文件夹下新建attrs.xml文件,编写自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="Circ

  • Android自定义View设定到FrameLayout布局中实现多组件显示的方法 分享

    如果想在自定义的View上面显示Button 等View组件需要完成如下任务 1.在自定义View的类中覆盖父类的构造(注意是2个参数的) 复制代码 代码如下: public class MyView2 extends View{ public MyView2(Context context,AttributeSet att) {super(context,att); } public void onDraw(Canvas c) { // 这里绘制你要的内容 } } 2.定义布局文件 复制代码

  • Android自定义View中Paint、Rect、Canvas介绍(一)

    自定义View对于新手而言貌似是一个很复杂的东西.格式,各函数的意义.对于大神经常忘记各函数及一些参数的具体写法及意义,刚好在做一个风车效果,把过程及遇到的问题都写下来 1.如何自定义一个View public class LeafView extends View { private String TAG = "--------LeafView"; public LeafView(Context context, AttributeSet attrs) { super(context

  • Android 自定义View的使用介绍

    在项目开发中,可能系统自带的一些widget不能满足我们的需求,这时就需要自定义View. 通过查看系统中的常用widget如Button,TextView,EditText,他们都继承自View,所以我们在继承自定义View的时候也自然的需要继承View.1.首先新建一个类LView继承自View 复制代码 代码如下: public class LView extends View { private Paint paint; public LView(Context context) {  

随机推荐