贝塞尔曲线——水波纹实现
1.贝塞尔曲线来源
在数学的数值分析领域中,贝赛尔曲线(Bézier曲线)是电脑图形学中相当重要的参数曲线。更高维度的广泛化贝塞尔曲线就称作贝塞尔曲面,其中贝塞尔三角是一种特殊的实例。
贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau算法开发,以稳定数值的方法求出贝塞尔曲线。
2.贝塞尔曲线公式
一阶贝塞尔曲线
B(t) = (1-t)Po + tP1 , t∈[0,1]
t的取值范围:是从p0到p1直线到达所用的时间,整体化最大为1.
其中,p0为起点,p1为终点,t表示当前时间,B(t)表示公示的结果。其函数意义为随时间t所形成的轨迹,一阶为直线匀速运动。
二阶贝塞尔曲线
B(t) = (1-t)² Po + 2t(1-t)P1 + t² P2, t ∈ [0,1]
其中,p0为起始点,p1是控制点,p2是中点。
p1控制点:有了p1,一阶的直线就变成曲线了!
三阶贝塞尔曲线
B(t) = Po(1-t)³ + 3P1 t(1-t)² + 3P2 t²(1-t) + P3 t³, t∈[0,1]
其中,P0是起始点,P3是终点,p1是第一个控制点,p2是第二个控制点。
分析:
整体由三条一阶贝塞尔曲线组成:
路线 随时间变化的点
P0---P1 Q0
P1---P2 Q1
P2---P3 Q2
然后又它们移动的点Q0、Q1、Q2有组成了2条一阶的贝塞尔曲线:
路线 随时间变化的点
Q0---Q1 R0
Q1---Q2 R1
最后由他们移动的点R0、R1构成了1条一阶贝塞尔曲线:
路线 随时间变化的点
R0 --- R1 B
就形成了最终的三阶贝塞尔曲线。
还有四阶、五阶……
3.贝塞尔曲线—quadTo
Path类里这四个方法与贝塞尔曲线有关
//二阶贝赛尔
public void quadTo(float x1, float y1, float x2, float y2)
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)
//三阶贝赛尔
public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
public void rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
其中(x1,y1)是控制点坐标,(x2,y2)是终点坐标。
那么起点:(x2-x1,y2)
完整代码
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/reset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="reset" />
<com.example.bsrdemo.MyView
android:id="@+id/myview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
自定义一个myView
package com.example.bsrdemo;
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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
public class MyView extends View {
private Paint mPaint;
private Path mPath = new Path();
private float mPreX,mPreY;
private int mItemWaveLength = 1000;
private int dx;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
public MyView(Context context) {
super(context);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX() , event.getY());
mPreX = event.getX();
mPreY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
float endX = (mPreX+event.getX())/2;
float endY = (mPreY+event.getY())/2;
mPath.quadTo(mPreX,mPreY,endX,endY);
mPreX = event.getX();
mPreY =event.getY();
invalidate();
break;
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
int originY = 300;
int halfWaveLen = mItemWaveLength/2;
mPath.moveTo(-mItemWaveLength +dx,originY);
for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){
mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0);
mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0);
}
mPath.lineTo(getWidth(),getHeight());
mPath.lineTo(0,getHeight());
mPath.close();
canvas.drawPath(mPath,mPaint);
}
public void reset(){
mPath.reset();
invalidate();
}
public void startAnim(){
final ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
animator.setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx = (int)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
MainActivity.java
ackage com.example.bsrdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyView myView = (MyView)findViewById(R.id.myview);
myView.startAnim();
Button reset_btn = findViewById(R.id.reset);
reset_btn.setOnClickListener(new View.OnClickListener( ) {
@Override
public void onClick(View v) {
myView.reset();
}
});
}
}