1、正弦曲线知识
对这个初中知识遗忘了的可以先看看正弦曲线百度百科词条方便加深理解。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOP79Chs-1613616058592)(https://gss1.bdstatic.com/-vo3dSag_xI4khGkpoWK1HF6hhy/baike/w=268;g=0/sign=c17c02b9cebf6c81f7372bee8405d608/a1ec08fa513d26970410ee8c57fbb2fb4216d824.jpg)]
正弦曲线可表示为y=Asin(ωx+φ)+k
,定义为函数y=Asin(ωx+φ)+k
在直角坐标系上的图象,其中sin
为正弦符号,x是直角坐标系x轴上的数值,y是在同一直角坐标系上函数对应的y值,k、ω和φ是常数(k、ω、φ∈R且ω≠0)
- A——振幅,当物体作轨迹符合正弦曲线的直线往复运动时,其值为行程的1/2。
- (ωx+φ)——相位,反映变量y所处的状态。
- φ——初相,x=0时的相位;反映在坐标系上则为图像的左右移动。
- k——偏距,反映在坐标系上则为图像的上移或下移。
- ω——角速度, 控制正弦周期(单位弧度内震动的次数)。
2、静态正弦曲线绘制
其实如果理解了上面的公式,要绘制出来一条正弦曲线就很简单了,通过y=Asin(ωx+φ)+k
,我们可以知道随着x
变化的每一个y
坐标位置,然后将这些细密的点连接起来,就是一条正弦曲线了。下面就是静态绘制过程,最后会放出完整的代码。
//角频率, 控制周期
private double angularFrequency = 8 * 1.0f / 4;
//初次相位角,
private double phaseAngle = 0 * Math.PI / 180 + Math.PI / 2 * -1;
...
linePath.moveTo(0, height);
for (int i = 0; i < width; i++) {
double angle = i * 1F / width * 2 * Math.PI;
double y = amplitude * Math.sin(angle * angularFrequency + phaseAngle);
linePath.lineTo(i, (float) (y + height / 2));
}
linePath.lineTo(width, height + 1);
canvas.drawPath(linePath, linePaint);
...
通过点点相连,就画出来了一个静态的正弦曲线了。
在图片中,我们分别设置了控制按钮,下面先看看当振幅(A)、角速度(w)、初相位(φ)变化的时候的时候,会发生什么变化。看完下面的效果,相信你对于如何绘制动态的正弦曲线也会一目了然。
首先控制振幅变化(振幅影响到的是y轴变化)
角速度会影响到正弦曲线的周期变化
而初相位影响表现为左右移动
3、动态正弦曲线绘制
如果你仔细看了上线的效果,我相信你已经了解了怎么去让正弦曲线动起来,实现水波纹效果了。
只需要动态调整初相位即可。
public void setPhaseAngle(int progress) {
phaseAngle = progress * Math.PI / 180 + Math.PI / 2 * -1;
invalidate();
}
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
ValueAnimator animator = ValueAnimator.ofInt(0, metrics.widthPixels);
animator.setDuration(10000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
sinusoidalWaveView.setPhaseAngle((Integer) animation.getAnimatedValue());
}
});
animator.start();
来看看效果图
4、完整源码
public class SinusoidalWaveView extends View {
private int width;
private int height;
private Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path linePath;
private double amplitude;
//角频率, 控制周期
private double angularFrequency = 8 * 1.0f / 4;
//初次相位角,
private double phaseAngle = 0 * Math.PI / 180 + Math.PI / 2 * -1;
public SinusoidalWaveView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
linePaint.setColor(Color.parseColor("#23a393"));
linePaint.setStyle(Paint.Style.FILL);
linePath = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.width = w;
this.height = h;
amplitude = height / 2;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(widthMeasureSpec / 2, MeasureSpec.EXACTLY));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
linePath.reset();
linePath.moveTo(0, height);
for (int i = 0; i < width; i++) {
double angle = i * 1F / width * 2 * Math.PI;
double y = amplitude * Math.sin(angle * angularFrequency + phaseAngle);
linePath.lineTo(i, (float) (y + height / 2));
}
linePath.lineTo(width, height + 1);
canvas.drawPath(linePath, linePaint);
}
public void setAmplitude(int progress) {
amplitude = progress * 1.0F / 100 * height / 2;
invalidate();
}
public void setAngularFrequency(int progress) {
angularFrequency = progress * 1.0F / 4;
invalidate();
}
public void setPhaseAngle(int progress) {
phaseAngle = progress * Math.PI / 180 + Math.PI / 2 * -1;
invalidate();
}
}
SeekBar seekbarW = getView().findViewById(R.id.seekbarW);
seekbarW.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
sinusoidalWaveView.setAngularFrequency(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
SeekBar seekBarR = getView().findViewById(R.id.seekBarR);
seekBarR.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
sinusoidalWaveView.setPhaseAngle(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
SeekBar seekBarA = getView().findViewById(R.id.seekBarA);
seekBarA.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
sinusoidalWaveView.setAmplitude(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
ValueAnimator animator = ValueAnimator.ofInt(0, metrics.widthPixels);
animator.setDuration(10000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
sinusoidalWaveView.setPhaseAngle((Integer) animation.getAnimatedValue());
}
});
animator.start();