Path特效之PathMeasure打造萬能路徑動效
前面兩篇文章主要講解了 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
- mPath = new Path();
- mPath.moveTo(START_POINT[0], START_POINT[1]);
- mPath.quadTo(RIGHT_CONTROL_POINT[0], RIGHT_CONTROL_POINT[1], BOTTOM_POINT[0],
- BOTTOM_POINT[1]);
- mPath.quadTo(LEFT_CONTROL_POINT[0], LEFT_CONTROL_POINT[1], START_POINT[0], START_POINT[1]);
- mPathMeasure = new PathMeasure(mPath, true);
然後用一個數組紀錄點的坐標:
[html] view plain copy
- private float[] mCurrentPosition = new float[2];
向外暴露一個開啟動效的接口:
[html] view plain copy
- // 開啟路徑動畫
- public void startPathAnim(long duration) {
- // 0 - getLength()
- ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
- Log.i(TAG, "measure length = " + mPathMeasure.getLength());
- valueAnimator.setDuration(duration);
- // 減速插值器
- valueAnimator.setInterpolator(new DecelerateInterpolator());
- valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (Float) animation.getAnimatedValue();
- // 獲取當前點坐標封裝到mCurrentPosition
- mPathMeasure.getPosTan(value, mCurrentPosition, null);
- postInvalidate();
- }
- });
- valueAnimator.start();
- }
實時獲取到當前點之後,將目標繪制到對應位置:
[html] view plain copy
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.drawColor(Color.WHITE);
- canvas.drawPath(mPath, mPaint);
- canvas.drawCircle(RIGHT_CONTROL_POINT[0], RIGHT_CONTROL_POINT[1], 5, mPaint);
- canvas.drawCircle(LEFT_CONTROL_POINT[0], LEFT_CONTROL_POINT[1], 5, mPaint);
- // 繪制對應目標
- canvas.drawCircle(mCurrentPosition[0], mCurrentPosition[1], 10, mPaint);
- }
到這裏目標環繞 path 的效果就ok了,不管這條路徑簡單也好,復雜也罷,我們都可以如此簡單的完成對應的效果,而不需要自己用簡單或復雜函數模擬求解了;
完成了一步,自己提的需求還有一點就是光暈的問題,這個東西如何是好呢?切圖?! 不需要,Android 已經給我們提供了一個好用的東西 MaskFilter ,後面我就不做了,大家有興趣自己做的玩玩,只需要註意一點,MaskFilter 不支持硬件加速,記得關掉!
好了,PathMeasure 看似很簡單,但著實很有用,有了它,再結合上 Path 、Shader、ColorMatrix 等利器,我們已經可以做出很多酷炫的效果了!
最後,完整的代碼獻上,請笑納:
[html] view plain copy
- public class DynamicHeartView extends View {
- private static final String TAG = "DynamicHeartView";
- private static final int PATH_WIDTH = 2;
- // 起始點
- private static final int[] START_POINT = new int[] {
- 300, 270
- };
- // 愛心下端點
- private static final int[] BOTTOM_POINT = new int[] {
- 300, 400
- };
- // 左側控制點
- private static final int[] LEFT_CONTROL_POINT = new int[] {
- 450, 200
- };
- // 右側控制點
- private static final int[] RIGHT_CONTROL_POINT = new int[] {
- 150, 200
- };
- private PathMeasure mPathMeasure;
- private Paint mPaint;
- private Path mPath;
- private float[] mCurrentPosition = new float[2];
- public DynamicHeartView(Context context) {
- super(context);
- init();
- }
- private void init() {
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setStyle(Style.STROKE);
- mPaint.setStrokeWidth(PATH_WIDTH);
- mPaint.setColor(Color.RED);
- mPath = new Path();
- mPath.moveTo(START_POINT[0], START_POINT[1]);
- mPath.quadTo(RIGHT_CONTROL_POINT[0], RIGHT_CONTROL_POINT[1], BOTTOM_POINT[0],
- BOTTOM_POINT[1]);
- mPath.quadTo(LEFT_CONTROL_POINT[0], LEFT_CONTROL_POINT[1], START_POINT[0], START_POINT[1]);
- mPathMeasure = new PathMeasure(mPath, true);
- mCurrentPosition = new float[2];
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.drawColor(Color.WHITE);
- canvas.drawPath(mPath, mPaint);
- canvas.drawCircle(RIGHT_CONTROL_POINT[0], RIGHT_CONTROL_POINT[1], 5, mPaint);
- canvas.drawCircle(LEFT_CONTROL_POINT[0], LEFT_CONTROL_POINT[1], 5, mPaint);
- // 繪制對應目標
- canvas.drawCircle(mCurrentPosition[0], mCurrentPosition[1], 10, mPaint);
- }
- // 開啟路徑動畫
- public void startPathAnim(long duration) {
- // 0 - getLength()
- ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
- Log.i(TAG, "measure length = " + mPathMeasure.getLength());
- valueAnimator.setDuration(duration);
- // 減速插值器
- valueAnimator.setInterpolator(new DecelerateInterpolator());
- valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (Float) animation.getAnimatedValue();
- // 獲取當前點坐標封裝到mCurrentPosition
- mPathMeasure.getPosTan(value, mCurrentPosition, null);
- postInvalidate();
- }
- });
- valueAnimator.start();
- }
- }
以上代碼的效果如下,其余效果大家自行補充:
PathMeasure打造萬能路徑特效 - 源碼下載
Path特效之PathMeasure打造萬能路徑動效