PathMeasure的基本使用

PathMeasure顧名思義是Path的一個測量工具類,可以對Path繪製的路徑進行測量、裁剪等操作;在使用的時候直接new一個PathMeasure對象就可以了,系統提供了兩種類型的構造方法:

//無參構造
public PathMeasure() {
    mPath = null;
    native_instance = native_create(0, false);
}
//有參構造
public PathMeasure(Path path, boolean forceClosed) {
    // The native implementation does not copy the path, prevent it from being GC'd
    mPath = path;
    native_instance = native_create(path != null ? path.readOnlyNI() : 0,
                                forceClosed);
}

公共方法:

//關聯一個Path
void setPath(Path path, boolean forceClosed)    
//是否閉合
boolean isClosed()  
//獲取Path的長度
float   getLength() 
//跳轉到下一個輪廓
boolean nextContour()   
//截取片段
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
//獲取指定長度的位置座標及該點 切線值tangle  
boolean getPosTan(float distance, float[] pos, float[] tan)
//獲取指定長度的位置座標及該點Matrix(矩陣)
boolean getMatrix(float distance, Matrix matrix, int flags) 

如果創建對象的時候使用的是無參構造,可以通過setPath方法關聯Path;

如果創建對象的時候使用的是有參構造,在傳入第二個參數的時候需要注意;

forceClosed=false :path繪製沒有關閉的話就不會進行測量
forceClosed=true:不管path繪製是否關閉,都會進行測量計算長度
//平移畫布  viewWidth--->view寬度的一半   viewHeight--->view高度的一半
canvas.translate(viewWidth / 2, viewHeight / 2);
Path path = new Path();
path.lineTo(0, 100);
path.lineTo(100, 100);
path.lineTo(100, 0);

PathMeasure measure1 = new PathMeasure(path, false);
PathMeasure measure2 = new PathMeasure(path, true);
Log.e("TAG", "measure1---false-->" + measure1.getLength());
Log.e("TAG", "measure2---true-->" + measure2.getLength());

canvas.drawPath(path, paint);

看效果path並沒有閉合;
這裏寫圖片描述

但是measure1和measure2由於第二參數的不同,在通過getLength()方法獲取path路徑長度的時候結果就不一樣;

這裏寫圖片描述

measure1所獲取的就是path繪製的長度,measure2獲取的是path繪製的長度及未必閉合處的長度;所有在使用有參構造的時候第二個參數一般傳入false。

nextContour—>跳轉到下一個輪廓

Path path = new Path();
//多路徑的效果需要關閉硬件加速
path.addRect(-100, -100, 100, 100, Path.Direction.CW);
path.addRect(-50, -50, 50, 50, Path.Direction.CW);

PathMeasure pathMeasure = new PathMeasure(path, false);
//獲取下一個路徑,有可能沒有多個路徑了,返回false
float length = pathMeasure.getLength();
boolean nextContour = pathMeasure.nextContour();//獲取下一個路徑,有可能沒有多個路徑了,返回false
float length2 = pathMeasure.getLength();
Log.i("TAG", "length1:" + length);
Log.i("TAG", "length2:" + length2);
canvas.drawPath(path, paint);

這裏寫圖片描述

在使用繪製多條路徑的時候需要注意,要關閉硬件加速,可以在AndroidManifest.xml文件中的application中設置

android:hardwareAccelerated="false"

也可以使用View進行設置關閉硬件加速。

getSegment—>截取片段

Path path=new Path();
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
PathMeasure pathMeasure=new PathMeasure(path,false);
canvas.drawPath(path, paint);

Path dst=new Path();
dst.lineTo(-300,-300);
pathMeasure.getSegment(200,600,dst,true);
paint.setColor(Color.RED);
canvas.drawPath(dst, paint);

這裏寫圖片描述

getSegment()方法可以和Path的lineTo()一起使用的,不過在調用getSegment()方法時,第四個參數是傳入一個boolean值,傳入false和true的效果是不一樣的;

第四個參數false或true:代表該起始點是否是上一個的結束點(是否保持連續性)。

將上面的true改爲false
這裏寫圖片描述

getPosTan—>獲取指定長度的位置座標及該點 切線值tangle

Path path=new Path();
path.addCircle(0,0,300, Path.Direction.CW);

PathMeasure pathMeasure=new PathMeasure(path,false);

float [] pos=new float[2];
float [] tan=new float[2];
pathMeasure.getPosTan(pathMeasure.getLength()/4,pos,tan);
Log.i("TAG", "position:x-"+pos[0]+", y-"+pos[1]);
Log.i("TAG", "tan:x-"+tan[0]+", y-"+tan[1]);
canvas.drawPath(path, paint);

這裏寫圖片描述

打印的值:

這裏寫圖片描述

Path+PathMeasure自定義搜索放大鏡效果

這裏寫圖片描述
這個效果的話主要涉及到外圓環、放大鏡的圓、放大鏡的手柄這三個東西的繪製,而這三個東西有涉及到幾種狀態,普通、放大鏡開始執行動畫、開始搜索、搜索結束四種狀態的繪製;代碼如下:

public class SearchView extends View {
    //畫筆 
    private Paint mPaint;
    //放大鏡和外部圓環
    private Path pathSearch;
    private Path pathCircle;
    //測量Path並截取工具類
    private PathMeasure mMeasure;
    // 動畫數值(用於控制動畫狀態,因爲同一時間內只允許有一種狀態出現,具體數值處理取決於當前狀態)
    private float mAnimatorValue = 0;
    //用於控制動畫狀態轉換
    private Handler mAnimatorHandler;
    //動效過程監聽
    private ValueAnimator.AnimatorUpdateListener mUpdateListener;
    private Animator.AnimatorListener mAnimatorListener;
    //當前繪製的狀態
    private State mCurrentState = State.NONE;
    //判斷是否已經搜索結束
    private boolean isOver = false;

    //視圖的所有狀態
    public enum State {
        NONE,
        STARTING,
        SEARCHING,
        ENDING
    }

    //控制各個過程的動畫
    //開始動畫
    private ValueAnimator mStartingAnimator;
    //搜索動畫
    private ValueAnimator mSearchingAnimator;
    //搜索結束動畫
    private ValueAnimator mEndingAnimator;
    private int count = 0;
    //默認動畫的時長
    private int defaultDuration = 2000;
    private int viewHeight;
    private int viewWidth;

    public SearchView(Context context) {
        this(context, null);
    }

    public SearchView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
        initPath();
        initListener();
        initHandler();
        initAnimator();

        // 進入開始動畫
        mCurrentState = State.STARTING;
        mStartingAnimator.start();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //視圖大小改變時會回調
        viewWidth = w;
        viewHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //進行繪製
        drawSearch(canvas);
    }

    /**
     * 進行繪製
     *
     * @param canvas 對應的畫布
     */
    private void drawSearch(Canvas canvas) {
        mPaint.setColor(Color.WHITE);

        //平移畫布
        canvas.translate(viewWidth / 2, viewHeight / 2);
        canvas.drawColor(Color.parseColor("#0082D7"));
        //根據狀態進行繪製
        switch (mCurrentState) {
            case NONE:
                //普通狀態
                canvas.drawPath(pathSearch, mPaint);
                break;
            case STARTING:
                //開始狀態
                mMeasure.setPath(pathSearch, false);
                Path dts = new Path();
                //進行裁剪
                mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dts,true);
                //繪製截取部分
                canvas.drawPath(dts,mPaint);
                break;
            case SEARCHING:
                //搜索狀態
                mMeasure.setPath(pathCircle,false);
                Path dts1 = new Path();
                float stop = mMeasure.getLength() * mAnimatorValue;
                float start=(float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * 200f));
                mMeasure.getSegment(start,stop,dts1,true);
                //繪製截取部分
                canvas.drawPath(dts1,mPaint);
                break;
            case ENDING:
                //結束狀態
                mMeasure.setPath(pathSearch, false);
                Path dst2 = new Path();
                mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst2, true);
                canvas.drawPath(dst2, mPaint);
                break;
        }
    }

    /**
     * 初始化屬性動畫
     */
    private void initAnimator() {
        //初始化
        mStartingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
        mSearchingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
        mEndingAnimator = ValueAnimator.ofFloat(1, 0).setDuration(defaultDuration);

        //動畫更新監聽
        mStartingAnimator.addUpdateListener(mUpdateListener);
        mSearchingAnimator.addUpdateListener(mUpdateListener);
        mEndingAnimator.addUpdateListener(mUpdateListener);

        //動畫執行監聽
        mStartingAnimator.addListener(mAnimatorListener);
        mSearchingAnimator.addListener(mAnimatorListener);
        mEndingAnimator.addListener(mAnimatorListener);
    }

    /**
     * 初始化Handler 並根據消息更新繪製狀態
     */
    private void initHandler() {
        mAnimatorHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (mCurrentState) {
                    case STARTING://開始
                        // 從開始動畫轉換好搜索動畫
                        isOver = false;
                        mCurrentState = State.SEARCHING;
                        //移除開始動畫的所有監聽
                        mStartingAnimator.removeAllListeners();
                        //開始搜索動畫
                        mSearchingAnimator.start();
                        break;
                    case SEARCHING:
                        // 如果搜索未結束 則繼續執行搜索動畫
                        if (isOver) {
                            mSearchingAnimator.start();
                            count++;
                            // count大於2則進入結束狀態
                            if (count > 2) {
                                isOver = true;
                            }
                        } else {
                            // 如果搜索已經結束 則進入結束動畫
                            mCurrentState = State.ENDING;
                            mEndingAnimator.start();
                        }
                        break;
                    case ENDING://結束
                        // 從結束動畫轉變爲無狀態
                        mCurrentState = State.NONE;
                        break;
                }
            }
        };
    }

    /**
     * 動畫監聽
     */
    private void initListener() {
        mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimatorValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        };
        mAnimatorListener = new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                //動畫開始回調
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                //動畫結束回調
                //發送消息通知動畫已經結束
                mAnimatorHandler.sendEmptyMessage(0);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                //動畫取消回調
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                //動畫重複回調
            }
        };
    }

    /**
     * 初始化Path
     */
    private void initPath() {
        pathSearch = new Path();
        pathCircle = new Path();
        mMeasure = new PathMeasure();
        // 注意,不要到360度,否則內部會自動優化,測量不能取到需要的數值
        // 放大鏡圓環
        RectF oval1 = new RectF(-50, -50, 50, 50);
        pathSearch.addArc(oval1, 45, 359.9f);

        // 外部圓環
        RectF oval2 = new RectF(-100, -100, 100, 100);
        pathCircle.addArc(oval2, 45, -359.9f);

        float[] pos = new float[2];
        // 放大鏡把手的位置
        //設置關聯Path
        mMeasure.setPath(pathCircle, false);
        //獲取座標
        mMeasure.getPosTan(0, pos, null);
        // 放大鏡把手
        pathSearch.lineTo(pos[0], pos[1]);
    }

    /**
     * 初始化畫筆
     */
    private void initPaint() {
        mPaint = new Paint();
        //設置樣式爲描邊
        mPaint.setStyle(Paint.Style.STROKE);
        //設置畫筆爲白色
        mPaint.setColor(Color.WHITE);
        //設置畫筆大小
        mPaint.setStrokeWidth(15);
        //設置畫帽
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        //設置抗鋸齒
        mPaint.setAntiAlias(true);
    }
}

自定義View的代碼都在上面,都有表明註釋,在佈局文件中直接使用就可以了;

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.pathmeasuretest.MainActivity">

    <com.pathmeasuretest.SearchView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
發佈了80 篇原創文章 · 獲贊 32 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章