波浪形温度刻度表完成

前语

之前的制作圆环,咱们了解了怎么制作想要的形状和进展的一些特点,那么此篇文章咱们更近一步,制作一个稍微复杂一点的刻度与波浪。来一同温习一下Android的制作。

相对应的这种类型的自界说View网上并不少见,可是假如咱们要做一些个性化的作用,最好还是自己制作一份,也相对的比较简略操控作用,假如想完成上面的作用,咱们一般来说分为以下几个步骤:

  1. 重写丈量办法,确保它是一个正方形
  2. 制作刻度
  3. 制作中心的圆与文字
  4. 水波纹的动画
  5. 设置进展与动画,一同动起来

思路咱们已经有了,下面一步一步的来完成吧。

话不多说,Let’s go

Android自定义View绘制进阶-水波浪温度刻度表

1、onMeasure重新丈量

之前的圆环进展,咱们并没有重写 onMeasure 办法,而是在布局中指定为固定的宽高,其实兼容性和健壮性并不好,万一写错了就会变形导致显现异常。

最好的办法是不论xml中设置为什么值,这儿都能保证为一个正方形,要么是取宽度为准,让高度和宽度共同,要么便是宽度高度取最大值,让他们保持共同。因为咱们是竖屏的运用,所以我就取宽度为准,让高度和宽度共同。

前面咱们只是讲了 onDraw 并没有讲到 onMeasure , 这儿简略的说一下。

咱们为什么要重写 onMeasure ?

  1. 为了自界说View尺度的规矩,假如你的自界说View的尺度是依据父控件行为共同,就不需求重写onMeasure()办法。
  2. 假如不重写onMeasure办法,那么自界说view的尺度默许就和父控件相同巨细,当然也能够在布局文件里面写死宽高,而重写该办法能够依据自己的需求设置自界说view巨细。

一般来说咱们重写的 onMeasure 长这样:

 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec,heightMeasureSpec)
}

widthMeasureSpec ,heightMeasureSpec 并不是真实的宽高,看名字就知道,它只是宽高丈量的标准,咱们经过 MeasureSpec 的一些静态办法,经过它们拿到一些信息。

static int getMode(int measureSpec):依据供给的丈量值(标准)提取模式(上述三个模式之一)

丈量的 Model 一共有三种

  1. UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素能够得到任意想要的巨细;
  2. EXACTLY(彻底),父元素决议自元素的确切巨细,子元素将被限制在给定的鸿沟里而疏忽它本身巨细;
  3. AT_MOST(至多),子元素至多到达指定巨细的值。

咱们常用的便是 EXACTLY 和 AT_MOST ,EXACTLY 对应的便是咱们设置的match_parent或者300这样的准确值,而 AT_MOST 对应的便是wrap_content。

static int getSize(int measureSpec):依据供给的丈量值(标准)提取巨细值(这个巨细也便是咱们通常所说的巨细)

经过此办法就能获取控件的宽度和高度值。

static int makeMeasureSpec(int size,int mode):依据供给的巨细值和模式创立一个丈量值(标准)

经过具体的宽高和model,创立对应的宽高丈量标准,用于确认View的丈量

onMeasure 的最终设置确认宽度的丈量有两种方法,

  1. setMeasuredDimension(width, height)
  2. super.onMeasure(widthMeasureSpec,heightMeasureSpec)

实战:

比如咱们的自界说温度刻度View,咱们整个View要确保一个正方形,那么就拿到宽度,设置相同的高度,然后确认丈量,流程如下:

    //重新丈量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取控件的宽度,高度
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int newWidthMeasureSpec = widthMeasureSpec;
        //假如没有指定宽度,默许给200宽度
        if (widthMode != MeasureSpec.EXACTLY) {
            newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(200, MeasureSpec.EXACTLY);
        }
        //获取到最新的宽度
        int width = MeasureSpec.getSize(newWidthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        //咱们要的是矩形,不论高度是多高,让它总是和宽度共同
        int height = width;
        centerPosition.x = width / 2;
        centerPosition.y = height / 2;
        radius = width / 2f;
        mRectF.set(0f, 0f, width, height);
        //最后设置收效-下面两种方法都能够
        // setMeasuredDimension(width, height);
        super.onMeasure(
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
        );
    }

这儿有具体的注释,大致完成的作用如下:

Android自定义View绘制进阶-水波浪温度刻度表

2、制作刻度

因为本来的 Canvas 内部没有制作刻度这么一说,所以咱们只能用制作线条的方法,便是 drawLine 办法。

为了了解到坐标系和方便完成,咱们能够先制作一个圆环,定位咱们刻度需求制作的位置。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画圆环
        canvas.drawArc(
                mRectF.left + 2f, mRectF.top + 2f, mRectF.right - 2f, mRectF.bottom - 2f,
                mStartAngle, mSweepAngle, false, mDegreeCirPaint
        );
    }

这个圆环是之前讲到过了,就不过多赘述了,完成作用如下:

Android自定义View绘制进阶-水波浪温度刻度表

因为开端制作的当地在左上角位置,咱们要移动到圆的中心点开端制作,也便是红色点移动到蓝色点。

咱们就需求x轴和y轴做一下偏移 canvas.translate(radius, radius);

默许的 drawLine 都是横向制作,咱们想要完成作用图的作用,就需求旋转一下画笔,也便是用到 canvas.rotate(rotateAngle);

那么旋转多少了,假如说最底部是90度,咱们的开端视点是120度开端的,咱们就开端旋转30度。后边每一次旋转就按照百分比来,比如咱们100度的温度,那么就相当于要画100个刻度,咱们就用需求制作的视点除以100,便是每一个刻度的视点。

具体的刻度完成代码:


    private float mStartAngle = 120f;  // 圆弧的开端视点
    private float mSweepAngle = 300f; //制作的开端视点和滑过视点(制作300度)
    private float mTargetAngle = 300f;  //刻度的视点(依据此核算需求制作有色的进展)
    private void drawDegreeLine(Canvas canvas) {
        //先保存
        canvas.save();
        // 移动画布
        canvas.translate(radius, radius);
        // 旋转坐标系,需求确认旋转视点
        canvas.rotate(30);
        // 每次旋转的视点
        float rotateAngle = mSweepAngle / 100;
        // 累计叠加的视点
        float currentAngle = 0;
        for (int i = 0; i <= 100; i++) {
            if (currentAngle <= mTargetAngle && mTargetAngle != 0) {
                // 核算累方案过的刻度百分比
                float percent = currentAngle / mSweepAngle;
                //动态的设置颜色
                mDegreelinePaint.setColor(evaluateColor(percent, Color.GREEN, Color.RED));
                canvas.drawLine(0, radius, 0, radius - 20, mDegreelinePaint);
                // 画过的视点进行叠加
                currentAngle += rotateAngle;
            } else {
                mDegreelinePaint.setColor(Color.WHITE);
                canvas.drawLine(0, radius, 0, radius - 20, mDegreelinePaint);
            }
            //画完一个刻度就要旋转移动位置
            canvas.rotate(rotateAngle);
        }
        //再恢复
        canvas.restore();
    }

加上圆环与刻度的作用图:

Android自定义View绘制进阶-水波浪温度刻度表

3. 设置刻度动画

前面的一篇咱们运用的是属性动画不断的制作然后完成进展的作用,那么这一次咱们运用守时使命的方法也是能够完成动画的作用。

因为咱们之前的 drawDegreeLine 办法内部操控制作进展的变量便是 targetAngle 来操控的,所以咱们经过进口办法设置温度的时分经过守时使命的方法来操控。

代码如下:


    //动画状况
    private boolean isAnimRunning;
    // 手动完成越来越慢的作用
    private int[] slow = {10, 10, 10, 8, 8, 8, 6, 6, 6, 6, 4, 4, 4, 4, 2};
    // 动画的下标
    private int goIndex = 0;
    //设置温度,进口的开端
    public void setupTemperature(float temperature) {
        mCurPercent = 0f;
        totalAngle = (temperature / 100) * mSweepAngle;
        targetAngle = 0f;
        mCurPercent = 0f;
        mCurTemperature = "0.0";
        mWaveUpValue = 0;
        startTimerAnim();
    }
      //运用守时使命做动画
    private void startTimerAnim() {
        if (isAnimRunning) {
            return;
        }
        mAnimTimer = new Timer();
        mAnimTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                isAnimRunning = true;
                targetAngle += slow[goIndex];
                goIndex++;
                if (goIndex == slow.length) {
                    goIndex--;
                }
                if (targetAngle >= totalAngle) {
                    targetAngle = totalAngle;
                    isAnimRunning = false;
                    mAnimTimer.cancel();
                }
                // 核算的温度
                mCurPercent = targetAngle / mSweepAngle;
                mCurTemperature = mDecimalFormat.format(mCurPercent * 100);
                // 水波纹的高度
                mWaveUpValue = (int) (mCurPercent * (mSmallRadius * 2));
                postInvalidate();
            }
        }, 250, 30);
    }

那么刻度动画的作用如下:

Android自定义View绘制进阶-水波浪温度刻度表

4. 制作中心的圆与文字

咱们再动画中记载动画的百分比进展,和动画当时的温度。

    ...
    // 核算的温度
    mCurPercent = targetAngle / mSweepAngle;
    mCurTemperature = mDecimalFormat.format(mCurPercent * 100);
    postInvalidate();
    ...

咱们记载一下小圆的半径和文本的画笔资源

   private float mSmallRadius = 0f;
    private Paint mTextPaint;
    private Paint mSmallCirclePaint;
    private float mCurPercent = 0f;  //进展
    private String mCurTemperature = "0.0";
    private DecimalFormat mDecimalFormat;
    private void init() {
        ...
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setColor(Color.WHITE);
        mSmallCirclePaint = new Paint();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        ...
        //画小圆
        drawSmallCircle(canvas, evaluateColor(mCurPercent, Color.GREEN, Color.RED));
        //画中心的圆与文本
        drawTemperatureText(canvas);
    }

具体的文本与小圆的制作

    private void drawSmallCircle(Canvas canvas, int evaluateColor) {
        mSmallCirclePaint.setColor(evaluateColor);
        mSmallCirclePaint.setAlpha(65);
        canvas.drawCircle(centerPosition.x, centerPosition.y, mSmallRadius, mSmallCirclePaint);
    }
    private void drawTemperatureText(Canvas canvas) {
        //提示文字
        mTextPaint.setTextSize(mSmallRadius / 6f);
        canvas.drawText("当时温度", centerPosition.x, centerPosition.y - mSmallRadius / 2f, mTextPaint);
        //温度文字
        mTextPaint.setTextSize(mSmallRadius / 2f);
        canvas.drawText(mCurTemperature, centerPosition.x, centerPosition.y + mSmallRadius / 4f, mTextPaint);
        //制作单位
        mTextPaint.setTextSize(mSmallRadius / 6f);
        canvas.drawText("C", centerPosition.x + (mSmallRadius / 1.5f), centerPosition.y, mTextPaint);
    }

因为进展和温度都是动画在 invalidate 之前赋值的,所以咱们的文本和小圆天然就支撑动画的作用了。

作用如下:

Android自定义View绘制进阶-水波浪温度刻度表

5. 水波纹动画

水波纹的作用,咱们不能直接用 Canvas 来制作,咱们能够用刻度的办法用 drawLine的方法来制作,怎么制作呢?相信咱们也有了解,便是正弦函数了。

因为咱们的作用是两个水波纹彼此叠加起起伏伏的作用,所以咱们界说两个函数。

总体的思路是:咱们界说两个数组来办理咱们的Y轴的值,经过正弦函数给Y轴赋值,然后在drawLine的时分取出对应的x轴的y值就能够制作出来。

x轴其实便是咱们的控件宽度,咱们先用一个数组保存起来

    private float[] mFirstWaterLine;
    private float[] mSecondWaterLine;
     @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取控件的宽度,高度
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int newWidthMeasureSpec = widthMeasureSpec;
        //假如没有指定宽度,默许给200宽度
        if (widthMode != MeasureSpec.EXACTLY) {
            newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(200, MeasureSpec.EXACTLY);
        }
        //获取到最新的宽度
        int width = MeasureSpec.getSize(newWidthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        //咱们要的是矩形,不论高度是多高,让它总是和宽度共同
        int height = width;
        mFirstWaterLine = new float[width];
        mSecondWaterLine = new float[width];
        super.onMeasure(
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
        );
    }

然后咱们再制作之前就先对x轴对应的y值赋值,然后制作的时分就取出对应的y值来 drawLine,具体的代码如下:

动画的时分先对横向运动和笔直运动的变量做一个赋值:

    private int mWaveUpValue = 0;
    private float mWaveMoveValue = 0f;
    //运用守时使命做动画
    private void startTimerAnim() {
        if (isAnimRunning) {
            return;
        }
        mAnimTimer = new Timer();
        mAnimTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                ...
                // 核算的温度
                mCurPercent = targetAngle / mSweepAngle;
                mCurTemperature = mDecimalFormat.format(mCurPercent * 100);
                // 水波纹的高度
                mWaveUpValue = (int) (mCurPercent * (mSmallRadius * 2));
                postInvalidate();
            }
        }, 250, 30);
    }
    public void moveWaterLine() {
        mWaveTimer = new Timer();
        mWaveTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                mWaveMoveValue += 1;
                if (mWaveMoveValue == 100) {
                    mWaveMoveValue = 1;
                }
                postInvalidate();
            }
        }, 500, 200);
    }

拿到了对应的变量值之后,然后开端制作:

 /**
     * 制作水波
     */
    private void drawWaterWave(Canvas canvas, int color) {
        int len = (int) mRectF.right;
        // 将周期定为view总宽度
        float mCycleFactorW = (float) (2 * Math.PI / len);
        // 得到第一条波的峰值
        for (int i = 0; i < len; i++) {
            mFirstWaterLine[i] = (float) (10 * Math.sin(mCycleFactorW * i + mWaveMoveValue) - mWaveUpValue);
        }
        // 得到第一条波的峰值
        for (int i = 0; i < len; i++) {
            mSecondWaterLine[i] = (float) (15 * Math.sin(mCycleFactorW * i + mWaveMoveValue + 10) - mWaveUpValue);
        }
        canvas.save();
        // 裁剪成圆形区域
        Path path = new Path();
        path.addCircle(len / 2f, len / 2f, mSmallRadius, Path.Direction.CCW);
        canvas.clipPath(path);
        path.reset();
        // 将坐标系移到底部
        canvas.translate(0, centerPosition.y + mSmallRadius);
        mSmallCirclePaint.setColor(color);
        for (int i = 0; i < len; i++) {
            canvas.drawLine(i, mFirstWaterLine[i], i, len, mSmallCirclePaint);
        }
        for (int i = 0; i < len; i++) {
            canvas.drawLine(i, mSecondWaterLine[i], i, len, mSmallCirclePaint);
        }
        canvas.restore();
    }

一个是对Y轴赋值,一个是取出x轴对应的y轴进行制作,这儿需求留意的是咱们裁剪出了一个小圆的图形,而且覆盖在小圆上面完成出作用图的姿态。

运转的作用如下:

Android自定义View绘制进阶-水波浪温度刻度表

要记住对守时器进行资源你的关闭哦。

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mWaveTimer != null) {
            mWaveTimer.cancel();
        }
        if (mAnimTimer != null && isAnimRunning) {
            mAnimTimer.cancel();
        }
    }

运用的时分咱们只需求设置温度即可开端动画。

       findViewById<View>(R.id.set_progress).click {
           val temperatureView = findViewById<TemperatureView>(R.id.temperature_view)
            temperatureView .setupTemperature(70f)
        }

跋文

因为是自用定制的,自己也比较懒,所以并没有对一些装备的属性做自界说属性的抽取,比如圆环的距离,巨细,颜色,波纹的距离,动画的快慢等等。

内部加了一点点丈量的用法,可是首要还是制作的流程,基本上把常用的几种制作方法都用到了。今后有类似的作用咱们也能够按需修改即可。

因为是自用的一个View,相对圆环进展没有那么多场景运用,就没有抽取出来上传到Maven,假如咱们有爱好能够查看源码点击【传送门】。

一起,你也能够重视我的这个Kotlin项目,我有时间都会持续更新。

常规,我如有解说不到位或讹夺的当地,希望同学们能够指出交流。

假如感觉本文对你有一点点的启发,还望你能点赞支撑一下,你的支撑是我最大的动力。

Ok,这一期就此结束。

Android自定义View绘制进阶-水波浪温度刻度表

本文正在参加「金石方案 . 瓜分6万现金大奖」