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

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

时间: 2017-11-29

Android自定义View简易折线图控件(二)的相关文章

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

下面就是结果图(每种状态用一个表情图片表示): 一.主页面的布局文件如下: <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实现折线图效果

下面就是结果图(每种状态用一个表情图片表示): 一.主页面的布局文件如下: <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示例(三)—滑动控件

MainActivity如下: package cc.testview4; import cc.testview4.SlideView.SwitchChangedListener; import android.app.Activity; import android.os.Bundle; /** * Demo描述: * 自定义滑动控件 * * 参考资料: * http://blog.csdn.net/lfdfhl/article/details/8195441 * * 备注说明: * 在Cop

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

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

android自定义view-Android在自定义View控制Activity里控件

问题描述 Android在自定义View控制Activity里控件 最近在学习Android,想做一个效果遇到了困难.我自定义了一个View,然后在View里设置触摸事件,点击一个出现一个按钮, 再点击一下按钮消失.我不知道怎么在自定义View里添加Button,就放在了布局了.但是用在自定义View设置的点击事件来控 制布局里的按钮?跪谢跪谢. 解决方案 以下是在Activity里的操作 private Button bt1;//你的按钮控件 View myView=View.inflate(

图片-安卓 带阴影的折线图控件 如何绘制阴影

问题描述 安卓 带阴影的折线图控件 如何绘制阴影 如图 这种折线下带有阴影的控件,阴影是怎么绘制上去的 解决方案 自问自答............... 我觉得可以先用一个渐变色把背景填充了 然后再画一个不规则的图 里面填充上白色 然后再画其他的线 解决方案二: 使用线性渐变 用Path绘制不过则的图形,然后把线性渐变设置给画笔,这样就实现了上述效果 解决方案三: 你好,你的这个折线demo能给我看看吗? 解决方案四: 你好,你的这个折线demo能给我看看吗? 解决方案五: 好的,我最近整理一下

Android重写View实现全新的控件_Android

通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 本文来讨论最难的一种自定义控件形式,重写View来实现全新的控件. 首先,我们要明白在什么样的情况下,需要重写View来实现一种全新的控件,一般当我们遇到了原生控件无法满足我们现有的需求的时候,我们此时就可以考虑创建一个全新的View来实现我们所需要的功能.创建一个全新View实现自定义控件,无非分成这么几步: Ⅰ.在

Android重写View实现全新的控件

通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 本文来讨论最难的一种自定义控件形式,重写View来实现全新的控件. 首先,我们要明白在什么样的情况下,需要重写View来实现一种全新的控件,一般当我们遇到了原生控件无法满足我们现有的需求的时候,我们此时就可以考虑创建一个全新的View来实现我们所需要的功能.创建一个全新View实现自定义控件,无非分成这么几步: Ⅰ.在

Android自定义View之酷炫圆环(二)_Android

先看下最终的效果 静态: 动态: 一.开始实现 新建一个DoughnutProgress继承View public class DoughnutProgress extends View { } 先给出一些常量.变量以及公共方法的代码,方便理解后面的代码     private static final int DEFAULT_MIN_WIDTH = 400; //View默认最小宽度 private static final int RED = 230, GREEN = 85, BLUE =