做者:
放码过来
,地址: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源创计划”,欢迎正在阅读的你也加入,一块儿分享。