Android自定义控件-折线图,android折线,刚开始看到这个效果图,我
Android自定义控件-折线图,android折线,刚开始看到这个效果图,我
好长时间没有更新博客了,终于可以抽出时间写点东西了,写点什么呢?最近在qq群里边有人问,下边的这个控件怎么画?如下图所示:图可以左右拖动,直到显示完全为止。刚开始看到这个效果图,我也想了一下总共分为以下几个步骤:
(1)坐标轴的绘画,并绘画坐标轴上的坐标值
(2)绘画坐标上的点,并将其串联起来
(3)最后进行封闭图形的填充
(4)事件的拖动重绘
1、首先定义自定义属性文件,并确定坐标轴的颜色,宽度,坐标文字的大小,线的颜色等
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LineChart"> <attr name="xylinecolor" format="color" ></attr> <attr name="xylinewidth" format="dimension"></attr> <attr name="xytextcolor" format="color"></attr> <attr name="xytextsize" format="dimension"></attr> <attr name="linecolor" format="color"></attr> <attr name="interval" format="dimension"></attr> <attr name="bgcolor" format="color"></attr> </declare-styleable> </resources>
2、主布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:ypm = "http://schemas.android.com/apk/res/com.ypm.linechartdemo" android:layout_width="match_parent" android:layout_height="match_parent" > <com.ypm.linechartdemo.LineChart android:id="@+id/id_linechart" android:layout_width="match_parent" android:layout_height="match_parent" ypm:xylinecolor="@color/xylinecolor" ypm:xylinewidth="@dimen/xylinewidth" ypm:xytextsize = "@dimen/xytextsize" ypm:linecolor="@color/linecolor" > </com.ypm.linechartdemo.LineChart> </RelativeLayout>
3、接下来就是自定义LineChart控件,首先定义一些列的变量值如下:
/** * 坐标轴的颜色 */ private int xyColor; /** * 坐标轴的宽度 */ private int xyWidth; /** * 坐标轴文字的颜色 */ private int xyTextColor; /** * 坐标轴文字的大小 */ private int xyTextSize; /** * 坐标轴的之间的间距 */ private int interval; /** * 折线的颜色 */ private int lineColor; /** * 背景颜色 */ private int bgColor; /** * 原点坐标最大x */ private int ori_x; /** * 第一个点的坐标 */ private int first_x; /** * 第一个点的坐标最小x,和最大x坐标 */ private int ori_min_x,ori_max_x; /** * 原点坐标y */ private int ori_y; /** * x的刻度值长度 默认值40 */ private int xScale = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 80, getResources() .getDisplayMetrics()); /** * y的刻度值长度 */ private int yScale = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 55, getResources() .getDisplayMetrics()); /** * x刻度 */ private String[] xLabels; /** * y刻度 */ private String[] yLabels; /** * x坐标轴中最远的坐标值 */ private int maxX_X, maxX_Y; /** * y坐标轴的最远坐标值 */ private int minY_X, minY_Y; /** * x轴最远的坐标轴 */ private int x_last_x, x_last_y; /** * y轴最远的坐标值 */ private int y_last_x, y_last_y; private double[] dataValues; /** * 滑动时候,上次手指的x坐标 */ private float startX;
4、读取属性文件上的值
public LineChart (Context context , AttributeSet attrs , int defStyle) { super(context, attrs, defStyle); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LineChart); int count = array.getIndexCount(); for (int i = 0; i < count; i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.LineChart_xylinecolor: xyColor = array.getColor(attr, Color.GRAY); break; case R.styleable.LineChart_xylinewidth: xyWidth = (int) array.getDimension(attr, 5); break; case R.styleable.LineChart_xytextcolor: xyTextColor = array.getColor(attr, Color.BLACK); break; case R.styleable.LineChart_xytextsize: xyTextSize = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics())); break; case R.styleable.LineChart_linecolor: lineColor = array.getColor(attr, Color.GRAY); break; case R.styleable.LineChart_bgcolor: bgColor = array.getColor(attr, Color.WHITE); break; case R.styleable.LineChart_interval: interval = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 100, getResources().getDisplayMetrics())); break; default: break; } } array.recycle(); }
5、初始化相应的坐标值,在onMeasure中
主要进行原点,第一个点坐标,以及x最大值的相关的计算
int width = getWidth(); int height = getHeight(); ori_x = 40; ori_y = height - 40; maxX_X = width - 50; minY_Y = 50; ori_min_x = width - 50 -40 - dataValues.length * xScale; first_x = ori_x; ori_max_x = first_x;
6、绘画坐标轴
/** * * 功能描述:绘画坐标轴 * * @param canvas * @版本 1.0 * @创建者 ypm * @创建时间 2015-8-24 上午10:39:59 * @版权所有 * @修改者 ypm * @修改时间 2015-8-24 上午10:39:59 修改描述 */ private void drawXYLine(Canvas canvas) { Paint paint = new Paint(); paint.setColor(xyColor); paint.setAntiAlias(true); paint.setStrokeWidth(xyWidth); paint.setTextSize(xyTextSize); // 绘画x轴 int max = first_x + (xLabels.length-1) * xScale + 50; if (max > maxX_X) { max = getMeasuredWidth(); } x_last_x = max; x_last_y = ori_y; canvas.drawLine(first_x, ori_y, max, ori_y, paint); // 绘画y轴 int min = ori_y - (yLabels.length - 1) * yScale - 50; if (min < minY_Y) { min = minY_Y; } y_last_x = first_x; y_last_y = min; canvas.drawLine(first_x, ori_y, first_x, min, paint); // 绘画x轴的刻度 drawXLablePoints(canvas, paint); // 绘画y轴的刻度 drawYLablePoints(canvas, paint); }
7、绘画折线图
这里运用到了多边形的绘画,通过Path进行绘画,并使用了多边形的填充,以及xfermode的相关知识,可以查相关的api进行了解
private void drawDataLine(Canvas canvas) { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); // paint.setStyle(Paint.Style.FILL); paint.setColor(xyColor); Path path = new Path(); for (int i = 0; i < dataValues.length; i++) { int x = first_x + xScale * i; if (i == 0) { path.moveTo(x, getYValue(dataValues[i])); } else { path.lineTo(x, getYValue(dataValues[i])); } canvas.drawCircle(x, getYValue(dataValues[i]), xyWidth, paint); } path.lineTo(first_x + xScale * (dataValues.length - 1), ori_y); path.lineTo(first_x, ori_y); path.close(); paint.setStrokeWidth(5); // paint.setColor(Color.parseColor("#D7FFEE")); paint.setColor(Color.parseColor("#A23400")); paint.setAlpha(100); // 画折线 canvas.drawPath(path, paint); paint.setStyle(Paint.Style.FILL); paint.setColor(Color.RED); canvas.clipPath(path); // 将折线超出x轴坐标的部分截取掉 paint.setStyle(Paint.Style.FILL); paint.setColor(bgColor); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); RectF rectF = new RectF(0, 0, x_last_x, ori_y); canvas.drawRect(rectF, paint); } private float getYValue(double value) { return (float) (ori_y - value / 50 * yScale); }
8、事件的拖动,重写onTouchEvent方法,
这里主要的逻辑就是:
(1)当手机的宽度小于坐标值的最大值的时候,就禁止拖动
(2)如果超过手机的宽度的时候,就通过裁剪功能将渲染的图像就行裁剪,拖动的时候,将没有显示的部分进行显示
主要分为3块:
第一块:第一个点的坐标+拖动的距离和第一点坐标的最大值进行比较
第二块:第一个点的坐标+拖动的距离和第一点坐标的最小值进行比较
第三块:是在第一,二块之间的
@Override public boolean onTouchEvent(MotionEvent event) { if ((dataValues.length * xScale + 50 + ori_x) < maxX_X- ori_x) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); break; case MotionEvent.ACTION_MOVE: float distance = event.getX() - startX; // Log.v("tagtag", "startX="+startX+",distance="+distance); startX = event.getX(); if(first_x+distance > ori_max_x) { Log.v("tagtag", "111"); first_x = ori_max_x; } else if(first_x+distance<ori_min_x) { Log.v("tagtag", "222"); first_x = ori_min_x; } else { Log.v("tagtag", "333"); first_x = (int)(first_x + distance); } invalidate(); break; } return true; }
9、最终效果图,如下
总结:
自定义控件的编写步骤可以分为以下几个步骤:
(1)编写attr.xml文件
(2)在layout布局文件中引用,同时引用命名空间
(3)在自定义控件中进行读取(构造方法拿到attr.xml文件值)
(4)覆写onMeasure()方法
(5)覆写onLayout(),onDraw()方法
具体用到哪几个方法,具体情况具体分析,关键点还是在算法上边。
参考文章:
http://blog.csdn.net/yifei1989/article/details/29891211
转载请注明来源于http://www.cnblogs.com/persist-confident 谢谢合作!!!!!!!!
用户评论