1. 程式人生 > >Path特效之PathMeasure打造萬能路徑動效

Path特效之PathMeasure打造萬能路徑動效

rep flag fill pub tin 我們 set mcu ext

前面兩篇文章主要講解了 Path 的概念和基本使用,今天我們一起利用 Path 做個比較實用的小例子;

上一篇我們使用 Path 繪制了一個小桃心,我們這一篇繼續圍繞著這個小桃心進行展開:

技術分享


--------------------------------------------------

如果你想看 GAStudio Github主頁,請戳這裏
如果你想看 GAStudio更多技術文章,請戳這裏
QQ技術交流群:277582728;

--------------------------------------------------


如果對這個桃心繪制有問題或有興趣的同學,可以鏈接到 Path相關方法講解(二),此時我們的需求是這樣的:

假定我們現在是一個婚戀產品,有一個“心動”的功能,用戶點擊“心動”按鈕的時候,有一個光點快速的沿著桃心轉一圈,然後整個桃心泛起光暈!

針對這個需求,很多人可能會想到以下方案:

不就一個光點沿著桃心跑一圈麽,既然桃心是使用貝塞爾曲線畫出來的,那麽我們就可以用對應的函數模擬出這條曲線,然後算出對應位置上的點,不斷將光點繪制到對應的位置上!

這個思路當然沒有問題,但我們還有相對簡單的方式,那就是使用 PathMeasure:

我們主要使用它兩個方法:

1.getLength() - 獲取路徑的長度

2.getPosTan(float distance, float pos[],float tan[]) - path 為 null ,返回 false

distance 為一個 0 - getLength() 之間的值,根據這個值 PathMeasure 會計算出當前點的坐標封裝到 pos 中;

上面這句話我們可以這麽來理解,不管實際 Path 多麽的復雜,PathMeasure 都相當於做了一個事情,就是把 Path “拉直”,然後給了我們一個接口(getLength)告訴我們path的總長度,然後我們想要知道具體某一點的坐標,只需要用相對的distance去取即可,這樣就省去了自己用函數模擬path,然後計算獲取點坐標的過程;

接下來,我們用代碼實現這一效果:

我們先創建一個 PathMeasure ,並將創建好的 path 作為參數傳入

[html] view plain copy
  1. mPath = new Path();
  2. mPath.moveTo(START_POINT[0], START_POINT[1]);
  3. mPath.quadTo(RIGHT_CONTROL_POINT[0], RIGHT_CONTROL_POINT[1], BOTTOM_POINT[0],
  4. BOTTOM_POINT[1]);
  5. mPath.quadTo(LEFT_CONTROL_POINT[0], LEFT_CONTROL_POINT[1], START_POINT[0], START_POINT[1]);
  6. mPathMeasure = new PathMeasure(mPath, true);

然後用一個數組紀錄點的坐標:

[html] view plain copy
  1. private float[] mCurrentPosition = new float[2];

向外暴露一個開啟動效的接口:

[html] view plain copy
  1. // 開啟路徑動畫
  2. public void startPathAnim(long duration) {
  3. // 0 - getLength()
  4. ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
  5. Log.i(TAG, "measure length = " + mPathMeasure.getLength());
  6. valueAnimator.setDuration(duration);
  7. // 減速插值器
  8. valueAnimator.setInterpolator(new DecelerateInterpolator());
  9. valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
  10. @Override
  11. public void onAnimationUpdate(ValueAnimator animation) {
  12. float value = (Float) animation.getAnimatedValue();
  13. // 獲取當前點坐標封裝到mCurrentPosition
  14. mPathMeasure.getPosTan(value, mCurrentPosition, null);
  15. postInvalidate();
  16. }
  17. });
  18. valueAnimator.start();
  19. }

實時獲取到當前點之後,將目標繪制到對應位置:

[html] view plain copy
  1. protected void onDraw(Canvas canvas) {
  2. super.onDraw(canvas);
  3. canvas.drawColor(Color.WHITE);
  4. canvas.drawPath(mPath, mPaint);
  5. canvas.drawCircle(RIGHT_CONTROL_POINT[0], RIGHT_CONTROL_POINT[1], 5, mPaint);
  6. canvas.drawCircle(LEFT_CONTROL_POINT[0], LEFT_CONTROL_POINT[1], 5, mPaint);
  7. // 繪制對應目標
  8. canvas.drawCircle(mCurrentPosition[0], mCurrentPosition[1], 10, mPaint);
  9. }

到這裏目標環繞 path 的效果就ok了,不管這條路徑簡單也好,復雜也罷,我們都可以如此簡單的完成對應的效果,而不需要自己用簡單或復雜函數模擬求解了;

完成了一步,自己提的需求還有一點就是光暈的問題,這個東西如何是好呢?切圖?! 不需要,Android 已經給我們提供了一個好用的東西 MaskFilter ,後面我就不做了,大家有興趣自己做的玩玩,只需要註意一點,MaskFilter 不支持硬件加速,記得關掉!

好了,PathMeasure 看似很簡單,但著實很有用,有了它,再結合上 Path 、Shader、ColorMatrix 等利器,我們已經可以做出很多酷炫的效果了!

最後,完整的代碼獻上,請笑納:

[html] view plain copy
  1. public class DynamicHeartView extends View {
  2. private static final String TAG = "DynamicHeartView";
  3. private static final int PATH_WIDTH = 2;
  4. // 起始點
  5. private static final int[] START_POINT = new int[] {
  6. 300, 270
  7. };
  8. // 愛心下端點
  9. private static final int[] BOTTOM_POINT = new int[] {
  10. 300, 400
  11. };
  12. // 左側控制點
  13. private static final int[] LEFT_CONTROL_POINT = new int[] {
  14. 450, 200
  15. };
  16. // 右側控制點
  17. private static final int[] RIGHT_CONTROL_POINT = new int[] {
  18. 150, 200
  19. };
  20. private PathMeasure mPathMeasure;
  21. private Paint mPaint;
  22. private Path mPath;
  23. private float[] mCurrentPosition = new float[2];
  24. public DynamicHeartView(Context context) {
  25. super(context);
  26. init();
  27. }
  28. private void init() {
  29. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  30. mPaint.setStyle(Style.STROKE);
  31. mPaint.setStrokeWidth(PATH_WIDTH);
  32. mPaint.setColor(Color.RED);
  33. mPath = new Path();
  34. mPath.moveTo(START_POINT[0], START_POINT[1]);
  35. mPath.quadTo(RIGHT_CONTROL_POINT[0], RIGHT_CONTROL_POINT[1], BOTTOM_POINT[0],
  36. BOTTOM_POINT[1]);
  37. mPath.quadTo(LEFT_CONTROL_POINT[0], LEFT_CONTROL_POINT[1], START_POINT[0], START_POINT[1]);
  38. mPathMeasure = new PathMeasure(mPath, true);
  39. mCurrentPosition = new float[2];
  40. }
  41. @Override
  42. protected void onDraw(Canvas canvas) {
  43. super.onDraw(canvas);
  44. canvas.drawColor(Color.WHITE);
  45. canvas.drawPath(mPath, mPaint);
  46. canvas.drawCircle(RIGHT_CONTROL_POINT[0], RIGHT_CONTROL_POINT[1], 5, mPaint);
  47. canvas.drawCircle(LEFT_CONTROL_POINT[0], LEFT_CONTROL_POINT[1], 5, mPaint);
  48. // 繪制對應目標
  49. canvas.drawCircle(mCurrentPosition[0], mCurrentPosition[1], 10, mPaint);
  50. }
  51. // 開啟路徑動畫
  52. public void startPathAnim(long duration) {
  53. // 0 - getLength()
  54. ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
  55. Log.i(TAG, "measure length = " + mPathMeasure.getLength());
  56. valueAnimator.setDuration(duration);
  57. // 減速插值器
  58. valueAnimator.setInterpolator(new DecelerateInterpolator());
  59. valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
  60. @Override
  61. public void onAnimationUpdate(ValueAnimator animation) {
  62. float value = (Float) animation.getAnimatedValue();
  63. // 獲取當前點坐標封裝到mCurrentPosition
  64. mPathMeasure.getPosTan(value, mCurrentPosition, null);
  65. postInvalidate();
  66. }
  67. });
  68. valueAnimator.start();
  69. }
  70. }

以上代碼的效果如下,其余效果大家自行補充:

技術分享


PathMeasure打造萬能路徑特效 - 源碼下載

Path特效之PathMeasure打造萬能路徑動效