1. 程式人生 > >android自定義View(一)、正弦波水波紋

android自定義View(一)、正弦波水波紋

文章目錄

1、正弦曲線知識

對這個初中知識遺忘了的可以先看看正弦曲線百度百科詞條方便加深理解。
在這裡插入圖片描述
正弦曲線可表示為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();