本文深入分析 BezierDemo 專案,揭示如何利用二階貝塞爾曲線實現類似 QQ 未讀消息拖拽的「粘連拉伸」動畫效果。
BezierView 繼承 FrameLayout,內部包含:
Path 和 Paint。(startX, startY)、拖拽圓中心 (x, y)、控制點 (anchorX, anchorY)。radius,隨距離增大而縮小。ImageView:tipImageView(數字提示)、exploredImageView(拉斷後動畫)。calculate() - 幾何計算核心根據兩圓心距離計算動態半徑:
float distance = sqrt((y-startY)^2 + (x-startX)^2);
radius = -distance/15 + DEFAULT_RADIUS;
if (radius < 9) { isAnimStart = true; /* 觸發拉斷動畫 */ }
計算四邊形四個切點(利用三角函數):
float angle = atan2(y - startY, x - startX);
float offsetX = radius * sin(angle);
float offsetY = radius * cos(angle);
// 四個切點座標
x1 = startX - offsetX; y1 = startY + offsetY;
x2 = x - offsetX; y2 = y + offsetY;
x3 = x + offsetX; y3 = y - offsetY;
x4 = startX + offsetX; y4 = startY - offsetY;
構建閉合路徑(核心):
path.reset();
path.moveTo(x1, y1);
path.quadTo(anchorX, anchorY, x2, y2); // 第一條貝塞爾曲線
path.lineTo(x3, y3); // 直線連接
path.quadTo(anchorX, anchorY, x4, y4); // 第二條貝塞爾曲線
path.lineTo(x1, y1); // 閉合
精髓:兩條二階貝塞爾曲線共用同一控制點 (anchorX, anchorY),控制點位於兩圓心連線中點。隨手指拖動,控制點移動,曲線內凹形成「橡皮筋」效果。
onDraw() - 繪製邏輯若未拉斷且正在觸摸,調用 calculate() 更新路徑,繪製 path、兩個圓。若已拉斷,播放動畫。
onTouchEvent() - 交互處理記錄手指座標 (x, y),計算控制點 anchor = (start + current) / 2,調用 invalidate() 觸發重繪。處理 ACTION_DOWN 判斷是否點擊提示圖,ACTION_UP/CANCEL 重置狀態。
作者通過修改 DEFAULT_RADIUS 放大圓圈、將 Paint.Style 改為 STROKE 顯示線框、隱藏圖片和圓圈僅繪製 Path,直觀展示了路徑由兩條貝塞爾曲線和兩條直線組成的閉合圖形,以及控制點如何「拖住」兩條曲線產生粘連效果。
粘連效果的關鍵:同一個控制點(中間點)「拖住」兩條二階貝塞爾曲線。
動態調整半徑進一步增強真實感。這個 Demo 代碼簡潔,是學習 Android 自定義 View、貝塞爾曲線動畫的絕佳入門案例。