54.自定义View练习(二)简易折线图控件

来源:互联网 发布:linux系统查看cpu主频 编辑:程序博客网 时间:2024/05/14 18:58

转载请注明出处 http://blog.csdn.net/qq_31715429/article/details/54022507
本文出自:猴菇先生的博客

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

2 0
原创粉丝点击