做者:
放碼過來
,地址:http://blog.huangyuanlove.com前端
先上圖
咱們能夠利用路徑動畫實現不少好玩的東西,好比上面圖中的相似支付寶支付完成的動畫。主要用到了PathMeasur
,ValueAnumator
這兩個類
程序員
PathMeasure
相似於一個計算器,能夠計算一些和路徑相關的東西。兩種初始化方式:web
PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(path,false);
或者編程
PathMeasure pathMeasure = new PathMeasure(path,false);
getLength()
用來獲取路徑長度,而且獲取到的是當前曲線的長度,而不是整個 Path 的長度canvas
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(150, 150);
path.addRect(-50,-50,50,50,Path.Direction.CW);
path.addRect(-100,-100,100,100,Path.Direction.CW);
path.addRect(-120,-120,120,120,Path.Direction.CW);
canvas.drawPath(path, paint);
pathMeasure.setPath(path,false);
do{
Log.e("huangyuan",pathMeasure.getLength()+"");
}while (pathMeasure.nextContour());
}
log以下:api
E/huangyuan: 400.0
E/huangyuan: 800.0
E/huangyuan: 960.0
pathMeasure.nextContour ()
獲得的曲線的順序與添加到 Path 中的順序相同數組
getSegment()
函數定義緩存
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)微信
懶得翻譯,本身看吧app
/**
* Given a start and stop distance, return in dst the intervening
* segment(s). If the segment is zero-length, return false, else return
* true. startD and stopD are pinned to legal values (0..getLength()).
* If startD >= stopD then return false (and leave dst untouched).
* Begin the segment with a moveTo if startWithMoveTo is true.
*/
咱們能夠這麼用
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(150, 150);
path.addRect(-50,-50,50,50,Path.Direction.CW);
Path dst = new Path();
pathMeasure.setPath(path,false);
pathMeasure.getSegment(0,150,dst,true);
canvas.drawPath(dst,paint);
}
獲得:
若是 dst 路徑不爲空
Path dst = new Path();
dst.lineTo(10,100);
獲得
若是 dst 路徑不空,startWithMoveTo
爲 false
,獲得
路徑加載動畫
public TestPathMeasure(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//禁用硬件加速
setLayerType(LAYER_TYPE_SOFTWARE,null);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.STROKE);
dstPath = new Path();
circlePath = new Path();
circlePath.addCircle(100,100,50,Path.Direction.CW);
pathMeasure = new PathMeasure(circlePath,true);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentAnimValue = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(2000);
valueAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float length = pathMeasure.getLength();
float stop = pathMeasure.getLength() * currentAnimValue;
float start = (float) (stop- ((0.5- Math.abs(currentAnimValue-0.5))*length));
dstPath.reset();
pathMeasure.getSegment(start,stop,dstPath,true);
canvas.drawPath(dstPath,paint);
}
咱們能夠在路徑動畫上加個箭頭,
public class TestPathMeasure extends View {
private Paint paint;
private Path path;
private Path dstPath;
private Path circlePath;
private PathMeasure pathMeasure;
private float currentAnimValue;
private Bitmap icChevronRight;
private float[] pos = new float[2];
private float[] tan = new float[2];
public TestPathMeasure(Context context) {
super(context);
init(context);
}
public TestPathMeasure(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TestPathMeasure(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
//禁用硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.STROKE);
dstPath = new Path();
circlePath = new Path();
circlePath.addCircle(300, 300, 150, Path.Direction.CW);
pathMeasure = new PathMeasure(circlePath, true);
BitmapFactory.Options options = new BitmapFactory.Options();
icChevronRight = BitmapFactory.decodeResource(context.getResources(), R.drawable.right);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentAnimValue = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(2000);
valueAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float length = pathMeasure.getLength();
float stop = pathMeasure.getLength() * currentAnimValue;
float start = (float) (stop - ((0.5 - Math.abs(currentAnimValue - 0.5)) * length));
dstPath.reset();
pathMeasure.getSegment(start, stop, dstPath, true);
canvas.drawPath(dstPath, paint);
pathMeasure.getPosTan(stop, pos, tan);
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI);
Matrix matrix = new Matrix();
// matrix.postRotate(degrees,icChevronRight.getWidth()/2,icChevronRight.getHeight()/2);
// matrix.postTranslate(pos[0] -icChevronRight.getWidth()/2,pos[1]-icChevronRight.getHeight()/2);
pathMeasure.getMatrix(stop, matrix, PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);
matrix.preTranslate(-icChevronRight.getWidth() / 2, -icChevronRight.getHeight() / 2);
canvas.drawBitmap(icChevronRight, matrix, paint);
}
}
這裏主要用到了 Matrix
和 getPosTan ()
, 用來獲得路徑上某一長度的位置以及該位置的正切值。函數原型以下:
/**
* Pins distance to 0 <= distance <= getLength(), and then computes the
* corresponding position and tangent. Returns false if there is no path,
* or a zero-length path was specified, in which case position and tangent
* are unchanged.
*
* @param distance The distance along the current contour to sample
* @param pos If not null, returns the sampled position (x==[0], y==[1])
* @param tan If not null, returns the sampled tangent (x==[0], y==[1])
* @return false if there was no path associated with this measure object
*/
public boolean getPosTan(float distance, float pos[], float tan[])
參數解釋:
-
float distance:
距離 Path 起點的長度,取值範圍 0<=distance<=getLength -
float [] pos:
該點的座標值,pos [0] 表示 x 座標,pos [y] 表示 y 座標 -
float [] tan:
該點的正切值。
在上面的代碼中須要注意的是
-
pos
、tan
數組在使用時必須先使用 new 關鍵詞分配存儲空間,而PathMeasure.getPosTan
函數只會向數組中的元素賦值。 -
經過 Math.atan2 (tan [1],tan [0])
獲得的是弧度值,而不是角度 -
先利用 matrix.postRotate 將圖片旋轉指定角度,而後用 matix.postTranslate
將圖片移動到當前路徑最前端 (註釋掉的那兩句) -
pathMeasure
有個getMatrix
函數,是對咱們本身實現的那種方式的封裝,咱們只須要將圖片移動一下就行了
山寨支付寶支付成功動畫
就是先畫一個圓,而後圓形裏面畫個對勾。。。。。很簡陋的一種實現方式
public class AliPaySuccess extends View {
private Paint paint;
private Path dstPath;
private Path circlePath;
private int centerX = 500;
private int centerY = 500;
private int radius = 250;
private PathMeasure pathMeasure;
private float currentAnimValue;
private boolean switchLine;
public AliPaySuccess(Context context) {
super(context);
init();
}
public AliPaySuccess(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public AliPaySuccess(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
setLayerType(LAYER_TYPE_SOFTWARE, null);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.STROKE);
dstPath = new Path();
circlePath = new Path();
circlePath.addCircle(centerX,centerY,radius,Path.Direction.CW);
circlePath.moveTo(centerX-radius/2,centerY);
circlePath.lineTo(centerX,centerY+radius/2);
circlePath.lineTo(centerX+radius/2,centerY-radius/3);
pathMeasure = new PathMeasure(circlePath,false);
ValueAnimator animator = ValueAnimator.ofFloat(0,2);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentAnimValue = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(2000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(currentAnimValue<1){
float stop = pathMeasure.getLength()*currentAnimValue;
pathMeasure.getSegment(0,stop,dstPath,true);
}else if (currentAnimValue >1 && !switchLine){
pathMeasure.getSegment(0,pathMeasure.getLength(),dstPath,true);
switchLine = true;
pathMeasure.nextContour();
}else {
float stop = pathMeasure.getLength()*(currentAnimValue-1);
pathMeasure.getSegment(0,stop,dstPath,true);
}
canvas.drawPath(dstPath,paint);
}
}
首先初始化各類參數:畫筆、路徑和動畫
-
初始化路徑時,先添加了外面的圓形,而後是裏面的對勾。設置動畫從
0~2
,0-1
時畫圓,1-2 時畫對勾。 -
重寫
onDraw
,判斷當前的動畫值,在0-1
時畫圓,當動畫值第一次大於 1 時,切換到對號那條線上。 -
這裏的
currentAnimValue
不必定會有等於 1 的時候,至少我執行了十幾遍也沒出現一次。因此取的是第一次大於時切換。
這裏面的數值和顏色之類的屬性都是直接寫死的,應該經過 xml 文件讀取。
以上就是本文所有內容,求個三連,奧利給!
---END---
更文不易,點個「在看」支持一下👇
本文分享自微信公衆號 - 技術最TOP(Tech-Android)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。