自定义图表控件(坐标系)

来源:互联网 发布:dnf强行交易软件 编辑:程序博客网 时间:2024/04/30 15:43

话说就在昨天下午,本屌正在我们的大DDUP群里快乐的装逼,水友们正在聊妹子,无奈咱没有妹子,正在黯然神伤之际,突然学校里的一个小学弟发来消息请帮忙做个图表控件,功能很简单,只需要动态显示就可以了,当时心想,这很简单,就立刻开始撸码,半小时搞定,但是……出了个错误,这个错误让我找了一个小时(主要也是脑袋进水),所以决定记下来,防止再犯。

先来个效果图:


主要代码如下:

public class MyLineView extends View {// x轴和y轴上面的刻度表private List<Float> x_list = new ArrayList<Float>();private List<Float> y_list = new ArrayList<Float>();// x轴和y轴上面的刻度个数private int ySize;private int xSize;// 手指触摸的点的集合以及存放点的横纵坐标的集合(为了测试方便直接使用了三个list)private List<PointF> point_list = new ArrayList<PointF>();private List<Integer> list1 = new ArrayList<Integer>();private List<Integer> list2 = new ArrayList<Integer>();// x轴每个最小刻度之间的距离private float degree_x;// y轴每个最小刻度之间的距离private float degree_y;// 控件的宽高以及paddingprivate int mWidth, mHeight;private int padding_top;private int padding_left;private int padding_bottom;private int padding_right;// 坐标轴原点的XY坐标private int zero_x, zero_y;// 最远的x坐标private int max_x;// 最高的y坐标private int max_y;// 画笔private Paint mPaint;public MyLineView(Context context) {this(context, null);// TODO Auto-generated constructor stub}public MyLineView(Context context, AttributeSet attrs) {this(context, attrs, 0);// TODO Auto-generated constructor stub}public MyLineView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stub// 构造方法中初始化画笔mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setTextSize(26);}/* * 初始化数据,获取宽高和padding,计算原点的坐标等,为画出XY轴做准备 */private void initData() {mWidth = getWidth();mHeight = getHeight();padding_top = getPaddingTop();padding_left = getPaddingLeft();padding_bottom = getPaddingBottom();padding_right = getPaddingRight();// 初始化原点的坐标,注意android中的坐标系是左上角为原点,和我们要显示的坐标系不同zero_x = padding_left + 50;// 为刻度值留出距离zero_y = mHeight - padding_bottom - 30;max_x = mWidth - padding_right;max_y = padding_top;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);// 以下代码在这里其实无卵用,只是想说明一点:直接继承View的自定义控件,需要自己处理当layoutParams是wrap_content的情况int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);if (widthSpecMode == MeasureSpec.AT_MOST&& heightSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(500, 500);} else if (widthSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(500, heightSpecSize);} else if (heightSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSpecSize, 500);}}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubaddPoint(event.getX(), event.getY());return true;}@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubsuper.onDraw(canvas);initData();drawXY(canvas);}/* * 画出坐标系以及网格和内容 * */private void drawXY(Canvas canvas) {mPaint.setStrokeWidth(1);mPaint.setColor(Color.BLACK);// x轴canvas.drawLine(zero_x, zero_y, max_x, zero_y, mPaint);// y轴canvas.drawLine(zero_x, zero_y, zero_x, max_y, mPaint);// 根据y轴上面的刻度画出网格以及刻度值ySize = y_list.size();if (ySize > 0) {// 每个最小刻度之间的距离,由于刻度不一定等分,所以这里使用了坐标轴的长度除以最大刻度来计算degree_y = (zero_y - max_y) / (y_list.get(ySize - 1));for (int i = 0; i < y_list.size(); i++) {String s = String.valueOf(y_list.get(i));mPaint.setColor(Color.GREEN);canvas.drawLine(zero_x, zero_y - degree_y * (y_list.get(i)),max_x, zero_y - degree_y * (y_list.get(i)), mPaint);mPaint.setColor(Color.BLACK);canvas.drawText(s, padding_left,zero_y - degree_y * (y_list.get(i)), mPaint);}}// 根据x轴上面的刻度画网格以及刻度值xSize = x_list.size();if (xSize > 0) {// 每个最小刻度之间的距离degree_x = (max_x - zero_x) / (x_list.get(xSize - 1));for (int i = 0; i < x_list.size(); i++) {String s = String.valueOf(x_list.get(i));mPaint.setColor(Color.GREEN);canvas.drawLine(zero_x + (degree_x * (x_list.get(i))), zero_y,zero_x + (degree_x * (x_list.get(i))), max_y, mPaint);mPaint.setColor(Color.BLACK);canvas.drawText(s, zero_x + (degree_x * (x_list.get(i))),zero_y + 30, mPaint);}}if (point_list.size() > 0) {for (int i = 1; i < point_list.size(); i++) {mPaint.setColor(Color.RED);mPaint.setStrokeWidth(5);canvas.drawPoint(point_list.get(i - 1).x,point_list.get(i - 1).y, mPaint);String str = "(" + list1.get(i - 1) + "," + list2.get(i - 1)+ ")";canvas.drawText(str, point_list.get(i - 1).x + 10,point_list.get(i - 1).y - 10, mPaint);if (point_list.size() > 1) {mPaint.setColor(Color.BLUE);mPaint.setStrokeWidth(1);canvas.drawLine(point_list.get(i - 1).x,point_list.get(i - 1).y, point_list.get(i).x,point_list.get(i).y, mPaint);}}}}/* * 设置控件的XY轴刻度 */public void setDegree(List<Float> x, List<Float> y) {x_list = x;y_list = y;}/* * 将点添加进集合中并画出,参数为点的x和y坐标值 */public void addPoint(float x, float y) {// 注意android坐标系和控件要显示的坐标系是不同的,所以这里要显示的数字需要做出相应的转换float point_x = (x - zero_x) * x_list.get(xSize - 1) / (max_x - zero_x);float point_y = y_list.get(ySize - 1) - (y - max_y)* y_list.get(ySize - 1) / (zero_y - max_y);PointF point = new PointF(x, y);point_list.add(point);list1.add(floatToInt(point_x));list2.add(floatToInt(point_y));postInvalidate();}/* * 将float转换成int,四舍五入,或者可以直接使用Math.round()方法来四舍五入(脑袋进水了,一开始居然没想到) */public int floatToInt(float f) {int i = 0;if (f > 0) {i = (int) (f * 10 + 5) / 10;} else if (f < 0) {i = (int) (f * 10 - 5) / 10;} else {i = 0;}return i;}}

代码不多,唯一要注意的就是android中的坐标系是左上角为原点,而要显示的坐标系却是左下角为原点

总结几点:

1、 注意精度,精度,精度,重要的事要说三遍,自定义view的时候,如果用到了数值之间的乘除,一定要注意精度

PS:本屌在这里栽了个好大的跟头,在算degree的时候,一开始没注意声明成了int类型的变量,结果本来该是15.8的,强转之后直接变成了15,导致之后每次算的值老是差那么一点,蛋疼ing

2、 如果自定义控件是直接继承view的,那么在onmeasure方法中,需要自己处理当layoutParams是wrap_content的时候,不然就会导致控件在使用的时候wrap_content和match_parent效果一样。

3、同2,如果直接继承view,那么还需要自己处理padding,margin是由父控件处理的,所以自定义的时候不用处理,但是padding是需要自理的。

4、在画布中写文字的时候(canvas.drawText方法),要注意,坐标是从第一个字的左下角开始的,所以要考虑字的高度和宽度。


欢迎评论,如有错误还请不吝指正


0 0