自定义曲线图

自定义放大缩小曲线图

前言

作为五月的最后一个周末,理论上我应该写一个关于相机的文章,但是因为太忙了,没时间研究,还是把之前写的自定义View拿来充一下数吧,下个月发相机文章,一定.

效果

支持左右滑动和双指缩放(缩放需要使用真机测试)

代码

package com.lwyy.wheel.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.lwyy.wheel.R;
import com.lwyy.wheel.model.ActivityDetailPoint;
import com.lwyy.wheel.util.TimeUtil;
import com.lwyy.wheel.util.ToolUtil;
import com.lwyy.wheel.util.UnitUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BezierCurve extends View {
    private static final int NONE = 0;
    private static final int DRAG = 1;
    private static final int ZOOM = 2;
    private float typeRateMax = 0.6f;
    private Paint mPaint;
    private Paint mLinePaint;
    private Paint mTimeTextPaint;
    private float prevx, lastcurx, curx, cury, curd, prevd;
    private int mode = NONE;
    private long min_time, max_time, touch_time;
    private long total_time, static_min, static_max;
    private List<Point> pointList = new ArrayList<>();
    private List<ActivityDetailPoint> dataLists = new ArrayList<>();
    private float rate;
    private int midx;
    private int linex;
    private long MAX_TIME, MIN_TIME;
    private Path path = new Path();
    private Rect layoutRect = new Rect();
    private int showPadding = 0;
    private Context context;
    private float keyBase = 0;
    private int lineNumY = 5;
    private int showTypeYValueArray = 44;
    private float maxYPointValue;
    private int horizontalNum = 13;
    private int distanceW, distanceH;
    private int lineStartX;
    private String[] dayStrArray = {"00:00", "02:00", "04:00", "06:00", "08:00", "10:00",
            "12:00", "14:00", "16:00", "18:00", "20:00", "22:00", "24:00"};
    private int minValue = 0;
    private int touchIdArray = R.mipmap.activity_detail_blue;
    private int lineIdArray = R.color.colorActivityDetailHeartRate;
    private Paint mValueLinePaint;
    private Paint mTextRectPaint;

    public BezierCurve(Context context) {
        this(context, null);
    }

    public BezierCurve(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BezierCurve(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        initPaints();
    }

    private void initPaints() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint = new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setStrokeWidth(1);
        mLinePaint.setColor(getResources().getColor(R.color.colorRecyclerItemDividerBg));

        mTimeTextPaint = new Paint();
        mTimeTextPaint.setAntiAlias(true);
        mTimeTextPaint.setTextSize(UnitUtil.dp2px(context, 7));
        mTimeTextPaint.setColor(getResources().getColor(R.color.colorTextCustomDeep));

        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(5);
        mPaint.setTextSize(UnitUtil.dp2px(context, 10));
        mPaint.setTextAlign(Paint.Align.LEFT);
        linex = -1;

        mTextRectPaint = new Paint();
        mTextRectPaint.setAntiAlias(true);
        mTextRectPaint.setTextAlign(Paint.Align.CENTER);
        mTextRectPaint.setTextSize(UnitUtil.dp2px(context, 10));
        mTextRectPaint.setColor(getResources().getColor(R.color.colorTextSelect));

        mValueLinePaint = new Paint();
        mValueLinePaint.setAntiAlias(true);
    }

    public void setData(List<ActivityDetailPoint> datasList, int minTime, int maxTime) {
        refreshData(datasList, true);
        this.MAX_TIME = maxTime;
        this.MIN_TIME = minTime;
        init();
    }

    public void refreshData(List<ActivityDetailPoint> datasList, boolean isAutoRefresh) {
        if (null == datasList)
            return;
        if (this.dataLists.size() > 0)
            this.dataLists.clear();
        this.dataLists.addAll(datasList);
        List<Long> xValueList = new ArrayList<>();
        List<Float> yValueList = new ArrayList<>();
        for (ActivityDetailPoint point : datasList) {
            yValueList.add(point.avg);
            xValueList.add(point.timeStamp);
        }
        this.maxYPointValue = Collections.max(yValueList);
        this.minValue = 40;
        this.keyBase = 45;
        if (!isAutoRefresh) {
            convertData2Point();
            invalidate();
        }
    }

    private void init() {
        max_time = MAX_TIME;
        min_time = MIN_TIME;
        total_time = max_time - min_time;
        static_min = min_time;
        static_max = max_time;
        convertData2Point();
        invalidate();
    }

    private void convertData2Point() {
        if (pointList.size() > 0)
            pointList.clear();
        float rate1 = returnRate();
        float keyValueY = distanceH / (lineNumY + 1f);
        if (null != dataLists && dataLists.size() > 0) {
            for (ActivityDetailPoint heartRateData : dataLists) {
                float x = (rate1 * (heartRateData.timeStamp - min_time)) + lineStartX;
                float y = distanceH - heartRateData.avg / 1f / keyBase * keyValueY - keyValueY / 2 + keyValueY * minValue / keyBase;
                pointList.add(new Point(x, y));
            }
        }
    }

    //控制x坐标之间的间隔
    private float returnRate() {
        float dex = (distanceW + lineStartX * 9f) / (max_time - min_time);
        return dex;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                mode = DRAG;
                prevx = event.getX();

                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mode == DRAG) {
                    lastcurx = curx;
                    curx = event.getX();
                    long temp_min_time = (int) ((float) (total_time / distanceW) * (int) (prevx - curx) / 2) + static_min;
                    temp_min_time = temp_min_time < MIN_TIME ? MIN_TIME : temp_min_time;
                    long temp_max_time = temp_min_time + total_time;
                    if (max_time > (MAX_TIME + total_time * typeRateMax) && curx < lastcurx && curx < prevx && lastcurx > 0) {
                        return false;
                    }

                    long tempMinTime = min_time;
                    long tempMaxTime = max_time;
                    min_time = temp_min_time;
                    max_time = temp_max_time;
                    if (Math.abs((max_time - min_time)) < 3600) {
                        min_time = tempMinTime;
                        max_time = tempMaxTime;
                        break;
                    }
                } else if (mode == ZOOM) {
                    curd = spacing(event);
                    rate = prevd / curd;
                    rate = rate > 10f ? 10f : rate;
                    rate = rate < 0.1f ? 0.1f : rate;
                    if (((static_max - static_min) * rate) < 3600) {                                // 最大值与最小值之差小于1小时,不再缩放了
                        break;
                    }
                    changeTimeRange();
                }
                convertData2Point();
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                static_max = max_time;
                static_min = min_time;
                lastcurx = 0;
                linex = getClosestValueIndex(prevx);
                cury = event.getY();
                break;
            case MotionEvent.ACTION_POINTER_UP:
                mode = NONE;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                linex = -1;
                midx = (int) (event.getX(0) + event.getX(1)) / 2;
                touch_time = x2Timestamp(midx);
                mode = ZOOM;
                if (event.getPointerCount() > 1) {
                    prevd = spacing(event);
                }
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawXAxis(canvas);
        drawYAxis(canvas);
        drawBeizer(canvas);
        drawTouch(canvas);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int resultWidth = sizeWidth;
        int resultHeight = sizeHeight;
        // 考虑内边距对尺寸的影响
        resultWidth += getPaddingLeft() + getPaddingRight();
        resultHeight += getPaddingTop() + getPaddingBottom();
        // 考虑父容器对尺寸的影响
        distanceW = resultWidth = resolveMeasure(sizeWidth, resultWidth);
        distanceH = resultHeight = resolveMeasure(sizeHeight, resultHeight);
        setMeasuredDimension(resultWidth, resultHeight);
        showPadding = distanceW / (horizontalNum + 2);
        lineStartX = (distanceW - showPadding * (horizontalNum - 1)) / 2;
        layoutRect = new Rect(0, 0, distanceW, showPadding);
        init();
    }

    /**
     * 根据传入的值进行测量
     */
    public int resolveMeasure(int measureSpec, int defaultSize) {
        int result = defaultSize;
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (MeasureSpec.getMode(measureSpec)) {
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = Math.min(specSize, defaultSize);
                break;
        }
        return result;
    }

    // 画x轴
    private void drawXAxis(Canvas canvas) {
        float rate1 = returnRate();
        float y = layoutRect.bottom - 0.5f * getTextHeight();
        mTimeTextPaint.setTextAlign(Paint.Align.CENTER);
        int showNum = dayStrArray.length;
        long timeStamp;
        int dex = 2;
        for (int i = 0; i < showNum; i++) {
            timeStamp = 60 * 60 * i * dex + MIN_TIME;
            if (timeStamp >= min_time) {
                float x = (rate1 * (timeStamp - min_time)) + lineStartX;
                canvas.drawText(dayStrArray[i], x, y, mTimeTextPaint);
            }
        }
    }

    // 画Y轴及线
    private void drawYAxis(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(getResources().getColor(R.color.colorTextCustomDeep));
        float keyValueY = distanceH / (lineNumY + 1), yValue;
        String showStr;
        for (int i = 0; i < lineNumY; i++) {
            yValue = keyValueY * (i + 1) + keyValueY;
            showStr = ToolUtil.returnFormatNum(String.valueOf(keyBase * (lineNumY - i - 1) + minValue));
            if (i < lineNumY - 1) {
                canvas.drawLine(lineStartX - showPadding, yValue, distanceW - lineStartX + showPadding, yValue, mLinePaint);
            }
            canvas.drawText(showStr, lineStartX - showPadding, yValue + 0.5f * getTextHeight() - keyValueY / 2, mPaint);
        }
    }

    private void drawBeizer(Canvas canvas) {
        if (pointList != null && pointList.size() > 0) {
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(8);
            mPaint.setColor(getResources().getColor(lineIdArray));
            Point startp, endp;
            for (int i = 0; i < pointList.size() - 1; i++) {
                startp = pointList.get(i);
                endp = pointList.get(i + 1);
                float wt = (startp.px + endp.px) / 2;
                path.reset();
                path.moveTo(startp.px, startp.py);
                path.cubicTo(wt, startp.py, wt, endp.py, endp.px, endp.py);
                canvas.drawPath(path, mPaint);
            }
        }
    }

    private void drawTouch(Canvas canvas) {
        if (pointList.size() == 1) {
            linex = 0;
            cury = pointList.get(linex).py - 30;
        }

        if (pointList.size() > 0 && linex >= 0 && linex < pointList.size()) {
            String hAndMArray[] = TimeUtil.timeStampToString(dataLists.get(linex).timeStamp, TimeUtil.SDF_TYPE_HM).split(":");
            if (null != hAndMArray && hAndMArray.length > 1 && Math.abs(pointList.get(linex).py - cury) < 40) {//0 点
                float x = pointList.get(linex).px - getTextHeight();
                float y = pointList.get(linex).py - getTextHeight();
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), touchIdArray);
                int height = bitmap.getHeight();
                canvas.drawBitmap(bitmap, x, y, mValueLinePaint);
                canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.activity_detail_click_value_bg), x - height, y - 2 * height, mValueLinePaint);
                canvas.drawText(ToolUtil.returnFormatNum(String.valueOf(dataLists.get(linex).avg)), x + height / 2, y - height, mTextRectPaint);
            }
        }
    }

    // 文字的高度
    private float getTextHeight() {
        Paint.FontMetrics fm = mTimeTextPaint.getFontMetrics();
        return (float) Math.ceil(fm.descent - fm.ascent) - 2;
    }

    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    private void changeTimeRange() {
        min_time = (long) (touch_time - (touch_time - static_min) * rate);
        max_time = (long) (touch_time + (static_max - touch_time) * rate);
        if (min_time <= MIN_TIME)
            min_time = MIN_TIME;
        if (max_time >= MAX_TIME)
            max_time = MAX_TIME;
        total_time = max_time - min_time;
    }

    private int getClosestValueIndex(float x) {
        int res = 0;
        if (pointList.size() > 0) {
            float dif = distanceW / pointList.size(), tmp;
            for (int i = 0; i < pointList.size(); i++) {
                tmp = Math.abs((x - pointList.get(i).px));
                if (tmp < dif) {
                    dif = tmp;
                    res = i;
                }
            }
        }
        return res;
    }

    private long x2Timestamp(float x) {
        return (long) ((max_time - min_time) * (x / distanceW) + min_time);
    }

    private class Point {
        float px, py;

        Point(float px, float py) {
            this.px = px;
            this.py = py;
        }

        @Override
        public String toString() {
            return "px:" + px + ", py:" + py;
        }
    }
}

地址

传git失败,有时间再传吧

猜你喜欢

转载自blog.csdn.net/liuwanyouyue/article/details/80471939
今日推荐