View系列:View项目:绘制曲线

本文详细描述了一个自定义的SpringInterpolator应用,展示了如何通过计算和绘制轨迹曲线、坐标轴、网格、数据点和下标,实时显示插值器的运动过程。关键步骤包括数据点生成、坐标尺寸计算和视觉元素的绘制。
摘要由CSDN通过智能技术生成

1:如图所示View

 

预置条件:

1.1 先通过 查找器Interpolator.getInterpolator(x) 得到坐标x ,坐标y的集合点

for (float x = 0; x <= 1; x += 1.0f / FRAME) {
     float y = mInterpolator.getInterpolation(x);

     PointF pointF = new PointF(x, y);
     dataList.add(pointF);
}




public class SpringInterpolator implements TimeInterpolator {

    /**
     * 参数 x,即为 x轴的值
     * 返回值 便是 y 轴的值
     */
    @Override
    public float getInterpolation(float x) {
        float factor = 0.4f;
        return (float) (Math.pow(2, -10 * x) * Math.sin((x - factor / 4) * (2 * Math.PI) / 
         factor) + 1);
    }
}

1.2 :计算每个坐标格子宽 

    /**
     * 计算每个格子的大小
     */
    private void calculateEachItemWidth() {

        // 获取 y正半轴 的分割个数
        mPositiveCount = (int) Math.abs(Math.ceil(mMaxPoint.y / GRID_INTERVAL_LENGTH));
        // 获取 y负半轴 的分割个数
        mNegativeCount = (int) Math.abs(Math.floor(mMinPoint.y / GRID_INTERVAL_LENGTH));

        // 计算需要分割的数量,最少十个
        int intervalCount = mPositiveCount + mNegativeCount;
        intervalCount = Math.max(intervalCount, GRID_INTERVAL_COUNT);

        mEachItemWidth = mWidth / intervalCount;

    }

2: 效果分析

2.1 :画轨迹曲线

    /**
     * 构建数据路径
     */
    private void buildDataPath() {
        mDataPath.reset();
        float width = mEachItemWidth * GRID_INTERVAL_COUNT;
        // 构建 路径,并选出最高和最低的point
        for (int i = 0; i < mLineDataList.size(); i++) {
            PointF curPoint = mLineDataList.get(i);
            if (i == 0) {
                mDataPath.moveTo(curPoint.x * width, -curPoint.y * width);
            } else {
                mDataPath.lineTo(curPoint.x * width, -curPoint.y * width);
            }
        }
    }


@override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // 通过函数 buildDataPath() 或者path路径 mDataPath ,然后再调用 
        drawPath(mDataPath,paint) 函数来绘制轨迹线
    canvas.drawPath(mDataPath,paint);
   
}

2.2:画坐标X轴Y轴

@override
public void onDraw(Canvas canvas) {
    super.canvas(canvas);

    // 调用绘制X轴和 Y轴函数
    
    drawCoordination(canvas);
}



private void drawCoordination(Canvas canvas) {

        mLinePaint.setStrokeWidth(dpToPx(1f));
        mLinePaint.setColor(COORDINATION_LINE_COLOR);

        // 画 y 轴
        canvas.drawLine(0,
                -mPositiveCount * mEachItemWidth,
                0,
                mNegativeCount * mEachItemWidth,
                mLinePaint);

        // 画 x 轴
        canvas.drawLine(0,
                0,
                GRID_INTERVAL_COUNT * mEachItemWidth,
                0,
                mLinePaint);

}

2.3:画网格

 /**
     * 画网格线
     *
     * @param canvas
     */
    private void drawGrid(Canvas canvas) {
        mLinePaint.setStrokeWidth(dpToPx(0.5f));
        mLinePaint.setColor(GRID_LINE_COLOR);

        // 画y正轴横线
        for (int i = 1; i <= mPositiveCount; ++i) {
            canvas.drawLine(0,
                    -i * mEachItemWidth,
                    GRID_INTERVAL_COUNT * mEachItemWidth,
                    -i * mEachItemWidth,
                    mLinePaint);
        }
        
        // 画x正轴竖线
        for (int i = 1; i <= GRID_INTERVAL_COUNT; ++i) {
            canvas.drawLine(i * mEachItemWidth,
                    -mPositiveCount * mEachItemWidth,
                    i * mEachItemWidth,
                    mNegativeCount * mEachItemWidth,
                    mLinePaint);
        }

    }


@override
public void onDraw(Canvas canvas) {

    super.canvas(canvas);

    // 调用画网格的函数方法
    
    drawGrid(canvas);
}

2.4:画红色小球

    /**
     * 画圆点
     *
     * @param canvas
     */
    private void drawPoint(Canvas canvas) {
        if (mCurPoint == null) {
            return;
        }

        mPointPaint.setColor(CUR_POINT_COLOR);
        canvas.drawCircle(mCurPoint.x * mEachItemWidth * GRID_INTERVAL_COUNT,
                -mCurPoint.y * mEachItemWidth * GRID_INTERVAL_COUNT,
                CUR_POINT_RADIUS,
                mPointPaint);
    }


@override
public void onDraw(Canvas canvas) {

    super.canvas(canvas);

    // 调用绘制圆点的函数
    drawPoint(canvas);
}

2.5: 画下标(即坐标轴的值)

    /**
     * 画下标
     *
     * @param canvas
     */
    private void drawText(Canvas canvas) {

        canvas.drawText("0", -PADDING,
                0,
                mTextPaint);

        mTextPaint.setTextAlign(Paint.Align.RIGHT);
        for (int i = 1; i <= mPositiveCount; ++i) {
            if (i <= 10) {
                mTextPaint.setColor(COORDINATION_LINE_COLOR);
            } else {
                mTextPaint.setColor(DATA_LINE_COLOR);
            }
            canvas.drawText(getNumString(i * 0.1f), -PADDING / 2,
                    -i * mEachItemWidth,
                    mTextPaint);
        }

        mTextPaint.setColor(DATA_LINE_COLOR);
        for (int i = 1; i <= mNegativeCount; ++i) {
            canvas.drawText(getNumString(i * -0.1f), -PADDING / 2,
                    i * mEachItemWidth,
                    mTextPaint);
        }

        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setColor(COORDINATION_LINE_COLOR);
        for (int i = 1; i <= GRID_INTERVAL_COUNT; ++i) {
            canvas.drawText(getNumString(i * 0.1f), i * mEachItemWidth,
                    PADDING / 2 + TEXT_SIZE,
                    mTextPaint);
        }
    }


@override
public void onDraw(Canvas canvas) {

    surper.onDraw(canvas);

   // 调用绘制坐标轴画下标函数
   drawText(canvas);

}

3: 完整代码

/**
 * @author yuhongwen
 * @date 创建时间:2022/04/06
 * @description 插值器的坐标显示
 */
public class TimeInterpolatorView extends View {

    // 外边距
    private static final int PADDING = dpToPx(5f);
    // 字体大小
    private static final int TEXT_SIZE = dpToPx(8f);
    // 点的半径
    private static final int CUR_POINT_RADIUS = dpToPx(4.5f);

    // X、Y 轴色
    private static final int COORDINATION_LINE_COLOR = Color.BLACK;
    // 网格线色
    private static final int GRID_LINE_COLOR = Color.LTGRAY;
    // 数据线色
    private static final int DATA_LINE_COLOR = Color.parseColor("#DB001B");
    // 当前点的色
    private static final int CUR_POINT_COLOR = Color.parseColor("#DC143C");
    // 默认的最低点
    private static final PointF DEFAULT_MIN_POINT = new PointF(0, 0);
    // 默认的最高点
    private static final PointF DEFAULT_MAX_POINT = new PointF(0, 1);
    // 10个间隔
    private static final int GRID_INTERVAL_COUNT = 10;
    // 每个间隔的跨幅
    private static final float GRID_INTERVAL_LENGTH = 0.1f;

    // 速率的数据
    private final List<PointF> mLineDataList = new ArrayList<>();

    // 坐标的画笔
    private Paint mLinePaint;
    // 速率的轨迹
    private Path mDataPath = new Path();
    // 字体画笔
    private Paint mTextPaint;
    // 点的笔
    private Paint mPointPaint;

    // 数据的最低点
    private PointF mMinPoint = DEFAULT_MIN_POINT;
    // 数据的最高点
    private PointF mMaxPoint = DEFAULT_MAX_POINT;

    // 视图的宽
    private float mViewWidth;
    // 视图的高
    private float mViewHeight;
    // 坐标的宽
    private float mWidth;

    // 坐标中每个下标 的宽度
    private float mEachItemWidth;

    private int mPositiveCount;
    private int mNegativeCount;

    // 当前的点
    private PointF mCurPoint;

    public TimeInterpolatorView(Context context) {
        this(context, null, 0);
    }

    public TimeInterpolatorView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TimeInterpolatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {

        mLinePaint = new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setStyle(Paint.Style.STROKE);

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(TEXT_SIZE);

        mPointPaint = new Paint();
        mPointPaint.setAntiAlias(true);
        mPointPaint.setStyle(Paint.Style.FILL);

    }

    /**
     * 设置当前移动的点
     *
     * @param curPoint
     */
    public void setCurPoint(PointF curPoint) {
        this.mCurPoint = curPoint;
        invalidate();
    }

    public void setLineData(List<PointF> lineDataList) {
        mLineDataList.clear();
        mLineDataList.addAll(lineDataList);

        mMinPoint = DEFAULT_MIN_POINT;
        mMaxPoint = DEFAULT_MAX_POINT;
        // 构建 路径,并选出最高和最低的point
        for (int i = 0; i < mLineDataList.size(); i++) {
            PointF curPoint = mLineDataList.get(i);

            // 选最低点
            if (curPoint.y < mMinPoint.y) {
                mMinPoint = curPoint;
            }

            // 选最高点
            if (curPoint.y > mMaxPoint.y) {
                mMaxPoint = curPoint;
            }

        }

        calculateEachItemWidth();

        invalidate();

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mViewWidth = w;
        mViewHeight = h;

        // 需要减去 padding 的宽度 和 字体的大小
        mWidth = Math.min(w, h) - 2 * PADDING - TEXT_SIZE;

        calculateEachItemWidth();
    }

    /**
     * 计算每个格子的大小
     */
    private void calculateEachItemWidth() {

        // 获取 y正半轴 的分割个数
        mPositiveCount = (int) Math.abs(Math.ceil(mMaxPoint.y / GRID_INTERVAL_LENGTH));
        // 获取 y负半轴 的分割个数
        mNegativeCount = (int) Math.abs(Math.floor(mMinPoint.y / GRID_INTERVAL_LENGTH));

        // 计算需要分割的数量,最少十个
        int intervalCount = mPositiveCount + mNegativeCount;
        intervalCount = Math.max(intervalCount, GRID_INTERVAL_COUNT);

        mEachItemWidth = mWidth / intervalCount;

    }

    @Override
    protected void onDraw(Canvas canvas) {

        // 构建轨迹
        buildDataPath();

        canvas.save();

        // 移至原点
        moveToTheOrigin(canvas);

        // 画坐标
        drawCoordination(canvas);
        // 画网格
        drawGrid(canvas);

        // 画数据线
        drawDataLine(canvas);

        // 画下标
        drawText(canvas);

        // 画当前的点
        drawPoint(canvas);

        canvas.restore();

    }

    /**
     * 画点
     *
     * @param canvas
     */
    private void drawPoint(Canvas canvas) {
        if (mCurPoint == null) {
            return;
        }

        mPointPaint.setColor(CUR_POINT_COLOR);
        canvas.drawCircle(mCurPoint.x * mEachItemWidth * GRID_INTERVAL_COUNT,
                -mCurPoint.y * mEachItemWidth * GRID_INTERVAL_COUNT,
                CUR_POINT_RADIUS,
                mPointPaint);
    }

    /**
     * 画下标
     *
     * @param canvas
     */
    private void drawText(Canvas canvas) {

        canvas.drawText("0", -PADDING,
                0,
                mTextPaint);

        mTextPaint.setTextAlign(Paint.Align.RIGHT);
        for (int i = 1; i <= mPositiveCount; ++i) {
            if (i <= 10) {
                mTextPaint.setColor(COORDINATION_LINE_COLOR);
            } else {
                mTextPaint.setColor(DATA_LINE_COLOR);
            }
            canvas.drawText(getNumString(i * 0.1f), -PADDING / 2,
                    -i * mEachItemWidth,
                    mTextPaint);
        }

        mTextPaint.setColor(DATA_LINE_COLOR);
        for (int i = 1; i <= mNegativeCount; ++i) {
            canvas.drawText(getNumString(i * -0.1f), -PADDING / 2,
                    i * mEachItemWidth,
                    mTextPaint);
        }

        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setColor(COORDINATION_LINE_COLOR);
        for (int i = 1; i <= GRID_INTERVAL_COUNT; ++i) {
            canvas.drawText(getNumString(i * 0.1f), i * mEachItemWidth,
                    PADDING / 2 + TEXT_SIZE,
                    mTextPaint);
        }
    }

    private String getNumString(float num) {
        return String.format("%.1f", num);
    }

    /**
     * 画网格线
     *
     * @param canvas
     */
    private void drawGrid(Canvas canvas) {
        mLinePaint.setStrokeWidth(dpToPx(0.5f));
        mLinePaint.setColor(GRID_LINE_COLOR);

        // 画y正轴横线
        for (int i = 1; i <= mPositiveCount; ++i) {
            canvas.drawLine(0,
                    -i * mEachItemWidth,
                    GRID_INTERVAL_COUNT * mEachItemWidth,
                    -i * mEachItemWidth,
                    mLinePaint);
        }

        // 画y负轴横线
        for (int i = 1; i <= mNegativeCount; ++i) {
            canvas.drawLine(0,
                    i * mEachItemWidth,
                    GRID_INTERVAL_COUNT * mEachItemWidth,
                    i * mEachItemWidth,
                    mLinePaint);
        }

        // 画x正轴竖线
        for (int i = 1; i <= GRID_INTERVAL_COUNT; ++i) {
            canvas.drawLine(i * mEachItemWidth,
                    -mPositiveCount * mEachItemWidth,
                    i * mEachItemWidth,
                    mNegativeCount * mEachItemWidth,
                    mLinePaint);
        }


    }

    /**
     * 画数据线
     *
     * @param canvas
     */
    private void drawDataLine(Canvas canvas) {

        mLinePaint.setStrokeWidth(dpToPx(1f));
        mLinePaint.setColor(DATA_LINE_COLOR);

        canvas.drawPath(mDataPath, mLinePaint);

    }

    /**
     * 画 x、y 轴
     *
     * @param canvas
     */
    private void drawCoordination(Canvas canvas) {

        mLinePaint.setStrokeWidth(dpToPx(1f));
        mLinePaint.setColor(COORDINATION_LINE_COLOR);

        // 画 y 轴
        canvas.drawLine(0,
                -mPositiveCount * mEachItemWidth,
                0,
                mNegativeCount * mEachItemWidth,
                mLinePaint);

        // 画 x 轴
        canvas.drawLine(0,
                0,
                GRID_INTERVAL_COUNT * mEachItemWidth,
                0,
                mLinePaint);

    }

    /**
     * 构建数据路径
     */
    private void buildDataPath() {
        mDataPath.reset();
        float width = mEachItemWidth * GRID_INTERVAL_COUNT;
        // 构建 路径,并选出最高和最低的point
        for (int i = 0; i < mLineDataList.size(); i++) {
            PointF curPoint = mLineDataList.get(i);
            if (i == 0) {
                mDataPath.moveTo(curPoint.x * width, -curPoint.y * width);
            } else {
                mDataPath.lineTo(curPoint.x * width, -curPoint.y * width);
            }
        }
    }

    /**
     * 将画布移至 原点
     */
    private void moveToTheOrigin(Canvas canvas) {

        float verHeight = mEachItemWidth * mPositiveCount;

        // 计算 横向移动距离
        float horPadding = mViewWidth - mEachItemWidth * GRID_INTERVAL_COUNT - 2 * PADDING;
        float verPadding = mViewHeight - mEachItemWidth * (mPositiveCount + mNegativeCount) - 2 * PADDING - TEXT_SIZE / 2;

        canvas.translate(horPadding / 2 + PADDING, verPadding / 2 + verHeight + PADDING);

    }

    /**
     * 转换 dp 至 px
     *
     * @param dpValue dp值
     * @return px值
     */
    protected static int dpToPx(float dpValue) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        return (int) (dpValue * metrics.density + 0.5f);
    }

    /**
     * 获取视图的宽
     *
     * @return 视图宽 - 左右的内边距
     */
    private float getViewEnableWidth() {
        return mViewWidth - PADDING * 2;
    }

    /**
     * 获取视图的高
     *
     * @return 视图高 - 上下的内边距
     */
    private float getViewEnableHeight() {
        return mViewHeight - PADDING * 2;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值