欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Android自定义控件-折线图,android折线,刚开始看到这个效果图,我

来源: 开发者 投稿于  被查看 27677 次 评论:55

Android自定义控件-折线图,android折线,刚开始看到这个效果图,我


好长时间没有更新博客了,终于可以抽出时间写点东西了,写点什么呢?最近在qq群里边有人问,下边的这个控件怎么画?如下图所示:图可以左右拖动,直到显示完全为止。刚开始看到这个效果图,我也想了一下总共分为以下几个步骤:

(1)坐标轴的绘画,并绘画坐标轴上的坐标值

(2)绘画坐标上的点,并将其串联起来

(3)最后进行封闭图形的填充

(4)事件的拖动重绘

251013346396442.png

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、最终效果图,如下

251429156726980.gif

 总结:

自定义控件的编写步骤可以分为以下几个步骤:

(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 谢谢合作!!!!!!!!

用户评论