インターネットでいくつかの関連情報を見つけました. 一部のデータ コンテンツにはステップがなく、理解するのが困難です. ここでは、最初に簡単なカスタム波形図を示します:
効果は図に示されています
カスタム ビューにはいくつかの手順があります。
- クラスは View または ViewGroup から継承します。
- コンストラクターをオーバーライドする
- 初期化方法: ブラシ、カスタム属性などの初期化に使用されます。
- onMeasure() メソッドの書き換え: 画面サイズ、長さ、幅モードの測定に使用
- onLayout() メソッドを書き直します: レイアウト ビュー
- onDraw() メソッドをオーバーライドします: インターフェイスを描画します
- 関連する設定パラメータやコールバック インターフェイスなどを記述します。
ウェーブ関連のメソッドは次のとおりです。
まずはベジエ曲線
この Web サイトでどのような例かを確認できます: http://myst729.github.io/bezier-curve/
ベジエ曲線の原理について詳しく知りたい場合は、自分で検索できます
以下は、単純なコード プロセスです。
Android は Path クラスに 2 つのメソッドを提供します。
quadTo は二次方程式の方法を表しますcubicTo は三次方程式の方法を表します
x1、y1 で表されるイベント処理点 C、x2、y2 で表される終点 B、など
したがって、最初に開始点を設定します: x0、y0 は次の方法を使用します。
今、ポイントに移動し、プロセス ポイントを通過し、終点に移動します。
上記のコード:
WaveView は 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);
}
}
レンダリング:
簡単なベジエ曲線の描画が完了し、次のトピックに入ります。
最初に曲線をプロットします。
曲線モデル
図に示すように: 始点は (0, getHeight()/2) であり、4 つのプロセス ポイントがあるため、4 つの二次方程式メソッドを記述する必要があり、4 つの終点があります。
方法:
@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);
}
効果:
効果は出ています。曲線を動かしてみましょう。これは簡単な方法です。曲線を長くして、画面の左側を超えてから、右側に移動します。ValueAnimator を使用します。
理論上のグラフィック:
このような曲線を描き、x 軸のオフセットを連続的に変更します。上記のコードは次のとおりです。
@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);
}
もちろんoffsetは初期値を0にしておく必要がありますが、カーブの半分が画面左側にあり見えないので効果はあまり変わりません。
アニメーション効果を追加する:
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();
}
onLayout() メソッドに xcontroller() メソッドを追加します。
レンダリング:
ダイナミック カーブ コンプリート
次は塗りつぶしの色です。
ブラシ スタイルを FILL に変更します
上記のコード:
@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);
}
レンダリング:
なぜ上向きなのか、以下に紹介します。
このタイプのスーパーインポーズされた PorterDuff.Mode メソッドと呼ばれる Android のモードがあります。
検索可能な Android キャンバス オーバーレイについて詳しく知りたい場合は、ここでは説明しません。
重ね合わせとは、2 つのグラフィックスの交点を意味します。私が使用する方法は、サーフェスの重ね合わせのような円の CLEAR モードを削除することです。
最初に円を描きます:
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);
まず透明レイヤーについてですが、元のキャンバスにはオーバーレイ効果が作成されていないため、キャンバスのオーバーレイを使用することはできません.シート用のレイヤーを作成するのが最善です(画面全体の透明レイヤーを作成しました) )
完全なコード:
@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);
}
レンダリング:
完成した静的出力
ここで、いくつかの動的なものを追加する必要があります: ベースライン (曲線がゆっくりと上昇)、曲線の密度など。これは簡単なデモです。
完全なコード:
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;
}
}
プロジェクトアドレス: https://github.com/yzbbanban/TestView