Android自定义View-路径动画

1,328 阅读4分钟

路径动画

1.1、初始化

初始化后设置是否闭合:

Path path = new Path();
PathMeasure pathMeasure1 = new PathMeasure();
/**
 * 路径是否闭合 forceClosed
 */
pathMeasure1.setPath(path, true);

或者直接构造函数初始化:

PathMeasure pathMeasure2 = new PathMeasure(path, true);

1.2、获取路径长度

/**
 * 路径长度,测量的是当前路径状态的长度,如果forceClosed为true,则不论路径是否闭合,测量的都是路径的闭合长度。
 * 获取到的长度是当前曲线的长度。
 */
System.out.println(pathMeasure1.getLength());

1.3、判断路径是否闭合

/**
 * 判断路径是否闭合
 */
System.out.println(pathMeasure1.isClosed());

1.4、跳转到路径的下一条曲线

/**
 * 跳转到下一条曲线,true表示跳转成功,false表示跳转失败。
 *
 */
System.out.println(pathMeasure1.nextContour());

1.5、截取路径的某个片段

/**
 * 截取整个Path中startD到stopD的某个片段,如果不在范围则返回false,截取后添加Path到dst中。
 * startWithMoveTo:如果为true,则被截取的Path片段保持原状添加到dst中;如果为false,则被截取的Path片段的起始点移动到dst的最后一个点,保证dst路径的连续性。
 * 需要禁用硬件加速功能,setLayerType(LAYER_TYPE_SOFTWARE,null)。
 * 路径截取是以左上角顺时针Path.Direction.CW或逆时针Path.Direction.CCW截取。
 */
boolean isSuccess = pathMeasure1.getSegment(010new Path(), true);

1.6、获取路径上某一长度的位置以及该位置的正切值

/**
 *
 * 获取路径上某一长度的位置以及该位置的正切值
 * distance:距离path起始点的长度,0<=distance<=pathMeasure1.getLength()
 * pos 位置的坐标值,pos[0]是x坐标,pos[1]是y坐标
 * tan 位置坐标的正切值,也就是坐标到原点与X轴所成夹角对应的正切值,tan[0]是x坐标,tan[1]是y坐标,是半径为1的圆的对应点,y/x就是正切值
 * 求反正切值,根据正切值获得对应夹角的度数,也就是反正切,atan(double d)传入弧度值也就是正切的结果值,atan2(double x, double y)传入正切的点的坐标值。
 * 如果想要让移动点旋转至与切线重合,则旋转角度要与正切角度相同。
 */
boolean isTanSuccess = pathMeasure1.getPosTan(1, new float[]{1F, 1F}, new float[]{1F, 1F});

1.7、得到路径上某一长度的位置,以及该位置的正切值的矩阵。

/**
 * 得到路径上某一长度的位置,以及该位置的正切值的矩阵。
 * distance:距离Path起始点的长度
 * matrix:matrix会根据flags的设置而存入不同的内容。
 * flags:指定存入matrix中的内容。POSITION_MATRIX_FLAG:获取位置信息。TANGENT_MATRIX_FLAG:获取切边信息,使得图片按Path旋转。
 */
boolean isMatrixSuccess = pathMeasure1.getMatrix(1new Matrix(), PathMeasure.POSITION_MATRIX_FLAG);

1.8、加载实例

初始化数据的时候,监听动画的数值变化,并对UI进行刷新,也就是调用onDraw重新绘制:

private void init() {
    setLayerType(LAYER_TYPE_SOFTWARE,null);
    mArrow = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_arrow);
    mMatrix = new Matrix();
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(4);
    mPaint.setColor(Color.RED);

    mDstPath = new Path();
    mCirclePath = new Path();
    mCirclePath.addCircle(dip2px(getContext(),100),dip2px(getContext(),100),dip2px(getContext(),25),Path.Direction.CW);
    mPathMeasure = new PathMeasure(mCirclePath,true);

    ValueAnimator animator = ValueAnimator.ofFloat(0,1);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //获取到旋转的角度值后刷新界面
            mCurrentAnimValue = (Float) animation.getAnimatedValue();
            invalidate();
        }
    });
    animator.setDuration(2000);
    animator.start();
}

重新绘制界面,包括绘制路径和绘制箭头位图:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mStop = mPathMeasure.getLength()*mCurrentAnimValue;
    mStart = (float) (mStop-((0.5-Math.abs(mCurrentAnimValue-0.5))*mPathMeasure.getLength()));
    mDstPath.reset();
    mPathMeasure.getSegment(mStart,mStop, mDstPath,true);
    //绘制路径
    canvas.drawPath(mDstPath, mPaint);

    mMatrix = new Matrix();

    /**
     * 第一种方式
     */
    mPathMeasure.getPosTan(mStop,mPos,mTan);
    float degrees = (float)(Math.atan2(mTan[1],mTan[0])*180.0/Math.PI);
    mMatrix.postRotate(degrees,mArrow.getWidth()/2,mArrow.getHeight()/2);
    mMatrix.postTranslate(mPos[0]-mArrow.getWidth()/2,mPos[1]-mArrow.getHeight()/2);

    /**
     * 第二种方式
     */
    mPathMeasure.getMatrix(mStop,mMatrix,PathMeasure.POSITION_MATRIX_FLAG|PathMeasure.TANGENT_MATRIX_FLAG);
    mMatrix.preTranslate(mArrow.getWidth()/2,-mArrow.getHeight()/2);
    //绘制箭头
    canvas.drawBitmap(mArrow,mMatrix,mPaint);
}

欢迎关注Android技术堆栈公众号: