Android custom progress waveform

I found some relevant information on the Internet. Some data content has no steps, which is difficult to understand. Here is a simple custom waveform diagram first:


The effect is shown in the figure

There are several steps to custom view:

  1. A class inherits from View or ViewGroup;
  2. override constructor
  3. Initialization method: used to initialize brushes, custom attributes, etc.
  4. Rewrite onMeasure() method: used to measure screen size, length and width mode
  5. Rewrite the onLayout() method: layout view
  6. Override the onDraw() method: draw the interface
  7. Write related setting parameters or callback interface, etc.

Here are the wave-related methods:

The first is the Bezier curve

You can see what kind of example it is on this website: http://myst729.github.io/bezier-curve/


If you want to know more about the principle of Bezier curve, you can search by yourself

The following is a simple code process:

    Android provides two methods in the Path class:



quadTo represents the method of quadratic equation cubicTo represents the method of cubic equation


The event process point C represented by x1, y1, the end point B represented by x2, y2, and so on

So first set a starting point: x0, y0 use this method:


Now move to a point, then pass the process point, and then to the end point

Above code:

WaveView inherits from View

package com.riti.testview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by YZBbanban on 2018/2/28.
 */

public class WareView extends View {
    private Paint mPaint;
    private Path mPath;

    public WareView(Context context) {
        super(context);
        init();
    }

    public WareView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);//防锯齿
        mPaint.setDither(true);//防抖动
        mPath = new Path();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画笔颜色
        mPaint.setColor(Color.RED);
        //设置为描边
        mPaint.setStyle(Paint.Style.STROKE);
        //移动到左边屏幕中间
        mPath.moveTo(0.0f , getHeight()/2);
        //贝斯阿尔曲线
        mPath.quadTo(getWidth()/2,getHeight()/2,getWidth()/2,getHeight());
        //在画布上绘制
        canvas.drawPath(mPath,mPaint);

    }

}

Renderings:

The simple Bezier curve drawing is completed and the following enters the topic:

First plot the curve:

    curve model


As shown in the figure: the starting point is (0, getHeight()/2), and there are four process points, so you need to write four quadratic equation methods, and there are four end points:

method:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画笔颜色
        mPaint.setColor(Color.RED);
        //设置为描边
        mPaint.setStyle(Paint.Style.STROKE);
        //移动到左边屏幕中间
        mPath.moveTo(0.0f , getHeight()/2);
        //贝斯阿尔曲线
        mPath.quadTo(getWidth()/8,getHeight()/2-100,getWidth()/4,getHeight()/2);
        mPath.quadTo(3*getWidth()/8,getHeight()/2+100,getWidth()/2,getHeight()/2);
        mPath.quadTo(5*getWidth()/8,getHeight()/2-100,3*getWidth()/4,getHeight()/2);
        mPath.quadTo(7*getWidth()/8,getHeight()/2+100,getWidth(),getHeight()/2);
        //在画布上绘制
        canvas.drawPath(mPath,mPaint);

    }

Effect:


The effect is out, just let the curve move, here is a simple method, lengthen the curve, beyond the left side of the screen, and then move to the right, use ValueAnimator

Theoretical graphics:


Draw such a curve, and then continuously change the offset of the x-axis, the above code:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //画笔颜色
    mPaint.setColor(Color.RED);
    //设置为描边
    mPaint.setStyle(Paint.Style.STROKE);
    //初始化,不然会重复绘制
    mPath.reset();
    //移动到左边屏幕中间
    mPath.moveTo(0.0f , getHeight()/2);
    //贝斯阿尔曲线
    mPath.quadTo(-7 * getWidth() / 8 + offset, getHeight()/2 - 100, -3*getWidth() / 4 + offset, getHeight()/2);
    mPath.quadTo(-5 * getWidth() / 8 + offset, getHeight()/2 + 100, -getWidth()/2+offset, getHeight()/2);
    mPath.quadTo(-3 * getWidth() / 8 + offset, getHeight()/2 - 100, -getWidth() / 4 + offset, getHeight()/2);
    mPath.quadTo(-1 * getWidth() / 8 + offset, getHeight()/2 + 100, offset, getHeight()/2);

    mPath.quadTo(1 * getWidth() / 8 + offset, getHeight()/2 - 100, getWidth() / 4 + offset, getHeight()/2);
    mPath.quadTo(3 * getWidth() / 8 + offset, getHeight()/2 + 100, getWidth()/2 + offset, getHeight()/2);
    mPath.quadTo(5 * getWidth() / 8 + offset, getHeight()/2 - 100, 3*getWidth() / 4 + offset, getHeight()/2);
    mPath.quadTo(7 * getWidth() / 8 + offset, getHeight()/2 + 100, getWidth() + offset, getHeight()/2);
    //在画布上绘制
    canvas.drawPath(mPath,mPaint);

}


Of course, you need to set an initial value of 0 for offset. The effect does not change much, because half of the curve is on the left side of the screen and cannot be seen.

Add animation effects:

    private void xController() {
        ValueAnimator mAnimator = ValueAnimator.ofFloat(0, getWidth());
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float animatorValue = (float) valueAnimator.getAnimatedValue();
                offset = animatorValue;//不断的设置偏移量,并重画
                postInvalidate();

            }
        });
        mAnimator.setDuration(1000);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.start();
    }

Add the xcontroller() method in the onLayout() method

Renderings:


Dynamic Curve Complete

Next is the fill color:

Change the brush style to FILL

Above code:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画笔颜色
        mPaint.setColor(Color.RED);
        //改变模式为填充 fill
        mPaint.setStyle(Paint.Style.FILL);
        //初始化,不然会重复绘制
        mPath.reset();
        //移动到左边屏幕中间
        mPath.moveTo(0.0f , getHeight()/2);
        //贝斯阿尔曲线
        mPath.quadTo(-7 * getWidth() / 8 + offset, getHeight()/2 - 100, -3*getWidth() / 4 + offset, getHeight()/2);
        mPath.quadTo(-5 * getWidth() / 8 + offset, getHeight()/2 + 100, -getWidth()/2+offset, getHeight()/2);
        mPath.quadTo(-3 * getWidth() / 8 + offset, getHeight()/2 - 100, -getWidth() / 4 + offset, getHeight()/2);
        mPath.quadTo(-1 * getWidth() / 8 + offset, getHeight()/2 + 100, offset, getHeight()/2);

        mPath.quadTo(1 * getWidth() / 8 + offset, getHeight()/2 - 100, getWidth() / 4 + offset, getHeight()/2);
        mPath.quadTo(3 * getWidth() / 8 + offset, getHeight()/2 + 100, getWidth()/2 + offset, getHeight()/2);
        mPath.quadTo(5 * getWidth() / 8 + offset, getHeight()/2 - 100, 3*getWidth() / 4 + offset, getHeight()/2);
        mPath.quadTo(7 * getWidth() / 8 + offset, getHeight()/2 + 100, getWidth() + offset, getHeight()/2);
        //贝塞尔曲线中点为屏幕右端竖直方向的中点,所以选取屏幕上方作为一条线的中点
        mPath.lineTo(getWidth(),0);
        //在移动到屏幕起始点,形成闭区间
        mPath.lineTo(0,0);

        //在画布上绘制
        canvas.drawPath(mPath,mPaint);

    }

Renderings:

Why it is upward, the following will introduce:

There is a mode in android called superimposed PorterDuff.Mode method of this type

If you want to learn more about the searchable android canvas overlay, I won’t explain it here. Friends who have needs can leave a message and will reply in time

Superposition means the intersection of two graphics. The way I use it is to eliminate the CLEAR mode for a circle like surface superposition

First draw a circle:

        float radius = 150.0f;
//        Log.i(TAG, "onDraw: "+getWidth());
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.FILL);
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);//创建透明图层
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);

Let’s talk about the transparent layer first, because there is no overlay effect created on the original canvas, so the overlay of the canvas cannot be used. It is best to create a layer for the seat (I created a transparent layer for the entire screen)

Complete code:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float radius = 150.0f;
//        Log.i(TAG, "onDraw: "+getWidth());
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.FILL);
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);//创建透明图层
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);


        //画笔颜色
        mPaint.setColor(Color.RED);
        //改变模式为填充 fill
        mPaint.setStyle(Paint.Style.FILL);
        //设置图层叠加样式
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        //初始化,不然会重复绘制
        mPath.reset();
        //移动到左边屏幕中间
        mPath.moveTo(0.0f , getHeight()/2);
        //贝斯阿尔曲线
        mPath.quadTo(-7 * getWidth() / 8 + offset, getHeight()/2 - 100, -3*getWidth() / 4 + offset, getHeight()/2);
        mPath.quadTo(-5 * getWidth() / 8 + offset, getHeight()/2 + 100, -getWidth()/2+offset, getHeight()/2);
        mPath.quadTo(-3 * getWidth() / 8 + offset, getHeight()/2 - 100, -getWidth() / 4 + offset, getHeight()/2);
        mPath.quadTo(-1 * getWidth() / 8 + offset, getHeight()/2 + 100, offset, getHeight()/2);

        mPath.quadTo(1 * getWidth() / 8 + offset, getHeight()/2 - 100, getWidth() / 4 + offset, getHeight()/2);
        mPath.quadTo(3 * getWidth() / 8 + offset, getHeight()/2 + 100, getWidth()/2 + offset, getHeight()/2);
        mPath.quadTo(5 * getWidth() / 8 + offset, getHeight()/2 - 100, 3*getWidth() / 4 + offset, getHeight()/2);
        mPath.quadTo(7 * getWidth() / 8 + offset, getHeight()/2 + 100, getWidth() + offset, getHeight()/2);
        //贝塞尔曲线中点为屏幕右端竖直方向的中点,所以选取屏幕上方作为一条线的中点
        mPath.lineTo(getWidth(),0);
        //在移动到屏幕起始点,形成闭区间
        mPath.lineTo(0,0);

        //在画布上绘制
        canvas.drawPath(mPath,mPaint);
        //取消样式
        mPaint.setXfermode(null);
        //在 onDraw 之前保存画笔等的状态,便于下次直接使用画笔等工具
        canvas.restoreToCount(layerId);

        //绘制边界
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);

    }
Renderings:


finished static output

Now you need to add some dynamic things: baseline (the curve rises slowly), the density of the curve, etc. Here is a simple demonstration, so I simply made the following method

Full code:

package com.riti.testview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

/**
 * Created by YZBbanban on 2018/2/28.
 */

public class WareView extends View {
    private Paint mPaint;
    private Path mPath;
    private float offset;//偏移量
    private float baseLine;//基线


    public WareView(Context context) {
        super(context);
        init();
    }

    public WareView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPath = new Path();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        baseLine = MeasureSpec.getSize(heightMeasureSpec) / 2 + 150;
        Log.i(TAG, "onMeasure---->: " + baseLine);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        xController();
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float radius = 150.0f;
//        Log.i(TAG, "onDraw: "+getWidth());
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.FILL);
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);//创建透明图层
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);

        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        mPath.reset();
        mPath.moveTo(-getWidth() + offset, baseLine);

        //曲线密集度,8个周期 可自行添加或用属性设置,这里不做演示,有要求可留言
        int wareStep = 8;
        for (int i = 0; i < wareStep; i++) {
            float base = baseLine;
            if (i % 2 == 0) {
                base = base + 30f;
            } else {
                base = base - 30f;
            }
            mPath.quadTo(
                    (2 * i - wareStep + 1) * getWidth() / wareStep + offset,
                    base,
                    2 * (i - 3) * getWidth() / wareStep + offset,
                    baseLine);
        }

//        mPath.quadTo(-7 * getWidth() / 8 + offset, baseLine - 30, -3*getWidth() / 4 + offset, baseLine);
//        mPath.quadTo(-5 * getWidth() / 8 + offset, baseLine + 30, -getWidth()/2+offset, baseLine);
//        mPath.quadTo(-3 * getWidth() / 8 + offset, baseLine - 30, -getWidth() / 4 + offset, baseLine);
//        mPath.quadTo(-1 * getWidth() / 8 + offset, baseLine + 30, offset, baseLine);
//
//        mPath.quadTo(1 * getWidth() / 8 + offset, baseLine - 30, getWidth() / 4 + offset, baseLine);
//        mPath.quadTo(3 * getWidth() / 8 + offset, baseLine + 30, getWidth()/2 + offset, baseLine);
//        mPath.quadTo(5 * getWidth() / 8 + offset, baseLine - 30, 3*getWidth() / 4 + offset, baseLine);
//        mPath.quadTo(7 * getWidth() / 8 + offset, baseLine + 30, getWidth() + offset, baseLine);

        mPath.lineTo(getWidth(), 0.0f);
        mPath.lineTo(-getWidth() + offset, 0.0f);
        mPath.close();


        canvas.drawPath(mPath, mPaint);

        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);

        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);
    }

    private void xController() {
        ValueAnimator mAnimator = ValueAnimator.ofFloat(0, getWidth());
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float animatorValue = (float) valueAnimator.getAnimatedValue();
                offset = animatorValue;//不断的设置偏移量,并重画
                postInvalidate();

            }
        });
        mAnimator.setDuration(1000);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.start();
    }

    private static final String TAG = "WareView";

    public void setProcess(int process) {
        Log.i(TAG, "setProcess: " + process);
        //[-150,150]
        float step = process * 3f;

        float pro = getHeight() / 2 + (150f - step);
        Log.i(TAG, "setProcess getWidth: " + getWidth());
        Log.i(TAG, "pro: " + pro);

        this.baseLine = pro;
    }
}

Project address: https://github.com/yzbbanban/TestView


Guess you like

Origin blog.csdn.net/u013377003/article/details/79400605