今天在學習貝塞爾曲線的過程中覺得很新奇,特別是之前覺得很神祕的東西一下全部融會貫通了,爲了實踐,特地寫了一個demo——波浪圖,先看效果圖:
紙上得來終覺淺,絕知此事要躬行!本來覺得挺簡單的一件事結果各種坑!什麼,你說貝塞爾曲線不簡單?no,no,看看大神們是怎麼總結的?
二階貝塞爾曲線形成原理:
1.連接 A,B 形成 AB 線段,連接 B,C 形成 BC 線段。
2.在 AB 線段取一個點 D,BC 線段取一個點 E ,使其滿足條件: AD/AB = BE/BC,連接 D,E 形成線段 DE。
3.在 DE 取一個點 F,使其滿足條件:AD/AB = BE/BC = DF/DE。
4.而滿足這些條件的所有的F點所形成的軌跡就是二階貝塞爾曲線,動態過程如下:
上面的代碼解釋得很清楚了,已知D,可以求得E,已知D、E可以求得F,如果D爲一個變量,則F在二維平面就是一條曲線,如圖四。
那麼知道了二階貝塞爾曲線的原理以後,怎麼運用呢?
方法預覽:
public void quadTo (float x1, float y1, float x2, float y2)
怎麼用:
因爲二階貝塞爾曲線需要三個點才能確定,所以quadTo方法中的四個參數分別是確定第二,第三的點的。第一個點就是path上次操作的點。
現在用一個實例來練習下這個方法:水波紋效果:
實現思路:
我看見這幅圖的第一思路是首先得到波浪長度mWL、波浪中心點mCenterY。因爲圖中的波浪寬度爲整個屏幕寬度,所以我們需要得到屏幕的寬度。百度一下方法就出來了:
方法一:
//獲取屏幕的寬度
public static int getScreenWidth(Context context) {
WindowManager manager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
return display.getWidth();
}
//獲取屏幕的高度
public static int getScreenHeight(Context context) {
WindowManager manager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
return display.getHeight();
}
方法二:
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int screenWidth = dm.widthPixels;
int screenHeight = dm.heightPixels;
由於第二種方法只能在Activity裏面使用,所以這裏使用第一種方法。但是在寫的過程中我又發現一個提醒:
有強迫症的我馬上去官網查了一下,發現官網推薦的是另一個方法,於是馬上就改用了另一個方法:
於是,我們現在就可以根據波浪的寬度和中心點繪製一條靜態的波浪線,代碼如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
mPath.moveTo(-mWL, mCenterY); //將path操作的起點移動到(-mWL,mCenterY)
mPath.quadTo((-mWL * 3 / 4) , mCenterY + 60, (-mWL / 2), mCenterY); //畫出第一段波紋的第一條曲線
mPath.quadTo((-mWL / 4) , mCenterY - 60, 0, mCenterY); //畫出第一段波紋的第二條曲線
mPath.quadTo((mWL /4) , mCenterY + 60, (mWL / 2), mCenterY); //畫出第二段波紋的第一條曲線
mPath.quadTo((mWL * 3/ 4) , mCenterY - 60, mWL, mCenterY); //畫出第二段波紋的第二條曲線
mPath.lineTo(mScreenWidth, mScreenHeight);
mPath.lineTo(0, mScreenHeight);
mPath.close();
canvas.drawPath(mPath,mPaint);
}
查看加載效果的時候發現一個坑,就是榮耀的真機顯示繪製結果的時候左邊居然有一條很寬的線,這個時候爲了檢查不是貝塞爾曲線的問題,我還在貝塞爾曲線X方向起點到終點繪製了一條直線,但是依然如此。
然後我用自己的小米5測試了一下,居然完全沒有任何問題:
雖然後來發現是因爲使用了ConstraintLayout佈局,然後自定義控件左邊距有8dp,然後給自定義控件寫死了寬度,因此小米5的寬度剛剛合適,,,,,
修改了以後正常了,於是繼續將靜態的波浪線調整成動態的波浪線。其實邏輯是很簡單,就是給自定義View一個點擊事件,然後在時間中啓動一個屬性動畫,動態的修改波浪線水平方向右移的位置,全部代碼寫出來:
/**
* 自定義波浪圖
* Created by 魏興 on 2017/6/12.
*/
public class WaveView1 extends View implements View.OnClickListener{
private static final String TAG = "WaveView";
private Context mContext;
/**
* 波紋段長度
*/
private int mWL;
/**
* 波浪高度中點
*/
private float mCenterY;
private float mScreenWidth;
private float mScreenHeight;
private int mOffset;
private int mWaveCount;
private Path mPath;
private Paint mPaint;
private Paint mCyclePaint;
public WaveView1(Context context) {
super(context);
this.mContext = context;
// init();
}
public WaveView1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
// init();
}
public WaveView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
// init();
}
private void init() {
WindowManager manager = (WindowManager) mContext .getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
Point po = new Point();
display.getSize(po);
mWL = po.x;
mCyclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCyclePaint.setColor(Color.RED);
mCyclePaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mPaint = new Paint(); // 創建畫筆
mPaint.setColor(Color.BLUE); // 畫筆顏色 - 黑色
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE); // 填充模式 - 填充
mPath = new Path();
mWL = w*3/4;
mCenterY = h/2f;
mScreenHeight = h;
mScreenWidth = w;
//加1.5:至少保證波紋有2個,至少2個才能實現平移效果
mWaveCount = (int) Math.round(mScreenWidth / mWL + 1.5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
//移到屏幕外最左邊
mPath.moveTo(-mWL + mOffset, mCenterY);
for (int i = 0; i < mWaveCount; i++) {
//正弦曲線
mPath.quadTo((-mWL * 3 / 4) + (i * mWL) + mOffset, mCenterY + 60, (-mWL / 2) + (i * mWL) + mOffset, mCenterY);
mPath.quadTo((-mWL / 4) + (i * mWL) + mOffset, mCenterY - 60, i * mWL + mOffset, mCenterY);
//測試紅點
// canvas.drawCircle((-mWL * 3 / 4) + (i * mWL) + mOffset, mCenterY + 60, 5, mCyclePaint);
// canvas.drawCircle((-mWL / 2) + (i * mWL) + mOffset, mCenterY, 5, mCyclePaint);
// canvas.drawCircle((-mWL / 4) + (i * mWL) + mOffset, mCenterY - 60, 5, mCyclePaint);
// canvas.drawCircle(i * mWL + mOffset, mCenterY, 5, mCyclePaint);
}
//填充矩形
mPath.lineTo(mScreenWidth, mScreenHeight);
mPath.lineTo(0, mScreenHeight);
mPath.close();
canvas.drawPath(mPath, mPaint);
}
@Override
public void onClick(View view) {
try {
ValueAnimator animator = ValueAnimator.ofInt(-mWL, mWL);
animator.setDuration(1000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mOffset = (int) animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
然後在實例化控件以後調用Click方法(不能直接點擊View,因爲Click方法此處並沒有響應,需要重寫boolean onTouchEvent(MotionEvent event)方法來定義Click事件——我猜的哈,反正我直接點擊的時候此處方法並沒有響應)。於是我簡單申明控件以後直接調用了Click方法。大坑出現了!
此處波浪圖並沒有動起來,而且屬性方法裏面打印的日誌顯示設置的ofint()方法參數(這個是成員變量,即波浪寬度)爲900,偏移量爲0。上網百度“屬性動畫失效”找不到資料,後來查了半天發現是因爲Activity裏面聲明控件並調用了Click方法,這個時候寬度默認爲0,因此屬性動畫的偏移量一直爲0。
於是修改調用代碼:
public class WaveActivity extends AppCompatActivity {
private WaveView1 mWave;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wave);
mWave = (WaveView1) findViewById(R.id.waveView);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
mWave.onClick(null);
}
});
}
},500);
}
}
續三階貝塞爾曲線形成原理:
1.連接 A,B 形成 AB 線段,連接 B,C 形成 BC 線段,連接 C,D 形成 CD 線段。
2.在AB線段取一個點 E,BC 線段取一個點 F,CD 線段取一個點 G,使其滿足條件: AE/AB = BF/BE = CG/CD。連接 E,F 形成線段 EF,連接 F,G 形成線段 FG。
3.在EF線段取一個點 H,FG 線段取一個點 I,使其滿足條件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG。連接 H,I 形成線段 HI。
4.在 HI 線段取一個點 J,使其滿足條件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG = HJ/HI。
5.而滿足這些條件的所有的J點所形成的軌跡就是三階貝塞爾曲線,動態過程如下:
方法預覽:
public void quadTo (float x1, float y1, float x2, float y2)
繪製圓形:
修改座標:
最終效果:
最後再來一張效果圖:
參考資料: