android.graphics.PathMeasure

Here are the examples of the java api android.graphics.PathMeasure taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

175 Examples 7

19 Source : CircularSeekBar.java
with Apache License 2.0
from vpaliy

private void calculatePointerXYPosition() {
    PathMeasure pm = new PathMeasure(mCircleProgressPath, false);
    boolean returnValue = pm.getPosTan(pm.getLength(), mPointerPositionXY, null);
    if (!returnValue) {
        pm = new PathMeasure(mCirclePath, false);
        returnValue = pm.getPosTan(0, mPointerPositionXY, null);
    }
}

19 Source : CubeLoadingView.java
with MIT License
from thunderpunch

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    final int horizontalSpace = w - getPaddingLeft() - getPaddingRight();
    final int verticalSpace = h - getPaddingTop() - getPaddingBottom();
    int[] tempSize = calculateSizeByHorizontalSpace(getMinimumHorizontalSpace());
    if (horizontalSpace < tempSize[0] || verticalSpace < tempSize[1]) {
    // 给到空间小于所需最小宽高则按最小宽高计算
    } else {
        tempSize = calculateSizeByVerticalSpace(verticalSpace);
        if (tempSize[0] > horizontalSpace) {
            tempSize = calculateSizeByHorizontalSpace(horizontalSpace);
        }
    }
    mCubeLength = tempSize[2];
    mOrigin.x = (w - tempSize[0]) / 2;
    mOrigin.y = (h - tempSize[1]) / 2;
    final int diagonalHorizontal = (int) (2 * mCubeLength * Math.sin(Math.toRadians(DEGREE)));
    final int diagonalVertical = (int) (2 * mCubeLength * Math.cos(Math.toRadians(DEGREE)));
    mPath = new Path();
    mPath.moveTo(diagonalHorizontal, 0);
    mPath.lineTo(2 * diagonalHorizontal, diagonalVertical);
    mPath.lineTo(diagonalHorizontal * 3 / 2, diagonalVertical * 3 / 2);
    mPath.lineTo(diagonalHorizontal / 2, diagonalVertical / 2);
    mPath.close();
    PathMeasure pathMeasure = new PathMeasure(mPath, false);
    mCubes.clear();
    for (int i = 0; i < 4; i++) {
        mCubes.add(new Cube((1.0f / 4) * i, pathMeasure, diagonalHorizontal, diagonalVertical, mCubeLength));
    }
}

19 Source : CircularSeekBar.java
with Apache License 2.0
from RajneeshSingh007

protected void calculatePointerXYPosition() {
    PathMeasure pm = new PathMeasure(mCircleProgressPath, false);
    boolean returnValue = pm.getPosTan(pm.getLength(), mPointerPositionXY, null);
    if (!returnValue) {
        pm = new PathMeasure(mCirclePath, false);
        returnValue = pm.getPosTan(0, mPointerPositionXY, null);
    }
}

19 Source : MusicAnimBean.java
with GNU Affero General Public License v3.0
from qwsem

public void setPathMeasure(PathMeasure pathMeasure) {
    mPathMeasure = pathMeasure;
}

19 Source : PathModel.java
with GNU General Public License v3.0
from MrIkso

public void trimPath() {
    if (scaleMatrix != null) {
        if (trimPathStart == 0 && trimPathEnd == 1 && trimPathOffset == 0) {
            path = new Path(originalPath);
            path.transform(scaleMatrix);
        } else {
            PathMeasure pathMeasure = new PathMeasure(originalPath, false);
            float length = pathMeasure.getLength();
            trimmedPath = new Path();
            pathMeasure.getSegment((trimPathStart + trimPathOffset) * length, (trimPathEnd + trimPathOffset) * length, trimmedPath, true);
            path = new Path(trimmedPath);
            path.transform(scaleMatrix);
        }
    }
}

19 Source : EditGraphView.java
with MIT License
from jsuyash1514

private void calcPoints() {
    PathMeasure pathMeasure = new PathMeasure(mPath, false);
    float[] pos = new float[2];
    float[] tan = new float[2];
    points_On_Graph = new ArrayList<>();
    normalized_points = new ArrayList<>();
    for (int i = 0; i <= pathMeasure.getLength(); i++) {
        pathMeasure.getPosTan(i, pos, tan);
        points_On_Graph.add(new PointF(pos[0], pos[1]));
        normalized_points.add(new PointF(pos[0] / (vW - 100.0f), 1.0f - (pos[1] / vH)));
    }
}

19 Source : ArrowDrawable.java
with Apache License 2.0
from Ifxcyr

/**
 * 分解Path
 *
 * @return Path上的全部坐标点
 */
private float[] decomposePath(PathMeasure pathMeasure) {
    if (pathMeasure.getLength() == 0) {
        return new float[0];
    }
    final float pathLength = pathMeasure.getLength();
    int numPoints = (int) (pathLength / mPrecision) + 1;
    float[] points = new float[numPoints * 2];
    final float[] position = new float[2];
    int index = 0;
    float distance;
    for (int i = 0; i < numPoints; ++i) {
        distance = (i * pathLength) / (numPoints - 1);
        pathMeasure.getPosTan(distance, position, null);
        points[index] = position[0];
        points[index + 1] = position[1];
        index += 2;
    }
    return points;
}

19 Source : FloatAnimation.java
with Apache License 2.0
from hexingbo

/**
 * @author Vondear
 * @date 2018/6/19
 */
public clreplaced FloatAnimation extends Animation {

    private PathMeasure mPm;

    private View mView;

    private float mDistance;

    private float mRotation;

    public FloatAnimation(Path path, float rotation, View parent, View child) {
        mPm = new PathMeasure(path, false);
        mDistance = mPm.getLength();
        mView = child;
        mRotation = rotation;
        parent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    }

    private static float scale(double a, double b, double c, double d, double e) {
        return (float) ((a - b) / (c - b) * (e - d) + d);
    }

    @Override
    protected void applyTransformation(float factor, Transformation transformation) {
        Matrix matrix = transformation.getMatrix();
        mPm.getMatrix(mDistance * factor, matrix, PathMeasure.POSITION_MATRIX_FLAG);
        mView.setRotation(mRotation * factor);
        float scale = 1F;
        if (3000.0F * factor < 200.0F) {
            scale = scale(factor, 0.0D, 0.06666667014360428D, 0.20000000298023224D, 1.100000023841858D);
        } else if (3000.0F * factor < 300.0F) {
            scale = scale(factor, 0.06666667014360428D, 0.10000000149011612D, 1.100000023841858D, 1.0D);
        }
        mView.setScaleX(scale);
        mView.setScaleY(scale);
        transformation.setAlpha(1.0F - factor);
    }
}

19 Source : FloatAnimation.java
with Apache License 2.0
from Geekince

public clreplaced FloatAnimation extends Animation {

    private PathMeasure mPm;

    private View mView;

    private float mDistance;

    private float mRotation;

    public FloatAnimation(Path path, float rotation, View parent, View child) {
        mPm = new PathMeasure(path, false);
        mDistance = mPm.getLength();
        mView = child;
        mRotation = rotation;
        parent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    }

    @Override
    protected void applyTransformation(float factor, Transformation transformation) {
        Matrix matrix = transformation.getMatrix();
        mPm.getMatrix(mDistance * factor, matrix, PathMeasure.POSITION_MATRIX_FLAG);
        mView.setRotation(mRotation * factor);
        float scale = 1F;
        if (3000.0F * factor < 200.0F) {
            scale = scale(factor, 0.0D, 0.066D, 0.2D, 1.1D);
        } else if (3000.0F * factor < 300.0F) {
            scale = scale(factor, 0.066D, 0.1D, 1.1D, 1.0D);
        }
        mView.setScaleX(scale);
        mView.setScaleY(scale);
        transformation.setAlpha(1.0F - factor);
    }

    private float scale(double a, double b, double c, double d, double e) {
        return (float) ((a - b) / (c - b) * (e - d) + d);
    }
}

19 Source : Smoke.java
with MIT License
from florent37

private void matchVertsToPath(float wind) {
    PathMeasure pm = new PathMeasure(smokePath, false);
    for (int i = 0; i < staticVerts.length / 2; i++) {
        float yIndexValue = staticVerts[i * 2 + 1];
        float xIndexValue = staticVerts[i * 2];
        float percentOffsetY = (0.000001f + yIndexValue) / bitmap.getHeight();
        float percentOffsetY2 = (0.000001f + yIndexValue) / (bitmap.getHeight() + ((bitmap.getHeight() / numberOfTurns) * 4f));
        percentOffsetY2 += pathPointOffset;
        pm.getPosTan(pm.getLength() * (1f - percentOffsetY), coords, null);
        pm.getPosTan(pm.getLength() * (1f - percentOffsetY2), coords2, null);
        if (xIndexValue == 0) {
            float desiredXCoord = coords2[0] - (bitmap.getWidth()) / 2;
            desiredXCoord -= (desiredXCoord - x) * percentOffsetY;
            desiredXCoord += (wind / 3f) * density + ((wind * WIND_SENSITIVITY) * (1f - smokeExponentionWindStuff.getInterpolation(percentOffsetY)));
            setXY(drawingVerts, i, desiredXCoord, coords[1]);
        } else {
            float desiredXCoord = coords2[0] + (bitmap.getWidth()) / 2;
            desiredXCoord -= (desiredXCoord - x) * percentOffsetY;
            desiredXCoord += (wind / 3f) * density + ((wind * WIND_SENSITIVITY) * (1f - smokeExponentionWindStuff.getInterpolation(percentOffsetY)));
            setXY(drawingVerts, i, desiredXCoord, coords[1]);
        }
    }
}

19 Source : RenderableThree.java
with MIT License
from florent37

private void matchVertsToPath() {
    PathMeasure pmLeft = new PathMeasure(pathLeft, false);
    PathMeasure pmRight = new PathMeasure(pathRight, false);
    float[] coords = new float[2];
    for (int i = 0; i < staticVerts.length / 2; i++) {
        float yIndexValue = staticVerts[i * 2 + 1];
        float xIndexValue = staticVerts[i * 2];
        if (xIndexValue == 0) {
            float percentOffsetY = (0.000001f + yIndexValue) / bitmap.getHeight();
            pmLeft.getPosTan(pmLeft.getLength() * (1f - percentOffsetY), coords, null);
            setXY(drawingVerts, i, coords[0], coords[1]);
        } else {
            float percentOffsetY = (0.000001f + yIndexValue) / bitmap.getHeight();
            pmRight.getPosTan(pmRight.getLength() * (1f - percentOffsetY), coords, null);
            setXY(drawingVerts, i, coords[0], coords[1]);
        }
    }
}

19 Source : AnimationRouteHelper.java
with MIT License
from amalChandran

@Override
public void onPathMeasureChange() {
    PathMeasure pathMeasure = new PathMeasure(overlayPolyline.getDrawPath(), false);
    length = pathMeasure.getLength();
    dashValue = new float[] { length, length };
}

19 Source : AnimationArcHelper.java
with MIT License
from amalChandran

@Override
public void onPathMeasureChange() {
    PathMeasure pathMeasure = new PathMeasure(overlayPolyline.getDrawPath(), false);
    arcLength = pathMeasure.getLength();
    arcdDashValue = new float[] { arcLength, arcLength };
}

18 Source : StairsPathBuilder.java
with MIT License
from zyao89

/**
 * Created by zyao89 on 2018/3/10.
 * Contact me at [email protected] or [email protected]
 * For more projects: https://github.com/zyao89
 * My Blog: https://zyao89.cn
 */
public clreplaced StairsPathBuilder extends BaseStateBuilder {

    private final int FLOOR_NUM = 6;

    private Paint mPaint;

    private float mR;

    private Path mPath;

    private Path mPathFinal;

    private PathMeasure mPathMeasure;

    private Path mDrawPath;

    @Override
    protected int getStateCount() {
        return 3;
    }

    @Override
    protected void initParams(Context context, Paint paint) {
        mPaint = paint;
        mR = getAllSize();
        initPathMeasure();
        initPaths();
    }

    private void initPaths() {
        mPath = new Path();
        float space = mR * 2 / FLOOR_NUM;
        float startXP = getViewCenterX() - mR;
        float startYP = getViewCenterY() + mR;
        mPath.moveTo(startXP, startYP);
        for (int i = 0; i < FLOOR_NUM; i++) {
            mPath.lineTo((i) * space + startXP, startYP - space * (i + 1));
            mPath.lineTo((i + 1) * space + startXP, startYP - space * (i + 1));
        }
        mPathFinal = new Path(mPath);
        mPathFinal.lineTo((FLOOR_NUM) * space + startXP, startYP);
        mPathFinal.lineTo(startXP, startYP);
    }

    private void initPathMeasure() {
        mDrawPath = new Path();
        mPathMeasure = new PathMeasure();
    }

    @Override
    protected void onComputeUpdateValue(ValueAnimator animation, float animatedValue, int state) {
        switch(state) {
            case 0:
            case 1:
                resetDrawPath();
                mPathMeasure.setPath(mPath, false);
                float stop = mPathMeasure.getLength() * animatedValue;
                float start = (float) (stop - ((0.5 - Math.abs(animatedValue - 0.5)) * 200f));
                mPathMeasure.getSegment(start, stop, mDrawPath, true);
                break;
            case 2:
                resetDrawPath();
                mPathMeasure.setPath(mPathFinal, false);
                stop = mPathMeasure.getLength() * animatedValue;
                start = 0;
                mPathMeasure.getSegment(start, stop, mDrawPath, true);
                break;
            case 3:
                resetDrawPath();
                mPathMeasure.setPath(mPathFinal, false);
                stop = mPathMeasure.getLength() * (1 - animatedValue);
                start = 0;
                mPathMeasure.getSegment(start, stop, mDrawPath, true);
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(mDrawPath, mPaint);
    }

    @Override
    protected void prepareStart(ValueAnimator valueAnimator) {
        valueAnimator.setInterpolator(new DecelerateInterpolator());
    }

    @Override
    protected void prepareEnd() {
    }

    private void resetDrawPath() {
        mDrawPath.reset();
        mDrawPath.lineTo(0, 0);
    }
}

18 Source : SearchPathBuilder.java
with MIT License
from zyao89

/**
 * Created by zyao89 on 2017/4/11.
 * Contact me at [email protected] or [email protected]
 * For more projects: https://github.com/zyao89
 * My Blog: http://zyao89.me
 */
public clreplaced SearchPathBuilder extends ZLoadingBuilder {

    // 最终阶段
    private static final int FINAL_STATE = 3;

    // 当前动画阶段
    private int mCurrAnimatorState = 0;

    private float mR;

    private Paint mPaint;

    private Path mPath;

    private Path mPathZoom;

    private PathMeasure mPathMeasure;

    private Path mDrawPath;

    @Override
    protected void initParams(Context context) {
        mR = getAllSize();
        initPaint();
        initPathMeasure();
        initPaths();
    }

    private void initPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(15);
        mPaint.setColor(Color.BLACK);
        mPaint.setDither(true);
        mPaint.setFilterBitmap(true);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
    }

    private void initPaths() {
        float r = mR * 0.4f;
        mPath = new Path();
        mPath.addArc(new RectF(getViewCenterX() - mR, getViewCenterY() - mR, getViewCenterX() + mR, getViewCenterY() + mR), 45, 359.9f);
        mPathMeasure.setPath(mPath, false);
        float[] pos = new float[2];
        mPathMeasure.getPosTan(0, pos, null);
        mPathZoom = new Path();
        mPathZoom.addArc(new RectF(getViewCenterX() - r, getViewCenterY() - r, getViewCenterX() + r, getViewCenterY() + r), 45, 359.9f);
        mPathZoom.lineTo(pos[0], pos[1]);
    }

    private void initPathMeasure() {
        mDrawPath = new Path();
        mPathMeasure = new PathMeasure();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPath(mDrawPath, mPaint);
    }

    @Override
    protected void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    protected void prepareStart(ValueAnimator floatValueAnimator) {
        floatValueAnimator.setInterpolator(new DecelerateInterpolator());
    }

    @Override
    protected void prepareEnd() {
    }

    @Override
    protected void computeUpdateValue(ValueAnimator animation, @FloatRange(from = 0.0, to = 1.0) float animatedValue) {
        switch(mCurrAnimatorState) {
            case 0:
            case 1:
                resetDrawPath();
                mPathMeasure.setPath(mPath, false);
                float stop = mPathMeasure.getLength() * animatedValue;
                float start = (float) (stop - ((0.5 - Math.abs(animatedValue - 0.5)) * 200f));
                mPathMeasure.getSegment(start, stop, mDrawPath, true);
                break;
            case 2:
                resetDrawPath();
                mPathMeasure.setPath(mPath, false);
                stop = mPathMeasure.getLength() * animatedValue;
                start = 0;
                mPathMeasure.getSegment(start, stop, mDrawPath, true);
                break;
            case 3:
                mPathMeasure.setPath(mPathZoom, false);
                stop = mPathMeasure.getLength();
                start = stop * (1 - animatedValue);
                mPathMeasure.getSegment(start, stop, mDrawPath, true);
                break;
            default:
                break;
        }
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        if (++mCurrAnimatorState > FINAL_STATE) {
            // 还原到第一阶段
            mCurrAnimatorState = 0;
        }
    }

    @Override
    protected void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
    }

    private void resetDrawPath() {
        mDrawPath.reset();
        // 不知道什么坑
        mDrawPath.lineTo(0, 0);
    }
}

18 Source : MusicPathBuilder.java
with MIT License
from zyao89

/**
 * Created by zyao89 on 2018/3/11.
 * Contact me at [email protected] or [email protected]
 * For more projects: https://github.com/zyao89
 * My Blog: https://zyao89.cn
 */
public clreplaced MusicPathBuilder extends BaseStateBuilder {

    private final int MUSIC_LINE_COUNT = 5;

    private Paint mPaint;

    private float mR;

    private LinkedList<Path> mMusicPaths;

    private LinkedList<Path> mMusicDrawPaths;

    private PathMeasure mPathMeasure;

    private LinkedList<MusicParam> mMusicParams;

    private boolean mOpenJump = false;

    private DecelerateInterpolator mDecelerateInterpolator;

    private BounceInterpolator mBounceInterpolator;

    @Override
    protected int getStateCount() {
        return 3;
    }

    @Override
    protected void initParams(Context context, Paint paint) {
        mPaint = paint;
        mPaint.setStrokeWidth(2);
        mR = getAllSize();
        initPaths();
        initPathMeasure();
        initMusicParams();
        initInterpolator();
    }

    private void initPaths() {
        mMusicPaths = new LinkedList<Path>();
        // 线长
        float lineW = mR * 2;
        // 线间距
        float space = mR * 2 / MUSIC_LINE_COUNT;
        // 起点
        float startXP = getViewCenterX() - mR;
        float startYP = getViewCenterY() + mR;
        // 五线谱
        for (int i = 0; i < MUSIC_LINE_COUNT; i++) {
            Path path = new Path();
            path.moveTo(startXP, startYP - (i * space));
            path.lineTo(startXP + lineW, startYP - (i * space));
            mMusicPaths.add(path);
        }
    }

    private void initPathMeasure() {
        mMusicDrawPaths = new LinkedList<>();
        // 五线谱
        for (int i = 0; i < MUSIC_LINE_COUNT; i++) {
            Path drawPath = new Path();
            mMusicDrawPaths.add(drawPath);
        }
        mPathMeasure = new PathMeasure();
    }

    private void initMusicParams() {
        float musicWidth = mR * 0.2f;
        float musicHeight = mR;
        mMusicParams = new LinkedList<>();
        float musicPointHeight = mR * 2 / MUSIC_LINE_COUNT;
        float left = getViewCenterX() - musicWidth / 2;
        float right = getViewCenterX() + musicWidth / 2;
        float top = getViewCenterY() + musicHeight - musicPointHeight * 1.5f;
        float bottom = getViewCenterY() + musicHeight - musicPointHeight * 0.5f;
        RectF rectF = new RectF(left - mR * 0.5f, top, right - mR * 0.5f, bottom);
        float offsetX = (float) (musicWidth * 0.5 * Math.cos(75));
        PointF sPointF = new PointF(rectF.right + offsetX, rectF.centerY());
        PointF ePointF = new PointF(rectF.right + offsetX, rectF.centerY() - musicHeight);
        MusicParam musicParam = new MusicParam(rectF, sPointF, ePointF);
        mMusicParams.add(musicParam);
        rectF = new RectF(left + mR * 0.5f, top - musicPointHeight, right + mR * 0.5f, bottom - musicPointHeight);
        offsetX = (float) (musicWidth * 0.5 * Math.cos(75));
        sPointF = new PointF(rectF.right + offsetX, rectF.centerY());
        ePointF = new PointF(rectF.right + offsetX, rectF.centerY() - musicHeight);
        musicParam = new MusicParam(rectF, sPointF, ePointF);
        mMusicParams.add(musicParam);
    }

    private void initInterpolator() {
        mDecelerateInterpolator = new DecelerateInterpolator();
        mBounceInterpolator = new BounceInterpolator();
    }

    @Override
    protected void onComputeUpdateValue(ValueAnimator animation, float animatedValue, int state) {
        switch(state) {
            case 0:
                animation.setInterpolator(mDecelerateInterpolator);
                resetDrawPath();
                for (int i = 0; i < MUSIC_LINE_COUNT; i++) {
                    mPathMeasure.setPath(mMusicPaths.get(i), false);
                    if (i % 2 == 0) {
                        float stop = mPathMeasure.getLength() * animatedValue;
                        float start = (float) (stop - ((0.5 - Math.abs(animatedValue - 0.5)) * 200f));
                        mPathMeasure.getSegment(start, stop, mMusicDrawPaths.get(i), true);
                    } else {
                        float stop = mPathMeasure.getLength() * (1 - animatedValue);
                        float start = (float) (stop - ((0.5 - Math.abs((1 - animatedValue) - 0.5)) * 200f));
                        mPathMeasure.getSegment(start, stop, mMusicDrawPaths.get(i), true);
                    }
                }
                break;
            case 1:
                resetDrawPath();
                for (int i = 0; i < MUSIC_LINE_COUNT; i++) {
                    mPathMeasure.setPath(mMusicPaths.get(i), false);
                    if (i % 2 == 0) {
                        float stop = mPathMeasure.getLength() * animatedValue;
                        float start = 0;
                        mPathMeasure.getSegment(start, stop, mMusicDrawPaths.get(i), true);
                    } else {
                        float stop = mPathMeasure.getLength();
                        float start = mPathMeasure.getLength() * (1 - animatedValue);
                        mPathMeasure.getSegment(start, stop, mMusicDrawPaths.get(i), true);
                    }
                }
                break;
            case 2:
                animation.setInterpolator(mBounceInterpolator);
                // jump
                mOpenJump = true;
                // 线间距
                float space = mR * 2 / MUSIC_LINE_COUNT;
                for (int i = 0; i < mMusicParams.size(); i++) {
                    MusicParam musicParam = mMusicParams.get(i);
                    if (i % 2 == 0) {
                        musicParam.setOffsetY(animatedValue * space);
                    } else {
                        musicParam.setOffsetY((1 - animatedValue) * space);
                    }
                }
                break;
            case 3:
                // jump
                mOpenJump = true;
                // 线间距
                space = mR * 2 / MUSIC_LINE_COUNT;
                for (int i = 0; i < mMusicParams.size(); i++) {
                    MusicParam musicParam = mMusicParams.get(i);
                    if (i % 2 == 0) {
                        musicParam.setOffsetY((1 - animatedValue) * space);
                    } else {
                        musicParam.setOffsetY(animatedValue * space);
                    }
                }
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (Path drawPath : mMusicDrawPaths) {
            canvas.drawPath(drawPath, mPaint);
        }
        if (mOpenJump) {
            drawMusic(canvas);
        }
    }

    private void drawMusic(Canvas canvas) {
        for (MusicParam musicParam : mMusicParams) {
            mPaint.setStrokeWidth(4);
            canvas.save();
            RectF oldCircleRectF = musicParam.getCircleRectF();
            RectF circleRectF = new RectF(oldCircleRectF);
            float offsetY = musicParam.getOffsetY();
            circleRectF.set(oldCircleRectF.left, oldCircleRectF.top - offsetY, oldCircleRectF.right, oldCircleRectF.bottom - offsetY);
            canvas.rotate(75, circleRectF.centerX(), circleRectF.centerY());
            mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
            canvas.drawOval(circleRectF, mPaint);
            mPaint.setStyle(Paint.Style.STROKE);
            canvas.restore();
            PointF startPointF = musicParam.getLineStartPointF();
            PointF endPointF = musicParam.getLineEndPointF();
            canvas.drawLine(startPointF.x, startPointF.y - offsetY, endPointF.x, endPointF.y - offsetY, mPaint);
            mPaint.setStrokeWidth(2);
        }
    }

    @Override
    protected void prepareStart(ValueAnimator animation) {
        animation.setInterpolator(mDecelerateInterpolator);
    }

    @Override
    protected void prepareEnd() {
    }

    private void resetDrawPath() {
        mOpenJump = false;
        for (Path path : mMusicDrawPaths) {
            path.reset();
            path.lineTo(0, 0);
        }
        for (MusicParam musicParam : mMusicParams) {
            musicParam.clear();
        }
    }

    static clreplaced MusicParam {

        private final RectF mCircleRectF;

        private final PointF mLineStartPointF;

        private final PointF mLineEndPointF;

        private float mOffsetY = 0;

        MusicParam(RectF rectF, PointF sPointF, PointF ePointF) {
            mCircleRectF = rectF;
            mLineStartPointF = sPointF;
            mLineEndPointF = ePointF;
        }

        RectF getCircleRectF() {
            return mCircleRectF;
        }

        PointF getLineStartPointF() {
            return mLineStartPointF;
        }

        PointF getLineEndPointF() {
            return mLineEndPointF;
        }

        float getOffsetY() {
            return mOffsetY;
        }

        void setOffsetY(float v) {
            mOffsetY = v;
        }

        void clear() {
            mOffsetY = 0;
        }
    }
}

18 Source : PathBitmapMesh.java
with Apache License 2.0
from xujiaji

public void matchVertsToPath(Path path, float bottomCoord, float extraOffset) {
    PathMeasure pm = new PathMeasure(path, false);
    for (int i = 0; i < staticVerts.length / 2; i++) {
        float yIndexValue = staticVerts[i * 2 + 1];
        float xIndexValue = staticVerts[i * 2];
        float percentOffsetX = (0.000001f + xIndexValue) / bitmap.getWidth();
        float percentOffsetX2 = (0.000001f + xIndexValue) / (bitmap.getWidth() + extraOffset);
        percentOffsetX2 += pathOffsetPercent;
        pm.getPosTan(pm.getLength() * (1f - percentOffsetX), coords, null);
        pm.getPosTan(pm.getLength() * (1f - percentOffsetX2), coords2, null);
        if (yIndexValue == 0) {
            setXY(drawingVerts, i, coords[0], coords2[1]);
        } else {
            float desiredYCoord = bottomCoord;
            setXY(drawingVerts, i, coords[0], desiredYCoord);
        }
    }
}

18 Source : ObjectAnimatorCompatBase.java
with Apache License 2.0
from xuexiangjys

private static void calculateXYValues(Path path, @Size(NUM_POINTS) float[] xValues, @Size(NUM_POINTS) float[] yValues) {
    PathMeasure pathMeasure = new PathMeasure(path, false);
    float pathLength = pathMeasure.getLength();
    float[] position = new float[2];
    for (int i = 0; i < NUM_POINTS; ++i) {
        float distance = (i * pathLength) / (NUM_POINTS - 1);
        pathMeasure.getPosTan(distance, position, null);
        xValues[i] = position[0];
        yValues[i] = position[1];
    }
}

18 Source : ObjectAnimatorCompatBase.java
with Apache License 2.0
from xuexiangjys

private static void calculateXYValues(Path path, @Size(NUM_POINTS) int[] xValues, @Size(NUM_POINTS) int[] yValues) {
    PathMeasure pathMeasure = new PathMeasure(path, false);
    float pathLength = pathMeasure.getLength();
    float[] position = new float[2];
    for (int i = 0; i < NUM_POINTS; ++i) {
        float distance = (i * pathLength) / (NUM_POINTS - 1);
        pathMeasure.getPosTan(distance, position, null);
        xValues[i] = Math.round(position[0]);
        yValues[i] = Math.round(position[1]);
    }
}

18 Source : OBAnimPath.java
with Apache License 2.0
from XPRIZE

/**
 * OBAnimPath
 * Animates an OBControl along a Path.
 *
 * @see Path
 * @see OBControl
 * Created by alan on 07/06/16.
 */
public clreplaced OBAnimPath extends OBAnim {

    float angleOffset, angleStart;

    boolean changeAngle;

    Path path;

    PathMeasure pathMeasure;

    float pathLength;

    public OBAnimPath(Object obj, String ky, int ty) {
        super(obj, ky, ty);
    }

    public OBAnimPath(Object obj, Path p, boolean changle, float radians) {
        this(obj, "positionAndAngle", ANIM_TYPE_PATH);
        path = p;
        pathMeasure = new PathMeasure(p, false);
        pathLength = pathMeasure.getLength();
        changeAngle = changle;
        angleOffset = radians;
        angleStart = ((OBControl) obj).rotation;
    }

    public Object valueForT(float t) {
        float thisLen = t * pathLength;
        float[] pos = { 0, 0 }, ftan = { 0, 0 };
        pathMeasure.getPosTan(thisLen, pos, ftan);
        PointF vpt = new PointF(pos[0], pos[1]);
        if (changeAngle) {
            float radians = (float) Math.atan2(ftan[1], ftan[0]);
            radians += angleOffset;
            return Arrays.asList(vpt, radians);
        }
        return Arrays.asList(vpt, angleStart);
    }
}

18 Source : OBShapeLayer.java
with Apache License 2.0
from XPRIZE

/**
 * Created by alan on 21/04/16.
 */
public clreplaced OBShapeLayer extends OBLayer {

    public Path path;

    public Paint fillPaint, strokePaint;

    public OBStroke stroke;

    public int fillColour;

    public float currX, currY;

    float strokeStart, strokeEnd;

    PathMeasure pathMeasure;

    public OBShapeLayer() {
        super();
        fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        fillPaint.setStyle(Paint.Style.FILL);
        strokePaint.setStyle(Paint.Style.STROKE);
        opacity = 1;
        stroke = new OBStroke();
        strokeStart = 0;
        strokeEnd = 1;
    }

    public OBShapeLayer(Path p) {
        this();
        if (p == null)
            path = new Path();
        else
            path = p;
    }

    @Override
    public OBLayer copy() {
        OBShapeLayer obj = (OBShapeLayer) super.copy();
        obj.path = new Path(path);
        obj.fillPaint = new Paint(fillPaint);
        obj.strokePaint = new Paint(strokePaint);
        obj.stroke = stroke.copy();
        obj.fillColour = fillColour;
        obj.strokeStart = strokeStart;
        obj.strokeEnd = strokeEnd;
        obj.currX = currX;
        obj.currY = currY;
        return obj;
    }

    @Override
    public void draw(Canvas canvas) {
        if (path != null) {
            setUpPaint();
            if (fillColour != 0)
                canvas.drawPath(path, fillPaint);
            if (stroke != null)
                canvas.drawPath(path, strokePaint);
        }
    }

    public void setUpPaint() {
        if (stroke != null) {
            strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            strokePaint.setStrokeWidth(stroke.lineWidth);
            // int col = OBUtils.applyColourOpacity(stroke.colour,opacity);
            strokePaint.setColor(stroke.colour);
            strokePaint.setStrokeCap(stroke.paintLineCap());
            strokePaint.setStrokeJoin(stroke.paintLineJoin());
            DashPathEffect dpe = stroke.dashPathEffect();
            if (dpe == null)
                strokePaint.setPathEffect(null);
            else
                strokePaint.setPathEffect(dpe);
            strokePaint.setStyle(Paint.Style.STROKE);
            if (colourFilter == null)
                strokePaint.setColorFilter(null);
            else
                strokePaint.setColorFilter(colourFilter);
        }
        if (fillColour != 0) {
            fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            // int col = OBUtils.applyColourOpacity(fillColour,opacity);
            fillPaint.setColor(fillColour);
            // fillPaint.setMaskFilter(new BlurMaskFilter(7, BlurMaskFilter.Blur.NORMAL));
            fillPaint.setStyle(Paint.Style.FILL);
            if (colourFilter == null)
                fillPaint.setColorFilter(null);
            else
                fillPaint.setColorFilter(colourFilter);
        }
    }

    public PathMeasure pathMeasure() {
        if (pathMeasure == null)
            pathMeasure = new PathMeasure(path, false);
        return pathMeasure;
    }

    public float length() {
        return pathMeasure().getLength();
    }

    public float strokeEnd() {
        return strokeEnd;
    }

    public void setStrokeEnd(float f) {
        strokeEnd = OB_Maths.clamp01(f);
        setupStrokeDashes();
    }

    public void setStrokeStart(float f) {
        strokeStart = OB_Maths.clamp01(f);
        setupStrokeDashes();
    }

    private void setupStrokeDashes() {
        float len = length();
        if (strokeEnd < 1 || strokeStart > 0) {
            List<Float> lst = new ArrayList<>(4);
            lst.add(0f);
            lst.add(strokeStart * len);
            lst.add(OB_Maths.clamp01(strokeEnd - strokeStart) * len);
            lst.add(32767f);
            stroke.setDashes(lst);
        } else
            stroke.setDashes(null);
    }

    public void moveToPoint(float x, float y) {
        path.moveTo(currX = x, currY = y);
    }

    public void addLineToPoint(float x, float y) {
        path.lineTo(currX = x, currY = y);
    }
}

18 Source : CustomStatusView.java
with GNU General Public License v3.0
from xmmmmmovo

/**
 */
public clreplaced CustomStatusView extends View {

    // 进度颜色
    private int progressColor;

    // 成功的颜色
    private int loadSuccessColor;

    // 失败的颜色
    private int loadFailureColor;

    // 进度宽度
    private float progressWidth;

    // 圆环半径
    private float progressRadius;

    private Paint mPaint;

    // 状态
    private StatusEnum mStatus;

    private int startAngle = -90;

    private int minAngle = -90;

    private int sweepAngle = 120;

    private int curAngle = 0;

    // 追踪Path的坐标
    private PathMeasure mPathMeasure;

    // 画圆的Path
    private Path mPathCircle;

    // 截取PathMeasure中的path
    private Path mPathCircleDst;

    private Path successPath;

    private Path failurePathLeft;

    private Path failurePathRight;

    private ValueAnimator circleAnimator;

    private float circleValue;

    private float successValue;

    private float failValueRight;

    private float failValueLeft;

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

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

    public CustomStatusView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomStatusView, defStyleAttr, 0);
        progressColor = array.getColor(R.styleable.CustomStatusView_progress_color, ContextCompat.getColor(context, R.color.colorPrimary));
        loadSuccessColor = array.getColor(R.styleable.CustomStatusView_load_success_color, ContextCompat.getColor(context, R.color.load_success));
        loadFailureColor = array.getColor(R.styleable.CustomStatusView_load_failure_color, ContextCompat.getColor(context, R.color.load_failure));
        progressWidth = array.getDimension(R.styleable.CustomStatusView_progress_width, 6);
        progressRadius = array.getDimension(R.styleable.CustomStatusView_progress_radius, 100);
        array.recycle();
        initPaint();
        initPath();
        initAnim();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(progressColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setDither(true);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(progressWidth);
        // 设置画笔为圆角笔触
        mPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    private void initPath() {
        mPathCircle = new Path();
        mPathMeasure = new PathMeasure();
        mPathCircleDst = new Path();
        successPath = new Path();
        failurePathLeft = new Path();
        failurePathRight = new Path();
    }

    private void initAnim() {
        circleAnimator = ValueAnimator.ofFloat(0, 1);
        circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                circleValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 将当前画布的点移到getPaddingLeft,getPaddingTop,后面的操作都以该点作为参照点
        canvas.translate(getPaddingLeft(), getPaddingTop());
        if (mStatus == StatusEnum.Loading) {
            // 正在加载
            if (startAngle == minAngle) {
                sweepAngle += 6;
            }
            if (sweepAngle >= 300 || startAngle > minAngle) {
                startAngle += 6;
                if (sweepAngle > 20) {
                    sweepAngle -= 6;
                }
            }
            if (startAngle > minAngle + 300) {
                startAngle %= 360;
                minAngle = startAngle;
                sweepAngle = 20;
            }
            // 旋转的弧长为4
            canvas.rotate(curAngle += 4, progressRadius, progressRadius);
            canvas.drawArc(new RectF(0, 0, progressRadius * 2, progressRadius * 2), startAngle, sweepAngle, false, mPaint);
            invalidate();
        } else if (mStatus == StatusEnum.LoadSuccess) {
            // 加载成功
            mPaint.setColor(loadSuccessColor);
            mPathCircle.addCircle(getWidth() / 2, getWidth() / 2, progressRadius, Path.Direction.CW);
            mPathMeasure.setPath(mPathCircle, false);
            // 截取path并保存到mPathCircleDst中
            mPathMeasure.getSegment(0, circleValue * mPathMeasure.getLength(), mPathCircleDst, true);
            canvas.drawPath(mPathCircleDst, mPaint);
            if (circleValue == 1) {
                // 表示圆画完了,可以钩了
                successPath.moveTo(getWidth() / 8 * 3, getWidth() / 2);
                successPath.lineTo(getWidth() / 2, getWidth() / 5 * 3);
                successPath.lineTo(getWidth() / 3 * 2, getWidth() / 5 * 2);
                mPathMeasure.nextContour();
                mPathMeasure.setPath(successPath, false);
                mPathMeasure.getSegment(0, successValue * mPathMeasure.getLength(), mPathCircleDst, true);
                canvas.drawPath(mPathCircleDst, mPaint);
            }
        } else {
            // 加载失败
            mPaint.setColor(loadFailureColor);
            mPathCircle.addCircle(getWidth() / 2, getWidth() / 2, progressRadius, Path.Direction.CW);
            mPathMeasure.setPath(mPathCircle, false);
            mPathMeasure.getSegment(0, circleValue * mPathMeasure.getLength(), mPathCircleDst, true);
            canvas.drawPath(mPathCircleDst, mPaint);
            if (circleValue == 1) {
                // 表示圆画完了,可以画叉叉的右边部分
                failurePathRight.moveTo(getWidth() / 3 * 2, getWidth() / 3);
                failurePathRight.lineTo(getWidth() / 3, getWidth() / 3 * 2);
                mPathMeasure.nextContour();
                mPathMeasure.setPath(failurePathRight, false);
                mPathMeasure.getSegment(0, failValueRight * mPathMeasure.getLength(), mPathCircleDst, true);
                canvas.drawPath(mPathCircleDst, mPaint);
            }
            if (failValueRight == 1) {
                // 表示叉叉的右边部分画完了,可以画叉叉的左边部分
                failurePathLeft.moveTo(getWidth() / 3, getWidth() / 3);
                failurePathLeft.lineTo(getWidth() / 3 * 2, getWidth() / 3 * 2);
                mPathMeasure.nextContour();
                mPathMeasure.setPath(failurePathLeft, false);
                mPathMeasure.getSegment(0, failValueLeft * mPathMeasure.getLength(), mPathCircleDst, true);
                canvas.drawPath(mPathCircleDst, mPaint);
            }
        }
    }

    // 重制路径
    private void resetPath() {
        successValue = 0;
        circleValue = 0;
        failValueLeft = 0;
        failValueRight = 0;
        mPathCircle.reset();
        mPathCircleDst.reset();
        failurePathLeft.reset();
        failurePathRight.reset();
        successPath.reset();
    }

    private void setStatus(StatusEnum status) {
        mStatus = status;
    }

    public void loadLoading() {
        setStatus(StatusEnum.Loading);
        invalidate();
    }

    public void loadSuccess() {
        resetPath();
        setStatus(StatusEnum.LoadSuccess);
        startSuccessAnim();
    }

    public void loadFailure() {
        resetPath();
        setStatus(StatusEnum.LoadFailure);
        startFailAnim();
    }

    private void startSuccessAnim() {
        ValueAnimator success = ValueAnimator.ofFloat(0f, 1.0f);
        success.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                successValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        // 组合动画,一先一后执行
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(success).after(circleAnimator);
        animatorSet.setDuration(500);
        animatorSet.start();
    }

    private void startFailAnim() {
        ValueAnimator failLeft = ValueAnimator.ofFloat(0f, 1.0f);
        failLeft.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                failValueRight = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        ValueAnimator failRight = ValueAnimator.ofFloat(0f, 1.0f);
        failRight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                failValueLeft = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        // 组合动画,一先一后执行
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(failLeft).after(circleAnimator).before(failRight);
        animatorSet.setDuration(500);
        animatorSet.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width;
        int height;
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            width = size;
        } else {
            width = (int) (2 * progressRadius + progressWidth + getPaddingLeft() + getPaddingRight());
        }
        mode = MeasureSpec.getMode(heightMeasureSpec);
        size = MeasureSpec.getSize(heightMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            height = size;
        } else {
            height = (int) (2 * progressRadius + progressWidth + getPaddingTop() + getPaddingBottom());
        }
        setMeasuredDimension(width, height);
    }
}

18 Source : Brain.java
with GNU General Public License v3.0
from Xipher7934

public synchronized void removeByPath(Path path, float dist) {
    PathMeasure pm = new PathMeasure(path, false);
    float[] mpl = new float[2];
    for (int i = 0; i < pm.getLength(); i = i + 5) {
        pm.getPosTan(i, mpl, null);
        for (int j = neurons.size() - 1; j >= 0; j--) if (neurons.get(j).distance(mpl) < dist) {
            for (int k = axons.size() - 1; k >= 0; k--) if ((axons.get(k).start_distance(neurons.get(j).getLocation()) <= dist) || (axons.get(k).end_distance(neurons.get(j).getLocation()) <= dist))
                axons.remove(k);
            neurons.remove(j);
        }
        for (int j = axons.size() - 1; j >= 0; j--) if (axons.get(j).first_contact(mpl, dist / 2) > 0)
            axons.remove(j);
    }
    update_CTM();
}

18 Source : PathKeyframesSupport.java
with GNU General Public License v3.0
from wuyr

@Override
void init(MyPath path) {
    final PathMeasure pathMeasure = new PathMeasure(path, false);
    final float pathLength = pathMeasure.getLength();
    numPoints = (int) (pathLength / PRECISION) + 1;
    mX = new float[numPoints];
    mY = new float[numPoints];
    final float[] position = new float[2];
    for (int i = 0; i < numPoints; ++i) {
        final float distance = (i * pathLength) / (numPoints - 1);
        pathMeasure.getPosTan(distance, position, null);
        mX[i] = position[0];
        mY[i] = position[1];
    }
    mPath = path;
}

18 Source : CheckBox.java
with Apache License 2.0
from wuyinlei

/**
 * Created by bigmercu on 16/7/23.
 * Email:[email protected]
 */
public clreplaced CheckBox extends View implements Checkable {

    private static final String TAG = CheckBox.clreplaced.getSimpleName();

    private int hSize = dp2px(15);

    private int wSize = dp2px(15);

    private int textSize = dp2px(15);

    private int wStart = dp2px(1);

    private int hStart = dp2px(1);

    private int Duration = 300;

    private int strokeWidth = dp2px(2);

    private int mPaintColor;

    private String boxText = "CheckBox";

    private Paint mPaint;

    private Paint mPaintAfter;

    private Paint mPaintText;

    private Path mPath;

    private Path mDst;

    private PathMeasure pathMeasure;

    // 用于外圆边框动画
    private float pathLenthBorder;

    private Path mPathBorder;

    private Path mDstBorder;

    private PathMeasure pathMeasureBorder;

    private float pathLenth;

    private int AnimationValue = 255;

    private int AnimationValueBorder;

    private int AnimationValue1;

    private float AnimationValue2;

    private float AnimationValue3;

    private float AnimationValue4;

    private ValueAnimator valueAnimator;

    private ValueAnimator valueAnimatorBorder;

    private ValueAnimator valueAnimator1;

    private ValueAnimator valueAnimator2;

    private ValueAnimator valueAnimator3;

    private ValueAnimator valueAnimator4;

    private float cAnimationValue = 0;

    private float cAnimationValue1 = 0;

    private int cAnimationValue2 = 255;

    private ValueAnimator cValueAnimator;

    private ValueAnimator cValueAnimator1;

    private ValueAnimator cValueAnimator2;

    private boolean checked = false;

    private boolean isHook = true;

    private boolean isShowBorder = false;

    private int mScreenWidth, mSreenHeight;

    private OnCheckedChangeListener mOnCheckedChangeListener;

    private int redAfter;

    private int greenAfter;

    private int blueAfter;

    private int redBefore;

    private int greenBefore;

    private int blueBefore;

    private boolean isCircle;

    public CheckBox(Context context) {
        super(context);
    }

    public CheckBox(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        int textColor = 0;
        if (attrs != null) {
            TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.BBox);
            int colorAfter = array.getColor(R.styleable.BBox_color_after, Color.GRAY);
            int colorBefore = array.getColor(R.styleable.BBox_color_before, Color.GRAY);
            textColor = array.getColor(R.styleable.BBox_text_color, Color.BLUE);
            redAfter = (colorAfter & 0xff0000) >> 16;
            greenAfter = (colorAfter & 0x00ff00) >> 8;
            blueAfter = (colorAfter & 0x0000ff);
            redBefore = (colorBefore & 0xff0000) >> 16;
            greenBefore = (colorBefore & 0x00ff00) >> 8;
            blueBefore = (colorBefore & 0x0000ff);
            boxText = array.getString(R.styleable.BBox_check_text);
            isHook = array.getInt(R.styleable.BBox_check_style, 1) == 1;
            isShowBorder = array.getBoolean(R.styleable.BBox_show_border, false);
            isCircle = array.getBoolean(R.styleable.BBox_is_circle_border, true);
            if (boxText == null) {
                boxText = "CheckBox";
            }
            array.recycle();
        }
        mPaint.setStrokeWidth(strokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeCap(Paint.Cap.SQUARE);
        mPaint.setDither(true);
        mPaintText = new Paint();
        if (textColor == 0) {
            mPaintText.setColor(Color.WHITE);
        } else {
            mPaintText.setColor(textColor);
        }
        // mPaintText.setStrokeWidth(strokeWidth);
        mPaintText.setAntiAlias(true);
        mPaintText.setStyle(Paint.Style.STROKE);
        mPaintText.setTextSize(textSize);
        mPaintText.setAntiAlias(true);
        mPath = new Path();
        mPathBorder = new Path();
        pathMeasure = new PathMeasure();
        pathMeasureBorder = new PathMeasure();
        mPath.addRect(wStart, hStart, wSize + wStart, hSize + hStart, Path.Direction.CW);
        pathMeasure.setPath(mPath, true);
        pathLenth = pathMeasure.getLength();
        mPathBorder.addCircle(wStart + wSize / 2, hStart + hSize / 2, (float) Math.sqrt((wSize / 2) * (wSize / 2) + (hSize / 2) * (hSize / 2)), Path.Direction.CCW);
        pathMeasureBorder.setPath(mPathBorder, true);
        pathLenthBorder = pathMeasureBorder.getLength();
        mDst = new Path();
        mDstBorder = new Path();
        valueAnimator = ValueAnimator.ofInt(255, 0);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                AnimationValue = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        valueAnimatorBorder = ValueAnimator.ofInt(0, (int) pathLenthBorder);
        valueAnimatorBorder.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                AnimationValueBorder = (int) animation.getAnimatedValue();
            }
        });
        if (isHook) {
            valueAnimator1 = ValueAnimator.ofInt(180, 225);
            valueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    AnimationValue1 = (int) animation.getAnimatedValue();
                }
            });
            valueAnimator2 = ValueAnimator.ofFloat(hSize + wSize, wSize * 2 / 5);
            valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    AnimationValue2 = (float) animation.getAnimatedValue();
                }
            });
            valueAnimator3 = ValueAnimator.ofFloat(hSize + wSize, hSize + wSize * 2);
            valueAnimator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    AnimationValue3 = (float) animation.getAnimatedValue();
                }
            });
            valueAnimator4 = ValueAnimator.ofFloat((float) 0.0, (float) 0.207555);
            valueAnimator4.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    AnimationValue4 = (float) animation.getAnimatedValue();
                }
            });
        } else {
            cValueAnimator = ValueAnimator.ofFloat(0, (int) (hSize * 0.4));
            cValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    cAnimationValue = (float) animation.getAnimatedValue();
                }
            });
            cValueAnimator1 = ValueAnimator.ofFloat(0, wSize / 2);
            cValueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    cAnimationValue1 = (float) animation.getAnimatedValue();
                }
            });
        }
        setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                setChecked(!checked);
            }
        });
    }

    public CheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int paddingLeft = getPaddingLeft() + 10;
        int paddingRight = getPaddingRight() + 10;
        int paddingTop = getPaddingTop() + 10;
        int paddingBottom = getPaddingBottom() + 10;
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(2 * strokeWidth + wStart + wSize + 20 + paddingLeft + textSize * boxText.length(), hStart + hSize + paddingTop + paddingBottom + 2 * strokeWidth);
            Log.d(TAG, wStart + " " + wSize + " " + textSize * boxText.length() + "   " + boxText.length());
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(2 * strokeWidth + wStart + wSize + paddingLeft + 20 + textSize * boxText.length(), widthSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(heightSpecSize, hStart + hSize + paddingTop + paddingBottom + 2 * strokeWidth);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    public void setChecked(boolean set) {
        if (checked == set) {
            return;
        } else {
            if (checked) {
                animationReverse();
            } else {
                animationStart();
            }
            checked = set;
        }
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onChange(checked);
        }
        postInvalidate();
    }

    public boolean isChecked() {
        return checked;
    }

    @Override
    public void toggle() {
        setChecked(!checked);
    }

    public void setText(String text) {
        boxText = text;
        postInvalidate();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mScreenWidth = w;
        mSreenHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mDst.reset();
        mDstBorder.reset();
        mDstBorder.lineTo(0, 0);
        mDst.lineTo(0, 0);
        int paddingLeft = getPaddingLeft() + 10;
        int paddingRight = getPaddingRight() + 10;
        int paddingTop = getPaddingTop() + 10;
        int paddingBottom = getPaddingBottom() + 10;
        int r = (int) ((1 - (float) AnimationValue / 255) * redBefore + (float) AnimationValue / 255 * redAfter);
        int g = (int) ((1 - (float) AnimationValue / 255) * greenBefore + (float) AnimationValue / 255 * greenAfter);
        int b = (int) ((1 - (float) AnimationValue / 255) * blueBefore + (float) AnimationValue / 255 * blueAfter);
        mPaintColor = Color.rgb(r, g, b);
        mPaint.setColor(mPaintColor);
        if (isHook) {
            canvas.translate(wStart + paddingLeft, hStart + paddingTop);
            if (isShowBorder) {
                canvas.save();
            }
            canvas.drawText(boxText, wStart + wSize + 18, (hSize + textSize) / 2, mPaintText);
            // 确保图形在正中
            canvas.translate(-AnimationValue4 * wSize, (float) (-AnimationValue4 * hSize * 1.5));
            /**
             * 如果 startWithMoveTo 为 true, 则被截取出来到Path片段保持原状,如果 startWithMoveTo 为 false,
             * 则会将截取出来的 Path 片段的起始点移动到 dst 的最后一个点,以保证 dst 的连续性。
             */
            pathMeasure.getSegment(AnimationValue3, pathLenth, mDst, true);
            pathMeasure.getSegment(0, wSize * 2 / 5, mDst, true);
            pathMeasure.getSegment(wSize * 2 / 5, AnimationValue2, mDst, true);
            canvas.rotate(AnimationValue1, wStart + wSize / 2, hStart + hSize / 2);
            canvas.drawPath(mDst, mPaint);
            if (isShowBorder) {
                canvas.restore();
                if (isCircle) {
                    pathMeasureBorder.getSegment(0, AnimationValueBorder, mDstBorder, true);
                    canvas.drawPath(mDstBorder, mPaintText);
                } else {
                    canvas.drawRect(wStart, hStart, wStart + wSize, hSize + hStart, mPaintText);
                }
            }
        } else {
            canvas.translate(wStart + paddingLeft, hStart + paddingTop);
            if (isShowBorder) {
                canvas.save();
            }
            canvas.drawText(boxText, wStart + wSize + 18, (hSize + textSize) / 2, mPaintText);
            mDst.moveTo((float) (wStart + cAnimationValue * 0.3), (float) (hStart + cAnimationValue * 0.3));
            mDst.lineTo(wStart + wSize / 2, (float) (hStart + cAnimationValue + cAnimationValue * 0.2));
            mDst.lineTo((float) (wStart + wSize - cAnimationValue * 0.2), (float) (hStart + cAnimationValue * 0.2));
            mDst.lineTo((float) (wStart + wSize - cAnimationValue - cAnimationValue * 0.2), hSize / 2 + hStart);
            mDst.lineTo((float) (wStart + wSize - cAnimationValue * 0.2), (float) (hStart + hSize - cAnimationValue * 0.2));
            mDst.lineTo(wStart + wSize / 2, (float) (hStart + hSize - cAnimationValue - cAnimationValue * 0.2));
            mDst.lineTo((float) (wStart + cAnimationValue * 0.2), (float) (hStart + hSize - cAnimationValue * 0.2));
            mDst.lineTo((float) (wStart + cAnimationValue + cAnimationValue * 0.2), hStart + hSize / 2);
            mDst.lineTo((float) (wStart + cAnimationValue * 0.3), (float) (hStart + cAnimationValue * 0.3));
            canvas.drawPath(mDst, mPaint);
            if (isShowBorder) {
                canvas.restore();
                if (isCircle) {
                    pathMeasureBorder.getSegment(0, AnimationValueBorder, mDstBorder, true);
                    canvas.drawPath(mDstBorder, mPaintText);
                } else {
                    canvas.drawRect(wStart, hStart, wStart + wSize, hSize + hStart, mPaintText);
                }
            }
        }
    }

    private void animationStart() {
        if (isHook) {
            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.playTogether(valueAnimator1, valueAnimator2, valueAnimator3, valueAnimator4, valueAnimatorBorder, valueAnimator);
            animatorSet.setInterpolator(new DecelerateInterpolator());
            valueAnimatorBorder.setInterpolator(new AccelerateInterpolator());
            animatorSet.setDuration(Duration);
            animatorSet.start();
        } else {
            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.playTogether(cValueAnimator, cValueAnimator1, valueAnimatorBorder, valueAnimator);
            animatorSet.setInterpolator(new DecelerateInterpolator());
            valueAnimatorBorder.setInterpolator(new AccelerateInterpolator());
            animatorSet.setDuration(Duration);
            animatorSet.start();
        }
    }

    private void animationReverse() {
        if (isHook) {
            valueAnimator1.reverse();
            valueAnimator2.reverse();
            valueAnimator3.reverse();
            valueAnimator4.reverse();
        } else {
            cValueAnimator.reverse();
            cValueAnimator1.reverse();
        }
        valueAnimatorBorder.reverse();
        valueAnimator.reverse();
    }

    public int dp2px(float value) {
        float density = getResources().getDisplayMetrics().density;
        return (int) (value * density + 0.5f);
    }

    public interface OnCheckedChangeListener {

        void onChange(boolean checked);
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        this.mOnCheckedChangeListener = listener;
    }
}

18 Source : PathEvaluator.java
with Apache License 2.0
from wirecube

public float evaluate(float fraction, PathMode pathMode, Path path) {
    if (fraction == lastEvaluatedFraction) {
        return getResult(pathMode);
    }
    float[] tan = new float[2];
    PathMeasure pathMeasure = new PathMeasure(path, true);
    pathMeasure.getPosTan(pathMeasure.getLength() * fraction, lastPoint, tan);
    lastAngle = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
    lastEvaluatedFraction = fraction;
    return getResult(pathMode);
}

18 Source : ArrowPathFooterDrawer.java
with Apache License 2.0
from uin3566

/**
 * Created by Administrator on 2016/12/1.
 */
public clreplaced ArrowPathFooterDrawer extends BaseFooterDrawer {

    private static final String TAG = "ArrowPathFooterDrawer";

    // arrow
    private boolean hasInitArrowPath = false;

    private PathMeasure arrowPathMeasure;

    private Path arrowPath;

    private Path drawingArrowPath = new Path();

    private IconRotateController rotateController;

    private ArrowPathParams arrowPathParams;

    // hintText string
    private String hintText;

    private ArrayList<float[]> hintLineList;

    private ArrayList<Path> hintPathList;

    private PathMeasure hintPathMeasure;

    private Path drawingHintPath;

    private float hintOffsetX, hintOffsetY;

    private float hintWidth, hintHeight;

    private boolean hasBeginDrawArrowPath;

    private Paint pathPaint;

    private Paint bgPaint;

    private float rotateThreshold;

    private static clreplaced ArrowPathParams {

        float finishDrawArrowPathDragDistance;

        float arrowHeadLength;

        float arrowHeadHeight;

        float arrowTailHeight;

        float arrowTailLength;

        float arrowSmallRectLength;

        float arrowSmallRectMargin;

        float arrowMarginLeft;

        float arrowMarginRight;

        float arrowRightMostPosX;

        float arrowTailPosY;
    }

    private ArrowPathFooterDrawer(Builder builder) {
        hintText = builder.hintText;
        footerColor = builder.footerColor;
        footerRegion = new RectF();
        rotateThreshold = DimenUtil.dp2px(builder.context, builder.rotateThreshold);
        rotateController = new IconRotateController(rotateThreshold, builder.rotateDuration);
        pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        pathPaint.setStyle(Paint.Style.STROKE);
        pathPaint.setColor(builder.pathColor);
        pathPaint.setStrokeWidth(DimenUtil.dp2px(builder.context, builder.pathWidth));
        bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bgPaint.setColor(footerColor);
        bgPaint.setStyle(Paint.Style.FILL);
        setArrowPathParams(builder);
        setHintString();
    }

    private void setArrowPathParams(Builder builder) {
        arrowPathParams = new ArrowPathParams();
        Context context = builder.context;
        arrowPathParams.finishDrawArrowPathDragDistance = DimenUtil.dp2px(context, builder.finishDrawArrowPathDragDistance);
        arrowPathParams.arrowHeadLength = DimenUtil.dp2px(context, builder.arrowHeadLength);
        arrowPathParams.arrowHeadHeight = DimenUtil.dp2px(context, builder.arrowHeadHeight);
        arrowPathParams.arrowTailHeight = DimenUtil.dp2px(context, builder.arrowTailHeight);
        arrowPathParams.arrowTailLength = DimenUtil.dp2px(context, builder.arrowTailLength);
        arrowPathParams.arrowSmallRectLength = DimenUtil.dp2px(context, builder.arrowSmallRectLength);
        arrowPathParams.arrowSmallRectMargin = DimenUtil.dp2px(context, builder.arrowSmallRectMargin);
        arrowPathParams.arrowMarginLeft = DimenUtil.dp2px(context, builder.arrowMarginLeft);
        arrowPathParams.arrowMarginRight = DimenUtil.dp2px(context, builder.arrowMarginRight);
    }

    public void setHintString() {
        if (hintText == null || hintText.isEmpty()) {
            return;
        }
        hintPathMeasure = new PathMeasure();
        drawingHintPath = new Path();
        hintLineList = StoreHousePath.getPath(hintText, 65 * 0.01f, 24);
        setHintWidthAndHeight();
        initHintPathArray();
    }

    private void setHintWidthAndHeight() {
        for (int i = 0; i < hintLineList.size(); i++) {
            float[] line = hintLineList.get(i);
            hintWidth = Math.max(hintWidth, line[0]);
            hintWidth = Math.max(hintWidth, line[2]);
            hintHeight = Math.max(hintHeight, line[1]);
            hintHeight = Math.max(hintHeight, line[3]);
        }
    }

    private void initHintPathArray() {
        hintPathList = new ArrayList<>(hintLineList.size());
        for (int i = 0; i < hintLineList.size(); i++) {
            float[] line = hintLineList.get(i);
            Path path = new Path();
            path.moveTo(line[0], line[1]);
            path.lineTo(line[2], line[3]);
            hintPathList.add(path);
        }
    }

    @Override
    public void drawFooter(Canvas canvas, float left, float top, float right, float bottom) {
        super.drawFooter(canvas, left, top, right, bottom);
        drawRect(canvas);
        drawArrow(canvas);
        drawHintText(canvas);
    }

    private void drawRect(Canvas canvas) {
        canvas.drawRect(footerRegion, bgPaint);
    }

    private void drawArrow(Canvas canvas) {
        initArrowPath();
        drawArrowPath(canvas);
    }

    private void drawHintText(Canvas canvas) {
        if (hintText == null || !hasBeginDrawArrowPath) {
            return;
        }
        setStringOffset();
        float moveX = getDragDistance() - getArrowTotalLength() - arrowPathParams.arrowMarginLeft;
        float percent = getDragPercent(moveX);
        canvas.save();
        canvas.translate(hintOffsetX, hintOffsetY);
        for (int i = 0; i < hintLineList.size(); i++) {
            Path path = hintPathList.get(i);
            drawingHintPath.reset();
            hintPathMeasure.setPath(path, false);
            hintPathMeasure.getSegment(0, percent * hintPathMeasure.getLength(), drawingHintPath, true);
            canvas.drawPath(drawingHintPath, pathPaint);
        }
        canvas.restore();
    }

    private void setStringOffset() {
        hintOffsetX = arrowPathParams.arrowRightMostPosX + arrowPathParams.arrowMarginRight;
        hintOffsetY = footerRegion.top + (footerRegion.bottom - footerRegion.top - hintHeight) / 2;
    }

    private void initArrowPath() {
        if (hasInitArrowPath) {
            return;
        }
        arrowPathMeasure = new PathMeasure();
        arrowPath = new Path();
        arrowPathParams.arrowRightMostPosX = footerRegion.right;
        arrowPathParams.arrowTailPosY = (footerRegion.bottom - footerRegion.top) / 2 - arrowPathParams.arrowTailHeight / 2;
        setArrowPath(arrowPathParams.arrowRightMostPosX, arrowPathParams.arrowTailPosY);
        hasInitArrowPath = true;
    }

    // set arrow path
    private void setArrowPath(float rightMostPosX, float tailPosY) {
        arrowPath.reset();
        float arrowHeadDy = (arrowPathParams.arrowHeadHeight - arrowPathParams.arrowTailHeight) / 2;
        float arrowTailLength = arrowPathParams.arrowTailLength;
        float arrowTailHeight = arrowPathParams.arrowTailHeight;
        float rectLength = arrowPathParams.arrowSmallRectLength;
        float rectMargin = arrowPathParams.arrowSmallRectMargin;
        float tailPos = rightMostPosX - (arrowPathParams.arrowSmallRectLength + arrowPathParams.arrowSmallRectMargin) * 2;
        arrowPath.moveTo(tailPos, tailPosY);
        arrowPath.lineTo(tailPos - arrowTailLength, tailPosY);
        arrowPath.lineTo(tailPos - arrowTailLength, tailPosY - arrowHeadDy);
        arrowPath.lineTo(tailPos - arrowTailLength - arrowPathParams.arrowHeadLength, tailPosY + arrowTailHeight / 2);
        arrowPath.lineTo(tailPos - arrowTailLength, tailPosY + arrowTailHeight + arrowHeadDy);
        arrowPath.lineTo(tailPos - arrowTailLength, tailPosY + arrowTailHeight);
        arrowPath.lineTo(tailPos, tailPosY + arrowTailHeight);
        arrowPath.lineTo(tailPos, tailPosY);
        arrowPath.addRect(tailPos + rectMargin, tailPosY, tailPos + rectMargin + rectLength, tailPosY + arrowTailHeight, Path.Direction.CCW);
        arrowPath.addRect(tailPos + rectMargin * 2 + rectLength, tailPosY, tailPos + 2 * (rectMargin + rectLength), tailPosY + arrowTailHeight, Path.Direction.CW);
        arrowPathMeasure.setPath(arrowPath, false);
    }

    private float getDragDistance() {
        return footerRegion.right - footerRegion.left;
    }

    private float getArrowTotalLength() {
        return arrowPathParams.arrowHeadLength + arrowPathParams.arrowTailLength + arrowPathParams.arrowSmallRectMargin * 2 + arrowPathParams.arrowSmallRectLength * 2;
    }

    private float getDragPercent(float moveX) {
        float percent;
        if (moveX < arrowPathParams.finishDrawArrowPathDragDistance) {
            percent = moveX / arrowPathParams.finishDrawArrowPathDragDistance;
        } else {
            percent = 1.0f;
        }
        return percent;
    }

    private void drawArrowPath(Canvas canvas) {
        float arrowTotalLength = getArrowTotalLength();
        float arrowMarginLeft = arrowPathParams.arrowMarginLeft;
        float dragDx = getDragDistance();
        hasBeginDrawArrowPath = false;
        if (dragDx < arrowMarginLeft + arrowTotalLength) {
            return;
        }
        hasBeginDrawArrowPath = true;
        drawingArrowPath.reset();
        float moveX = dragDx - arrowTotalLength - arrowMarginLeft;
        float percent = getDragPercent(moveX);
        // move arrow
        arrowPathParams.arrowRightMostPosX = footerRegion.right - moveX;
        setArrowPath(arrowPathParams.arrowRightMostPosX, arrowPathParams.arrowTailPosY);
        arrowPathMeasure.getSegment(0, percent * arrowPathMeasure.getLength(), drawingArrowPath, true);
        while (arrowPathMeasure.nextContour()) {
            arrowPathMeasure.getSegment(0, percent * arrowPathMeasure.getLength(), drawingArrowPath, true);
        }
        // rotate arrow
        canvas.save();
        rotateController.calculateRotateDegree(dragState, getDragDistance());
        float degree = rotateController.getRotateDegree();
        float centerX = arrowPathParams.arrowRightMostPosX - arrowTotalLength / 2;
        float centerY = arrowPathParams.arrowTailPosY + arrowPathParams.arrowTailHeight / 2;
        canvas.rotate(degree, centerX, centerY);
        canvas.drawPath(drawingArrowPath, pathPaint);
        canvas.restore();
    }

    @Override
    public void updateDragState(int dragState) {
        super.updateDragState(dragState);
        if (dragState == DragContainer.RELEASE) {
            rotateController.reset();
        }
    }

    @Override
    public boolean shouldTriggerEvent(float dragDistance) {
        return dragDistance > rotateThreshold;
    }

    public static clreplaced Builder {

        private static final int DEFAULT_ROTATE_DURATION = 150;

        private static final float DEFAULT_ARROW_MARGIN = 8;

        private static final float DEFAULT_ROTATE_THRESHOLD = 100;

        private static final float DEFAULT_ARROW_HEAD_LENGTH = 15;

        private static final float DEFAULT_ARROW_HEAD_HEIGHT = 15;

        private static final float DEFAULT_ARROW_TAIL_LENGTH = 10;

        private static final float DEFAULT_ARROW_TAIL_HEIGHT = 5;

        private static final float DEFAULT_ARROW_SMALL_RECT_LENGTH = 2.5f;

        private static final float DEFAULT_ARROW_SMALL_RECT_MARGIN = 2.5f;

        private static final float DEFAULT_FINISH_DRAW_ARROW_PATH_DRAG_DISTANCE = 60;

        private static final float DEFAULT_PATH_WIDTH = 1;

        private static final int DEFAULT_PATH_COLOR = 0xffcdcdcd;

        private static final String DEFAULT_HINT_TEXT = "MORE";

        private final int footerColor;

        private final Context context;

        // length in dp
        private float arrowMarginLeft = DEFAULT_ARROW_MARGIN;

        private float arrowMarginRight = DEFAULT_ARROW_MARGIN;

        private float arrowHeadLength = DEFAULT_ARROW_HEAD_LENGTH;

        private float arrowHeadHeight = DEFAULT_ARROW_HEAD_HEIGHT;

        private float arrowTailLength = DEFAULT_ARROW_TAIL_LENGTH;

        private float arrowTailHeight = DEFAULT_ARROW_TAIL_HEIGHT;

        private float arrowSmallRectLength = DEFAULT_ARROW_SMALL_RECT_LENGTH;

        private float arrowSmallRectMargin = DEFAULT_ARROW_SMALL_RECT_MARGIN;

        private float finishDrawArrowPathDragDistance = DEFAULT_FINISH_DRAW_ARROW_PATH_DRAG_DISTANCE;

        private float rotateThreshold = DEFAULT_ROTATE_THRESHOLD;

        private float pathWidth = DEFAULT_PATH_WIDTH;

        private int rotateDuration = DEFAULT_ROTATE_DURATION;

        private String hintText = DEFAULT_HINT_TEXT;

        private int pathColor = DEFAULT_PATH_COLOR;

        public Builder(Context context, int footerColor) {
            this.context = context;
            this.footerColor = footerColor;
        }

        public Builder setPathColor(int pathColor) {
            this.pathColor = pathColor;
            return this;
        }

        public Builder setArrowMarginLeft(float arrowMarginLeft) {
            this.arrowMarginLeft = arrowMarginLeft;
            return this;
        }

        public Builder setArrowMarginRight(float arrowMarginRight) {
            this.arrowMarginRight = arrowMarginRight;
            return this;
        }

        public Builder setArrowHeadLength(float arrowHeadLength) {
            this.arrowHeadLength = arrowHeadLength;
            return this;
        }

        public Builder setArrowHeadHeight(float arrowHeadHeight) {
            this.arrowHeadHeight = arrowHeadHeight;
            return this;
        }

        public Builder setArrowTailLength(float arrowTailLength) {
            this.arrowTailLength = arrowTailLength;
            return this;
        }

        public Builder setArrowTailHeight(float arrowTailHeight) {
            this.arrowTailHeight = arrowTailHeight;
            return this;
        }

        public Builder setArrowSmallRectLength(float arrowSmallRectLength) {
            this.arrowSmallRectLength = arrowSmallRectLength;
            return this;
        }

        public Builder setArrowSmallRectMargin(float arrowSmallRectMargin) {
            this.arrowSmallRectMargin = arrowSmallRectMargin;
            return this;
        }

        public Builder setFinishDrawArrowPathDragDistance(float finishDrawArrowPathDragDistance) {
            this.finishDrawArrowPathDragDistance = finishDrawArrowPathDragDistance;
            return this;
        }

        public Builder setRotateThreshold(float rotateThreshold) {
            this.rotateThreshold = rotateThreshold;
            return this;
        }

        public Builder setPathWidth(float pathWidth) {
            this.pathWidth = pathWidth;
            return this;
        }

        public Builder setRotateDuration(int rotateDuration) {
            this.rotateDuration = rotateDuration;
            return this;
        }

        public Builder setHintText(String hintText) {
            this.hintText = hintText;
            return this;
        }

        public ArrowPathFooterDrawer build() {
            return new ArrowPathFooterDrawer(this);
        }
    }
}

18 Source : FloatingPath.java
with Apache License 2.0
from UFreedom

/**
 * Author UFreedom
 */
public clreplaced FloatingPath {

    private Path mPath;

    private PathMeasure mPathMeasure;

    protected FloatingPath() {
        this.mPath = new Path();
    }

    protected FloatingPath(Path path) {
        this.mPath = path;
    }

    public static FloatingPath create(Path path, boolean forceClose) {
        FloatingPath floatingPath = new FloatingPath(path);
        floatingPath.mPathMeasure = new PathMeasure(path, forceClose);
        return floatingPath;
    }

    public Path getPath() {
        return mPath;
    }

    public PathMeasure getPathMeasure() {
        return mPathMeasure;
    }
}

18 Source : WinSearch.java
with Apache License 2.0
from stytooldex

/**
 * Created by zhangml on 2016/9/12 0012.
 */
public clreplaced WinSearch extends View {

    private Paint mPaint;

    private Path mPath;

    private PathMeasure mPathMeasure;

    private int mWidth, mHeight;

    private ValueAnimator valueAnimator;

    // 用这个来接受ValueAnimator的返回值,代表整个动画的进度
    private float t;

    public WinSearch(Context context) {
        super(context);
    }

    public WinSearch(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
        valueAnimator.start();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(15);
        // mPaint.setColor(WinSearch.this.getResources().getColor(R.color.colorPrimary));
        // 设置画笔为园笔
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        // 抗锯齿
        mPaint.setAntiAlias(true);
        mPath = new Path();
        RectF rect = new RectF(-150, -150, 150, 150);
        mPath.addArc(rect, -90, 359.9f);
        mPathMeasure = new PathMeasure(mPath, false);
        valueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(3000);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                t = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(mWidth / 2, mHeight / 2);
        Path dst = new Path();
        if (t >= 0.95) {
            canvas.drawPoint(0, -150, mPaint);
        }
        int num = (int) (t / 0.05);
        float s, y, x;
        switch(num) {
            default:
            case 3:
                x = t - 0.15f * (1 - t);
                s = mPathMeasure.getLength();
                y = -s * x * x + 2 * s * x;
                mPathMeasure.getSegment(y, y + 1, dst, true);
            case 2:
                x = t - 0.10f * (1 - t);
                s = mPathMeasure.getLength();
                y = -s * x * x + 2 * s * x;
                mPathMeasure.getSegment(y, y + 1, dst, true);
            case 1:
                x = t - 0.05f * (1 - t);
                s = mPathMeasure.getLength();
                y = -s * x * x + 2 * s * x;
                mPathMeasure.getSegment(y, y + 1, dst, true);
            case 0:
                x = t;
                s = mPathMeasure.getLength();
                y = -s * x * x + 2 * s * x;
                mPathMeasure.getSegment(y, y + 1, dst, true);
                break;
        }
        canvas.drawPath(dst, mPaint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }
}

18 Source : WinSearch.java
with MIT License
from stytooldex

/**
 * Created by zhangml on 2016/9/12 0012.
 */
public clreplaced WinSearch extends View {

    private Paint mPaint;

    private Path mPath;

    private PathMeasure mPathMeasure;

    private int mWidth, mHeight;

    private ValueAnimator valueAnimator;

    // 用这个来接受ValueAnimator的返回值,代表整个动画的进度
    private float t;

    public WinSearch(Context context) {
        super(context);
    }

    public WinSearch(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
        valueAnimator.start();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(15);
        mPaint.setColor(WinSearch.this.getResources().getColor(R.color.colorPrimary));
        // 设置画笔为园笔
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        // 抗锯齿
        mPaint.setAntiAlias(true);
        mPath = new Path();
        RectF rect = new RectF(-150, -150, 150, 150);
        mPath.addArc(rect, -90, 359.9f);
        mPathMeasure = new PathMeasure(mPath, false);
        valueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(3000);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                t = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(mWidth / 2, mHeight / 2);
        Path dst = new Path();
        if (t >= 0.95) {
            canvas.drawPoint(0, -150, mPaint);
        }
        int num = (int) (t / 0.05);
        float s, y, x;
        switch(num) {
            default:
            case 3:
                x = t - 0.15f * (1 - t);
                s = mPathMeasure.getLength();
                y = -s * x * x + 2 * s * x;
                mPathMeasure.getSegment(y, y + 1, dst, true);
            case 2:
                x = t - 0.10f * (1 - t);
                s = mPathMeasure.getLength();
                y = -s * x * x + 2 * s * x;
                mPathMeasure.getSegment(y, y + 1, dst, true);
            case 1:
                x = t - 0.05f * (1 - t);
                s = mPathMeasure.getLength();
                y = -s * x * x + 2 * s * x;
                mPathMeasure.getSegment(y, y + 1, dst, true);
            case 0:
                x = t;
                s = mPathMeasure.getLength();
                y = -s * x * x + 2 * s * x;
                mPathMeasure.getSegment(y, y + 1, dst, true);
                break;
        }
        canvas.drawPath(dst, mPaint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }
}

18 Source : AlipayFailureView.java
with Apache License 2.0
from REBOOTERS

/**
 * Created by rookie on 2016/10/19.
 */
public clreplaced AlipayFailureView extends View {

    private String TAG = AlipayFailureView.clreplaced.getSimpleName();

    private static final float PADDING = 20;

    private static final int DEFAULT_RADIUS = 150;

    private Paint mCirclePanit;

    private Paint mLinePaint;

    private float mStrokeWidth = 10;

    private float factor = 0.8f;

    private float temp;

    private float mCenterX, mCenterY;

    private float mRadius = 250;

    private final RectF mRectF = new RectF();

    private int mDegree;

    private Float mLeftValue = 0f;

    private Float mRightValue = 0f;

    private AnimatorSet mAnimatorSet = new AnimatorSet();

    private ValueAnimator mCircleAnim;

    private ValueAnimator mLineLeftAnimator;

    private ValueAnimator mLineRightAnimator;

    private boolean mIsCanHide;

    private PathMeasure pathLeftMeasure;

    private PathMeasure pathRightMeasure;

    private float[] mLeftPos = new float[2];

    private float[] mRightPos = new float[2];

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

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

    public AlipayFailureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mCirclePanit = new Paint();
        mCirclePanit.setAntiAlias(true);
        mCirclePanit.setStrokeJoin(Paint.Join.ROUND);
        mCirclePanit.setStrokeWidth(mStrokeWidth);
        mCirclePanit.setColor(Color.WHITE);
        mCirclePanit.setStyle(Paint.Style.STROKE);
        mLinePaint = new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setStrokeJoin(Paint.Join.ROUND);
        mLinePaint.setStrokeWidth(mStrokeWidth);
        mLinePaint.setColor(Color.WHITE);
        mLinePaint.setStyle(Paint.Style.STROKE);
        reset();
        reMeasure();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mRectF.left = mCenterX - mRadius;
        mRectF.top = mCenterY - mRadius;
        mRectF.right = mCenterX + mRadius;
        mRectF.bottom = mCenterY + mRadius;
        canvas.drawArc(mRectF, 0, mDegree, false, mCirclePanit);
        if (mLeftPos[1] > (mCenterY - temp) && mRightPos[1] > (mCenterY - temp)) {
            canvas.drawLine(mCenterX - temp, mCenterY - temp, mLeftPos[0], mLeftPos[1], mLinePaint);
            canvas.drawLine(mCenterX + temp, mCenterY - temp, mRightPos[0], mRightPos[1], mLinePaint);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        reMeasure();
    }

    private void reMeasure() {
        int mViewWidth = getWidth();
        int mViewHeight = getHeight();
        mCenterX = mViewWidth / 2;
        mCenterY = mViewHeight / 2;
        temp = mRadius / 2.0f * factor;
        Path path = new Path();
        path.moveTo(mCenterX - temp, mCenterY - temp);
        path.lineTo(mCenterX + temp, mCenterY + temp);
        pathLeftMeasure = new PathMeasure(path, false);
        path = new Path();
        path.moveTo(mCenterX + temp, mCenterY - temp);
        path.lineTo(mCenterX - temp, mCenterY + temp);
        pathRightMeasure = new PathMeasure(path, false);
    }

    public void startAnim(int mRadius) {
        mRadius = mRadius <= 0 ? DEFAULT_RADIUS : mRadius;
        this.mRadius = mRadius - PADDING;
        if (null != mAnimatorSet && mAnimatorSet.isRunning()) {
            return;
        }
        reset();
        reMeasure();
        mCircleAnim = ValueAnimator.ofInt(0, 360);
        mLineLeftAnimator = ValueAnimator.ofFloat(0, pathLeftMeasure.getLength());
        mLineRightAnimator = ValueAnimator.ofFloat(0, pathRightMeasure.getLength());
        mCircleAnim.setDuration(700);
        mLineLeftAnimator.setDuration(350);
        mLineRightAnimator.setDuration(350);
        mCircleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mDegree = (Integer) animation.getAnimatedValue();
                invalidate();
            }
        });
        mLineLeftAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mLeftValue = (Float) valueAnimator.getAnimatedValue();
                Log.e("left", "-------->" + mLeftValue);
                pathLeftMeasure.getPosTan(mLeftValue, mLeftPos, null);
                invalidate();
            }
        });
        mLineRightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRightValue = (Float) animation.getAnimatedValue();
                pathRightMeasure.getPosTan(mRightValue, mRightPos, null);
                invalidate();
            }
        });
        mAnimatorSet.play(mCircleAnim).before(mLineLeftAnimator);
        mAnimatorSet.play(mLineRightAnimator).after(mLineLeftAnimator);
        mAnimatorSet.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                stop();
                if (mEndListner != null) {
                    mEndListner.onCircleDone();
                    failureAnim();
                }
            }
        });
        mAnimatorSet.start();
    }

    private void failureAnim() {
        float currentX = this.getTranslationX();
        ObjectAnimator tansXAnim = ObjectAnimator.ofFloat(this, "translationX", currentX + 20);
        tansXAnim.setDuration(1000);
        tansXAnim.setInterpolator(new CycleInterpolator(3));
        tansXAnim.start();
    }

    public void stop() {
        if (null != mCircleAnim) {
            mCircleAnim.end();
        }
        if (null != mLineLeftAnimator) {
            mLineLeftAnimator.end();
        }
        if (null != mLineRightAnimator) {
            mLineRightAnimator.end();
        }
        clearAnimation();
    }

    public void reset() {
        mDegree = 0;
        mLeftValue = 0f;
        mRightValue = 0f;
        pathLeftMeasure = null;
        pathRightMeasure = null;
    }

    private OnCircleFinishListener mEndListner;

    public void addCircleAnimatorEndListner(OnCircleFinishListener endListenr) {
        if (null == mEndListner) {
            this.mEndListner = endListenr;
        }
    }

    public interface OnCircleFinishListener {

        void onCircleDone();
    }

    public void setPaintColor(int color) {
        mCirclePanit.setColor(color);
        mLinePaint.setColor(color);
        invalidate();
    }
}

18 Source : MusicAnimBean.java
with GNU Affero General Public License v3.0
from qwsem

/**
 * Created by cxf on 2018/7/2.
 */
public clreplaced MusicAnimBean {

    private static final int STATUS_START = 0;

    private static final int STATUS_MIDDLE = 1;

    private static final int STATUS_END = 2;

    private ImageView mImageView;

    private ValueAnimator mAnimator;

    private PathMeasure mPathMeasure;

    private float mLength;

    private float[] mPos;

    private int mOffest;

    private int mStatus = STATUS_START;

    private boolean mAnimStarted;

    private int mRotateDirection;

    public MusicAnimBean(int offest) {
        mPos = new float[2];
        mOffest = offest;
    }

    public void setPathMeasure(PathMeasure pathMeasure) {
        mPathMeasure = pathMeasure;
    }

    public void setImageView(ImageView imageView) {
        mImageView = imageView;
        mImageView.setAlpha(0f);
    }

    public void setAnimator(ValueAnimator animator, float length, int rotateDirection) {
        mLength = length;
        mAnimator = animator;
        mRotateDirection = rotateDirection;
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float v = (float) animation.getAnimatedValue();
                if (mPathMeasure != null) {
                    mPathMeasure.getPosTan(v, mPos, null);
                    mImageView.setX(mPos[0] - mOffest);
                    mImageView.setY(mPos[1] - mOffest);
                    if (mStatus == STATUS_START) {
                        float rate = (int) (v / mLength / 0.3f * 100f);
                        if (rate >= 100) {
                            rate = 100;
                            mStatus = STATUS_MIDDLE;
                        }
                        float r = rate / 100f;
                        mImageView.setAlpha(r);
                        mImageView.setScaleX(0.2f + r);
                        mImageView.setScaleY(0.2f + r);
                        mImageView.setRotation(mRotateDirection * 30 * r);
                    }
                    if (mStatus == STATUS_MIDDLE) {
                        float rate = (int) ((1 - v / mLength) / 0.3f * 100f);
                        if (rate <= 100) {
                            mStatus = STATUS_END;
                        }
                    }
                    if (mStatus == STATUS_END) {
                        float rate = (int) ((1 - v / mLength) / 0.3f * 100f);
                        if (rate <= 0.1f) {
                            rate = 0;
                            mStatus = STATUS_START;
                        }
                        float r = rate / 100f;
                        mImageView.setAlpha(r);
                        // mImageView.setScaleX(0.3f + r);
                        // mImageView.setScaleY(0.3f + r);
                        mImageView.setRotation(mRotateDirection * 30 * r);
                    }
                }
            }
        });
    }

    public void startAnim() {
        if (mAnimator != null) {
            if (mAnimStarted) {
                if (mAnimator.isPaused()) {
                    mAnimator.resume();
                }
            } else {
                mStatus = STATUS_START;
                mAnimator.start();
                mAnimStarted = true;
            }
        }
    }

    public void pauseAnim() {
        if (mAnimator != null && mAnimator.isStarted()) {
            mAnimator.pause();
        }
    }

    public void cancelAnim() {
        if (mAnimator != null) {
            mImageView.setAlpha(0f);
            mAnimator.cancel();
            mAnimStarted = false;
        }
    }
}

18 Source : PathMeasureSubject.java
with Apache License 2.0
from pkware

/**
 * Propositions for {@link PathMeasure} subjects.
 */
public clreplaced PathMeasureSubject extends Subject {

    @Nullable
    private final PathMeasure actual;

    public PathMeasureSubject(@Nonnull FailureMetadata failureMetadata, @Nullable PathMeasure actual) {
        super(failureMetadata, actual);
        this.actual = actual;
    }

    public void hasLength(float length, float tolerance) {
        check("getLength()").that(actual.getLength()).isWithin(tolerance).of(length);
    }

    public void isClosed() {
        check("isClosed()").that(actual.isClosed()).isTrue();
    }

    public void isNotClosed() {
        check("isClosed()").that(actual.isClosed()).isFalse();
    }
}

18 Source : MaterialDesignProgressBar.java
with Apache License 2.0
from pinguo-sunjianfei

/**
 * time:2016/7/22
 * description:匀加速的圆形进度条
 *
 * @author sunjianfei
 */
public clreplaced MaterialDesignProgressBar extends View {

    private Path mPath;

    private Paint mPaint;

    private PathMeasure mPathMeasure;

    private float mAnimatorValue;

    private Path mDst;

    private float mLength;

    ValueAnimator valueAnimator;

    boolean isInit;

    private int mStokeWidth;

    private int mStrokeColor;

    public MaterialDesignProgressBar(Context context) {
        super(context);
    }

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

    public MaterialDesignProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs, defStyleAttr);
    }

    private void initAttr(Context context, AttributeSet attrs, int defStyle) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MaterialDesignProgressBar, defStyle, 0);
        mStokeWidth = a.getDimensionPixelSize(R.styleable.MaterialDesignProgressBar_progress_stroke_width, (int) PixelUtil.dp2px(2));
        mStrokeColor = a.getColor(R.styleable.MaterialDesignProgressBar_progress_stroke_color, 0xff000000);
    }

    private void init(int width) {
        isInit = true;
        float size = width * 1.0f / 2;
        mPathMeasure = new PathMeasure();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mStokeWidth);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setColor(mStrokeColor);
        mPath = new Path();
        mPath.addCircle(size, size, size - mStokeWidth, Path.Direction.CW);
        mPathMeasure.setPath(mPath, true);
        mLength = mPathMeasure.getLength();
        mDst = new Path();
        valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mAnimatorValue = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.setDuration(1500);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!isInit) {
            int width = View.MeasureSpec.getSize(widthMeasureSpec);
            init(width);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mDst.reset();
        // 硬件加速的BUG
        mDst.lineTo(0, 0);
        float stop = mLength * mAnimatorValue;
        float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * mLength));
        mPathMeasure.getSegment(start, stop, mDst, true);
        canvas.drawPath(mDst, mPaint);
    }

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        // 如果View状态发生变化,开/关插值计算
        if (null != valueAnimator) {
            if (visibility == GONE) {
                valueAnimator.end();
            } else {
                valueAnimator.start();
            }
        }
    }
}

18 Source : PopupLayer.java
with MIT License
from panshen

/**
 * 用来给每一个button设置一个中心点
 *
 * @param orbit 一个特定角度的path
 */
private void setPos(Path orbit) {
    PathMeasure measure = new PathMeasure(orbit, false);
    TextLableView tv;
    for (int i = 0; i < mButtons.size(); i++) {
        PopupButton pp = mButtons.get(i);
        tv = kvs.get(pp);
        float[] coords = new float[] { 0f, 0f };
        int length = (int) ((i) * measure.getLength() / mButtons.size());
        measure.getPosTan(length, coords, null);
        int px = (int) coords[0] - pp.getMeasuredWidth() / 2;
        int py = (int) coords[1] - pp.getMeasuredHeight() / 2;
        int tvx = (int) coords[0] - tv.getMeasuredWidth() / 2;
        tv.x = tvx;
        tv.y = py - 60;
        pp.x = px;
        pp.y = py;
    }
}

18 Source : ScopeView.java
with GNU General Public License v3.0
from mmbuw

private int channelOnCount() {
    int count = 0;
    PathMeasure measure = new PathMeasure(mPathChan1, false);
    count += measure.getLength() > 0 ? 1 : 0;
    measure.setPath(mPathChan2, false);
    count += measure.getLength() > 0 ? 1 : 0;
    return count;
}

18 Source : ScopeView.java
with GNU General Public License v3.0
from mmbuw

private float smallestDistanceToPath(Path path, float x, float y) {
    PathMeasure measure = new PathMeasure(path, false);
    float[] pos = { 0f, 0f };
    float minDist = 1000f;
    float dist;
    for (int i = 0; i < measure.getLength(); ++i) {
        measure.getPosTan(i, pos, null);
        dist = (float) Math.hypot(x - pos[0], y - pos[1]);
        if (dist < minDist)
            minDist = dist;
    }
    return minDist;
}

18 Source : OutsideLineRenderer.java
with Apache License 2.0
from LineChen

/**
 * Created by chenliu on 2017/1/12.<br/>
 * 描述:
 * </br>
 */
public clreplaced OutsideLineRenderer extends AbsRenderer {

    /**
     * 填充画笔*
     */
    private Paint fillPaint;

    private PathMeasure measure;

    /**
     * 动画结束标志
     */
    private boolean isAnimateEnd;

    /**
     * 是否开始绘制,防止动画绘制之前绘制一次
     */
    private boolean isShow;

    private float phase;

    private LinearGradient fillShader;

    public OutsideLineRenderer(Context context, View view) {
        super(context, view);
    }

    @Override
    protected void initPaint() {
        super.initPaint();
        fillPaint = new Paint();
        fillPaint.setStyle(Paint.Style.FILL);
    }

    /**
     * 画坐标轴 刻度值
     * @param canvas
     */
    public void drawCoordinateText(Canvas canvas, Axis axisX, Axis axisY, int moveX) {
        if (axisX != null && axisY != null) {
            // ////// X 轴
            // 1.刻度
            coordPaint.setColor(axisX.getTextColor());
            coordPaint.setTextSize(LeafUtil.sp2px(mContext, axisX.getTextSize()));
            // 获取标题文字的高度(fontMetrics.descent - fontMetrics.ascent)
            Paint.FontMetrics fontMetrics = coordPaint.getFontMetrics();
            float textH = fontMetrics.descent - fontMetrics.ascent;
            List<AxisValue> valuesX = axisX.getValues();
            if (axisX.isShowText()) {
                for (int i = 0; i < valuesX.size(); i++) {
                    AxisValue value = valuesX.get(i);
                    if (value.isShowLabel()) {
                        float textW = coordPaint.measureText(value.getLabel());
                        canvas.drawText(value.getLabel(), value.getPointX() - textW / 2 + moveX, value.getPointY() - textH / 2, coordPaint);
                    }
                }
            }
            // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            // ///// Y 轴
            coordPaint.setColor(axisY.getTextColor());
            coordPaint.setTextSize(LeafUtil.sp2px(mContext, axisY.getTextSize()));
            List<AxisValue> valuesY = axisY.getValues();
            if (axisY.isShowText()) {
                for (AxisValue value : valuesY) {
                    float textW = coordPaint.measureText(value.getLabel());
                    float pointx = value.getPointX() - 1.1f * textW;
                    canvas.drawText(value.getLabel(), pointx, value.getPointY(), coordPaint);
                }
            }
        }
    }

    /**
     * 画折线
     *
     * @param canvas
     */
    public void drawLines(Canvas canvas, Line line, Axis axisY, int moveX) {
        if (line != null && isShow) {
            linePaint.setColor(line.getLineColor());
            linePaint.setStrokeWidth(LeafUtil.dp2px(mContext, line.getLineWidth()));
            linePaint.setStyle(Paint.Style.STROKE);
            List<PointValue> values = line.getValues();
            Path path = line.getPath();
            int size = values.size();
            for (int i = 0; i < size; i++) {
                PointValue point = values.get(i);
                if (i == 0)
                    path.moveTo(point.getOriginX() + moveX, point.getOriginY());
                else
                    path.lineTo(point.getOriginX() + moveX, point.getOriginY());
            }
            measure = new PathMeasure(path, false);
            linePaint.setPathEffect(createPathEffect(measure.getLength(), phase, 0.0f));
            canvas.save(Canvas.CLIP_SAVE_FLAG);
            canvas.clipRect(axisY.getStartX(), 0, mWidth, mHeight);
            canvas.drawPath(path, linePaint);
            canvas.restore();
        }
    }

    /**
     * 填充
     * @param canvas
     */
    public void drawFillArea(Canvas canvas, Line line, Axis axisX, int moveX) {
        // 继续使用前面的 path
        if (line != null && line.getValues().size() > 1 && isShow) {
            List<PointValue> values = line.getValues();
            PointValue firstPoint = values.get(0);
            float firstX = firstPoint.getOriginX();
            Path path = line.getPath();
            PointValue lastPoint = values.get(values.size() - 1);
            float lastX = lastPoint.getOriginX();
            path.lineTo(lastX + moveX, axisX.getStartY());
            path.lineTo(firstX + moveX, axisX.getStartY());
            path.close();
            if (fillShader == null) {
                fillShader = new LinearGradient(0, 0, 0, mHeight, line.getFillColor(), Color.TRANSPARENT, Shader.TileMode.CLAMP);
                fillPaint.setShader(fillShader);
            }
            if (line.getFillColor() == 0)
                fillPaint.setAlpha(100);
            else
                fillPaint.setColor(line.getFillColor());
            canvas.save(Canvas.CLIP_SAVE_FLAG);
            canvas.clipRect(firstX, 0, phase * (lastX - firstX) + firstX + moveX, mHeight);
            canvas.drawPath(path, fillPaint);
            canvas.restore();
            path.reset();
        }
    }

    /**
     * 画圆点
     * @param canvas
     */
    public void drawPoints(Canvas canvas, Line line, Axis axisY, int moveX) {
        if (line != null && line.isHasPoints() && isShow) {
            List<PointValue> values = line.getValues();
            float radius = LeafUtil.dp2px(mContext, line.getPointRadius());
            float strokeWidth = LeafUtil.dp2px(mContext, 1);
            PointValue point;
            canvas.save(Canvas.CLIP_SAVE_FLAG);
            canvas.clipRect(axisY.getStartX(), 0, mWidth, mHeight);
            for (int i = 0, size = values.size(); i < size; i++) {
                point = values.get(i);
                labelPaint.setStyle(Paint.Style.FILL);
                labelPaint.setColor(line.getPointColor());
                canvas.drawCircle(point.getOriginX() + moveX, point.getOriginY(), radius, labelPaint);
                labelPaint.setStyle(Paint.Style.STROKE);
                labelPaint.setColor(Color.WHITE);
                labelPaint.setStrokeWidth(strokeWidth);
                canvas.drawCircle(point.getOriginX() + moveX, point.getOriginY(), radius, labelPaint);
            }
            canvas.restore();
        }
    }

    public void drawLabels(Canvas canvas, ChartData chartData, Axis axisY, int moveX) {
        if (isAnimateEnd) {
            if (chartData != null) {
                if (chartData.isHasLabels()) {
                    labelPaint.setTextSize(LeafUtil.sp2px(mContext, 12));
                    List<PointValue> values = chartData.getValues();
                    int size = values.size();
                    canvas.save(Canvas.CLIP_SAVE_FLAG);
                    canvas.clipRect(axisY.getStartX(), 0, mWidth, mHeight);
                    for (int i = 0; i < size; i++) {
                        PointValue point = values.get(i);
                        String label = point.getLabel();
                        Rect bounds = new Rect();
                        int length = label.length();
                        labelPaint.getTextBounds(label, 0, length, bounds);
                        float textW = bounds.width();
                        float textH = bounds.height();
                        float left, top, right, bottom;
                        if (length == 1) {
                            left = point.getOriginX() - textW * 2.2f;
                            right = point.getOriginX() + textW * 2.2f;
                        } else if (length == 2) {
                            left = point.getOriginX() - textW * 1.0f;
                            right = point.getOriginX() + textW * 1.0f;
                        } else {
                            left = point.getOriginX() - textW * 0.6f;
                            right = point.getOriginX() + textW * 0.6f;
                        }
                        top = point.getOriginY() - 2.5f * textH;
                        bottom = point.getOriginY() - 0.5f * textH;
                        // 控制位置
                        if (left < axisY.getStartX()) {
                            left = axisY.getStartX();
                            right += left;
                        }
                        if (top < 0) {
                            top = topPadding;
                            bottom += topPadding;
                        }
                        if (right > mWidth) {
                            right -= rightPadding;
                            left -= rightPadding;
                        }
                        RectF rectF = new RectF(left + moveX, top, right + moveX, bottom);
                        float labelRadius = LeafUtil.dp2px(mContext, chartData.getLabelRadius());
                        labelPaint.setColor(chartData.getLabelColor());
                        labelPaint.setStyle(Paint.Style.FILL);
                        canvas.drawRoundRect(rectF, labelRadius, labelRadius, labelPaint);
                        // drawText
                        labelPaint.setColor(Color.WHITE);
                        float xCoordinate = left + (right - left - textW) / 2 + moveX;
                        float yCoordinate = bottom - (bottom - top - textH) / 2;
                        canvas.drawText(point.getLabel(), xCoordinate, yCoordinate, labelPaint);
                    }
                    canvas.restore();
                }
            }
        }
    }

    /**
     * 带动画的绘制
     * @param duration
     */
    public void showWithAnimation(int duration) {
        isAnimateEnd = false;
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "phase", 0.0f, 1.0f);
        animator.setDuration(duration);
        animator.start();
        isShow = true;
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                phase = (float) animation.getAnimatedValue();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isAnimateEnd = true;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
    }

    // showWithAnimation动画开启后会调用该方法
    public void setPhase(float phase) {
        chartView.invalidate();
    }

    private PathEffect createPathEffect(float pathLength, float phase, float offset) {
        return new DashPathEffect(new float[] { phase * pathLength, pathLength }, 0);
    }
}

18 Source : LeafLineRenderer.java
with Apache License 2.0
from LineChen

/**
 * Created by chenliu on 2017/1/9.<br/>
 * 描述:
 * </br>
 */
public clreplaced LeafLineRenderer extends AbsRenderer {

    private static final float LINE_SMOOTHNESS = 0.16f;

    /**
     * 填充画笔
     */
    private Paint fillPaint;

    private PathMeasure measure;

    /**
     * 动画结束标志
     */
    private boolean isAnimateEnd;

    /**
     * 是否开始绘制,防止动画绘制之前绘制一次
     */
    private boolean isShow;

    private float phase;

    private LinearGradient fillShader;

    public LeafLineRenderer(Context context, View view) {
        super(context, view);
    }

    @Override
    protected void initPaint() {
        super.initPaint();
        fillPaint = new Paint();
        fillPaint.setStyle(Paint.Style.FILL);
    }

    /**
     * 画折线
     *
     * @param canvas
     */
    public void drawLines(Canvas canvas, Line line) {
        if (line != null && isShow) {
            linePaint.setColor(line.getLineColor());
            linePaint.setStrokeWidth(LeafUtil.dp2px(mContext, line.getLineWidth()));
            linePaint.setStyle(Paint.Style.STROKE);
            List<PointValue> values = line.getValues();
            Path path = line.getPath();
            int size = values.size();
            for (int i = 0; i < size; i++) {
                PointValue point = values.get(i);
                if (i == 0)
                    path.moveTo(point.getOriginX(), point.getOriginY());
                else
                    path.lineTo(point.getOriginX(), point.getOriginY());
            }
            measure = new PathMeasure(path, false);
            linePaint.setPathEffect(createPathEffect(measure.getLength(), phase, 0.0f));
            canvas.drawPath(path, linePaint);
        }
    }

    /**
     * 画曲线
     *
     * @param canvas
     */
    public void drawCubicPath(Canvas canvas, Line line) {
        if (line != null && isShow) {
            linePaint.setColor(line.getLineColor());
            linePaint.setStrokeWidth(LeafUtil.dp2px(mContext, line.getLineWidth()));
            linePaint.setStyle(Paint.Style.STROKE);
            Path path = line.getPath();
            float prePreviousPointX = Float.NaN;
            float prePreviousPointY = Float.NaN;
            float previousPointX = Float.NaN;
            float previousPointY = Float.NaN;
            float currentPointX = Float.NaN;
            float currentPointY = Float.NaN;
            float nextPointX = Float.NaN;
            float nextPointY = Float.NaN;
            List<PointValue> values = line.getValues();
            final int lineSize = values.size();
            for (int valueIndex = 0; valueIndex < lineSize; ++valueIndex) {
                if (Float.isNaN(currentPointX)) {
                    PointValue linePoint = values.get(valueIndex);
                    currentPointX = linePoint.getOriginX();
                    currentPointY = linePoint.getOriginY();
                }
                if (Float.isNaN(previousPointX)) {
                    if (valueIndex > 0) {
                        PointValue linePoint = values.get(valueIndex - 1);
                        previousPointX = linePoint.getOriginX();
                        previousPointY = linePoint.getOriginY();
                    } else {
                        previousPointX = currentPointX;
                        previousPointY = currentPointY;
                    }
                }
                if (Float.isNaN(prePreviousPointX)) {
                    if (valueIndex > 1) {
                        PointValue linePoint = values.get(valueIndex - 2);
                        prePreviousPointX = linePoint.getOriginX();
                        prePreviousPointY = linePoint.getOriginY();
                    } else {
                        prePreviousPointX = previousPointX;
                        prePreviousPointY = previousPointY;
                    }
                }
                // nextPoint is always new one or it is equal currentPoint.
                if (valueIndex < lineSize - 1) {
                    PointValue linePoint = values.get(valueIndex + 1);
                    nextPointX = linePoint.getOriginX();
                    nextPointY = linePoint.getOriginY();
                } else {
                    nextPointX = currentPointX;
                    nextPointY = currentPointY;
                }
                if (valueIndex == 0) {
                    // Move to start point.
                    path.moveTo(currentPointX, currentPointY);
                } else {
                    // Calculate control points.
                    final float firstDiffX = (currentPointX - prePreviousPointX);
                    final float firstDiffY = (currentPointY - prePreviousPointY);
                    final float secondDiffX = (nextPointX - previousPointX);
                    final float secondDiffY = (nextPointY - previousPointY);
                    final float firstControlPointX = previousPointX + (LINE_SMOOTHNESS * firstDiffX);
                    final float firstControlPointY = previousPointY + (LINE_SMOOTHNESS * firstDiffY);
                    final float secondControlPointX = currentPointX - (LINE_SMOOTHNESS * secondDiffX);
                    final float secondControlPointY = currentPointY - (LINE_SMOOTHNESS * secondDiffY);
                    if (currentPointY == previousPointY) {
                        path.lineTo(currentPointX, currentPointY);
                    } else {
                        path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY, currentPointX, currentPointY);
                    }
                }
                // Shift values by one back to prevent recalculation of values that have
                // been already calculated.
                prePreviousPointX = previousPointX;
                prePreviousPointY = previousPointY;
                previousPointX = currentPointX;
                previousPointY = currentPointY;
                currentPointX = nextPointX;
                currentPointY = nextPointY;
            }
            measure = new PathMeasure(path, false);
            linePaint.setPathEffect(createPathEffect(measure.getLength(), phase, 0.0f));
            canvas.drawPath(path, linePaint);
        }
    }

    /**
     * 填充
     *
     * @param canvas
     */
    public void drawFillArea(Canvas canvas, Line line, Axis axisX) {
        // 继续使用前面的 path
        if (line != null && line.getValues().size() > 1 && isShow) {
            List<PointValue> values = line.getValues();
            PointValue firstPoint = values.get(0);
            float firstX = firstPoint.getOriginX();
            Path path = line.getPath();
            PointValue lastPoint = values.get(values.size() - 1);
            float lastX = lastPoint.getOriginX();
            path.lineTo(lastX, axisX.getStartY());
            path.lineTo(firstX, axisX.getStartY());
            path.close();
            if (fillShader == null) {
                fillShader = new LinearGradient(0, 0, 0, mHeight, line.getFillColor(), Color.TRANSPARENT, Shader.TileMode.CLAMP);
                fillPaint.setShader(fillShader);
            }
            if (line.getFillColor() == 0)
                fillPaint.setAlpha(100);
            else
                fillPaint.setColor(line.getFillColor());
            canvas.save(Canvas.CLIP_SAVE_FLAG);
            canvas.clipRect(firstX, 0, phase * (lastX - firstX) + firstX, mHeight);
            canvas.drawPath(path, fillPaint);
            canvas.restore();
            path.reset();
        }
    }

    /**
     * 画圆点
     *
     * @param canvas
     */
    public void drawPoints(Canvas canvas, Line line) {
        if (line != null && line.isHasPoints() && isShow) {
            List<PointValue> values = line.getValues();
            float radius = LeafUtil.dp2px(mContext, line.getPointRadius());
            float strokeWidth = LeafUtil.dp2px(mContext, 1);
            PointValue point;
            for (int i = 0, size = values.size(); i < size; i++) {
                point = values.get(i);
                labelPaint.setStyle(Paint.Style.FILL);
                labelPaint.setColor(line.getPointColor());
                canvas.drawCircle(point.getOriginX(), point.getOriginY(), radius, labelPaint);
                labelPaint.setStyle(Paint.Style.STROKE);
                labelPaint.setColor(Color.WHITE);
                labelPaint.setStrokeWidth(strokeWidth);
                canvas.drawCircle(point.getOriginX(), point.getOriginY(), radius, labelPaint);
            }
        }
    }

    /**
     * 带动画的绘制
     *
     * @param duration
     */
    public void showWithAnimation(int duration) {
        isAnimateEnd = false;
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "phase", 0.0f, 1.0f);
        animator.setDuration(duration);
        animator.start();
        isShow = true;
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                phase = (float) animation.getAnimatedValue();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isAnimateEnd = true;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
    }

    // showWithAnimation动画开启后会调用该方法
    public void setPhase(float phase) {
        chartView.invalidate();
    }

    private PathEffect createPathEffect(float pathLength, float phase, float offset) {
        return new DashPathEffect(new float[] { phase * pathLength, pathLength }, 0);
    }

    @Override
    public void drawLabels(Canvas canvas, ChartData chartData, Axis axisY) {
        if (isAnimateEnd)
            super.drawLabels(canvas, chartData, axisY);
    }
}

18 Source : OkView.java
with MIT License
from lihangleo2

/**
 * Created by leo
 * on 2019/11/21.
 */
public clreplaced OkView extends View {

    // 绘制一个小圆圈
    private Paint paint;

    // 绘制打勾paint
    private Paint okPaint;

    // 背景圆圈的半径
    private int myRadius;

    // 绘制打勾的路径
    private Path path = new Path();

    // 绘制路径的长度,也可以理解为完成度
    private PathMeasure pathMeasure;

    // 绘制对勾(√)的动画
    private ValueAnimator animator_draw_ok;

    // 对路径处理实现绘制动画效果
    private PathEffect effect;

    private boolean startDrawOk;

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

    public OkView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OkView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        okPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        okPaint.setStrokeWidth(5);
        okPaint.setStyle(Paint.Style.STROKE);
        okPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, myRadius, paint);
        if (startDrawOk) {
            canvas.drawPath(path, okPaint);
        }
    }

    public void setOkColor(int color) {
        okPaint.setColor(color);
    }

    public void setCircleColor(int color) {
        paint.setColor(color);
    }

    public void setRadius(int radius) {
        myRadius = radius;
        // 对勾的路径
        int cHeight = radius * 2;
        path.moveTo(+cHeight / 8 * 3, cHeight / 2);
        path.lineTo(+cHeight / 2, cHeight / 5 * 3);
        path.lineTo(+cHeight / 3 * 2, cHeight / 5 * 2);
        pathMeasure = new PathMeasure(path, true);
        invalidate();
    }

    public void start(int duration) {
        animator_draw_ok = ValueAnimator.ofFloat(1, 0);
        animator_draw_ok.setDuration(duration);
        animator_draw_ok.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                startDrawOk = true;
                float value = (Float) animation.getAnimatedValue();
                effect = new DashPathEffect(new float[] { pathMeasure.getLength(), pathMeasure.getLength() }, value * pathMeasure.getLength());
                okPaint.setPathEffect(effect);
                invalidate();
            }
        });
        animator_draw_ok.start();
    }
}

18 Source : OvercastType.java
with Apache License 2.0
from li-yu

/**
 * Created by liyu on 2017/8/19.
 */
public clreplaced OvercastType extends BaseWeatherType {

    // 山坡的颜色
    private static final int hillColor = 0xFF59789D;

    private Paint mPaint;

    // 近处的山坡
    private Path mPathFront;

    // 远处的山坡
    private Path mPathRear;

    // 旋转的风扇的扇叶
    private Path fanPath = new Path();

    // 旋转的风扇的柱子
    private Path fanPillarPath = new Path();

    private float fanPillerHeight;

    // 旋转的风扇的角度
    private float curRotate;

    // 当前点的实际位置
    private float[] pos;

    // 当前点的tangent值,用于计算图片所需旋转的角度
    private float[] tan;

    PathMeasure measure;

    private String windSpeed;

    private Shader cloudShader;

    private Path cloudPath;

    private float cloudTransFactor;

    private float hillTransFactor;

    private float cloudOffset = 0;

    public OvercastType(Context context, ShortWeatherInfo info) {
        super(context);
        setColor(0xFF6D8DB1);
        mPathFront = new Path();
        mPathRear = new Path();
        mPaint = new Paint();
        pos = new float[2];
        tan = new float[2];
        measure = new PathMeasure();
        windSpeed = info.getWindSpeed();
        cloudPath = new Path();
        cloudOffset = SizeUtils.dp2px(context, 32);
    }

    @Override
    public void onDrawElements(Canvas canvas) {
        mPaint.reset();
        mPaint.setColor(hillColor);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        clearCanvas(canvas);
        canvas.drawColor(getDynamicColor());
        mPaint.setAlpha(100);
        mPathRear.reset();
        mPathRear.moveTo(getWidth(), getHeight() - getHeight() * 1 / 7 * hillTransFactor);
        mPathRear.rQuadTo(-getWidth() / 2, -20 * hillTransFactor, -getWidth(), getHeight() / 10 * hillTransFactor);
        mPathRear.lineTo(0, getHeight());
        mPathRear.lineTo(getWidth(), getHeight());
        mPathRear.close();
        canvas.drawPath(mPathRear, mPaint);
        mPaint.setAlpha(255);
        mPathFront.reset();
        mPathFront.moveTo(0, getHeight() - getHeight() * 1 / 7 * hillTransFactor);
        mPathFront.rQuadTo(getWidth() / 2, -20 * hillTransFactor, getWidth(), getHeight() / 10 * hillTransFactor);
        mPathFront.lineTo(getWidth(), getHeight());
        mPathFront.lineTo(0, getHeight());
        mPathFront.close();
        drawFan(canvas, mPathFront, 0.618f * hillTransFactor, 1f);
        drawFan(canvas, mPathRear, 0.15f * hillTransFactor, 0.4f);
        mPaint.reset();
        mPaint.setColor(hillColor);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawPath(mPathFront, mPaint);
        mPaint.setShader(cloudShader);
        cloudPath.reset();
        cloudPath.addCircle(getWidth() * cloudTransFactor, getHeight() * 0.618f - cloudOffset, getHeight() / 22, Path.Direction.CW);
        cloudPath.addCircle(getWidth() * cloudTransFactor, getHeight() * 0.618f, getHeight() / 22, Path.Direction.CW);
        cloudPath.addCircle(getWidth() * cloudTransFactor - getHeight() / 19, getHeight() * 0.618f - cloudOffset * 2 / 3, getHeight() / 26, Path.Direction.CW);
        cloudPath.addCircle(getWidth() * cloudTransFactor + getHeight() / 20, getHeight() * 0.618f - cloudOffset * 2 / 3, getHeight() / 30, Path.Direction.CW);
        canvas.drawPath(cloudPath, mPaint);
    }

    private void drawFan(Canvas canvas, Path path, float location, float scale) {
        int saveCount = canvas.save();
        measure.setPath(path, false);
        measure.getPosTan(getWidth() * location, pos, tan);
        canvas.translate(pos[0], pos[1] - fanPillerHeight * scale);
        canvas.scale(scale, scale);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.WHITE);
        canvas.drawPath(fanPillarPath, mPaint);
        canvas.rotate(curRotate * 360f);
        float speed = 0f;
        try {
            speed = Float.valueOf(windSpeed);
        } catch (Exception e) {
            e.printStackTrace();
        }
        speed = Math.max(speed, 0.75f);
        curRotate += 0.0002f * speed;
        if (curRotate > 1f) {
            curRotate = 0f;
        }
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawPath(fanPath, mPaint);
        canvas.rotate(120f);
        canvas.drawPath(fanPath, mPaint);
        canvas.rotate(120f);
        canvas.drawPath(fanPath, mPaint);
        canvas.restoreToCount(saveCount);
    }

    @Override
    public void generateElements() {
        cloudShader = new LinearGradient(getWidth() / 2, 0, getWidth() / 2, getHeight() * 0.618f, 0xFFFFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);
        final float textSize = getHeight() / 32f;
        fanPath.reset();
        // 风扇底部半圆的半径
        final float fanSize = textSize * 0.2f;
        final float fanHeight = textSize * 2f;
        final float fanCenterOffsetY = fanSize * 1.6f;
        fanPath.addArc(new RectF(-fanSize, -fanSize - fanCenterOffsetY, fanSize, fanSize - fanCenterOffsetY), 0, 180);
        fanPath.quadTo(-fanSize * 1f, -fanHeight * 0.5f - fanCenterOffsetY, 0, -fanHeight - fanCenterOffsetY);
        fanPath.quadTo(fanSize * 1f, -fanHeight * 0.5f - fanCenterOffsetY, fanSize, -fanCenterOffsetY);
        fanPath.close();
        fanPillarPath.reset();
        // 柱子的宽度
        final float fanPillarSize = textSize * 0.20f;
        fanPillarPath.moveTo(0, 0);
        // 柱子的高度
        fanPillerHeight = textSize * 4f;
        fanPillarPath.lineTo(2, 0);
        fanPillarPath.lineTo(fanPillarSize, fanPillerHeight);
        fanPillarPath.lineTo(-fanPillarSize, fanPillerHeight);
        fanPillarPath.lineTo(-2, 0);
        fanPillarPath.close();
    }

    @Override
    public void startAnimation(final DynamicWeatherView dynamicWeatherView, int fromColor) {
        super.startAnimation(dynamicWeatherView, fromColor);
        ValueAnimator animator = ValueAnimator.ofFloat(-0.2f, 1.2f);
        animator.setDuration(30000);
        animator.setRepeatCount(-1);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                cloudTransFactor = (float) animation.getAnimatedValue();
            }
        });
        animator.start();
        ValueAnimator animator2 = ValueAnimator.ofFloat(0, 1);
        animator2.setDuration(1000);
        animator2.setRepeatCount(0);
        animator2.setInterpolator(new OvershootInterpolator());
        animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                hillTransFactor = (float) animation.getAnimatedValue();
            }
        });
        animator2.start();
    }

    @Override
    public void endAnimation(DynamicWeatherView dynamicWeatherView, Animator.AnimatorListener listener) {
        super.endAnimation(dynamicWeatherView, listener);
        ValueAnimator animator = ValueAnimator.ofFloat(1, -1);
        animator.setDuration(1000);
        animator.setRepeatCount(0);
        animator.setInterpolator(new AccelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                hillTransFactor = (float) animation.getAnimatedValue();
            }
        });
        if (listener != null) {
            animator.addListener(listener);
        }
        animator.start();
    }
}

18 Source : PlayAndPauseView.java
with MIT License
from Jzvd

/**
 * 仿爱奇艺客户端播放暂停按钮动画
 */
public clreplaced PlayAndPauseView extends View {

    private int animationStep = 1;

    private int animationType = 2;

    // 左边动画时间
    private long leftDuration = 300;

    // 右边动画时间
    private long rightDuration = 2 * leftDuration / 3;

    private Path leftPath;

    private PathMeasure leftPathMeasure;

    private float leftZoomValue;

    // 线的间隔
    private float mLineInterval = Utils.dp2px(15);

    // 线的长度
    private float mLineLength = Utils.dp2px(18);

    private Paint linePaint;

    // 线的粗度
    private float lineThickness = Utils.dp2px(4);

    private Float mLeftCurAnimValue;

    private Path mLeftDstPath;

    private Float mRightCurAnimValue;

    private Path mRightDstPath;

    private AnimatorSet orderAnimatorSet;

    private AnimatorSet reverseAnimatorSet;

    private Path rightPath;

    private PathMeasure rightPathMeasure;

    private float rightZoomValue;

    public PlayAndPauseView(Context paramContext) {
        super(paramContext);
    }

    public PlayAndPauseView(Context paramContext, AttributeSet paramAttributeSet) {
        super(paramContext, paramAttributeSet);
        init();
    }

    private void init() {
        leftPath = new Path();
        rightPath = new Path();
        mLeftDstPath = new Path();
        mRightDstPath = new Path();
        leftPathMeasure = new PathMeasure();
        rightPathMeasure = new PathMeasure();
        linePaint = new Paint(1);
        linePaint.setColor(getResources().getColor(R.color.colorWhite));
        linePaint.setStrokeCap(Paint.Cap.ROUND);
        linePaint.setStrokeWidth(lineThickness);
        mLeftCurAnimValue = 1.0f;
        mRightCurAnimValue = 1.0f;
        initOrderAnimation();
        initReverseAnimation();
    }

    private void initOrderAnimation() {
        // 左边线条缩放隐藏动画
        final ObjectAnimator leftZoomHideAnimation = ObjectAnimator.ofFloat(this, "leftZoomValue", 0f, 1.0f);
        // 左边线条缩放动画
        ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(this, "leftZoomValue", 0f, 0.2f);
        objectAnimator1.setDuration(200);
        objectAnimator1.addListener(new Animator.AnimatorListener() {

            public void onAnimationCancel(Animator param1Animator) {
            }

            public void onAnimationEnd(Animator param1Animator) {
                // 左边线条缩放结束后开始左边线条的缩放隐藏动画
                animationStep = 2;
                leftZoomHideAnimation.start();
            }

            public void onAnimationRepeat(Animator param1Animator) {
            }

            public void onAnimationStart(Animator param1Animator) {
            }
        });
        // 左边线条动画
        ValueAnimator valueAnimator1 = ValueAnimator.ofFloat(0f, 1.0f);
        valueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            public void onAnimationUpdate(ValueAnimator param1ValueAnimator) {
                mLeftCurAnimValue = (Float) param1ValueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator1.setDuration(leftDuration);
        // 右边线条动画
        ValueAnimator valueAnimator2 = ValueAnimator.ofFloat(0f, 1.0f);
        valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            public void onAnimationUpdate(ValueAnimator param1ValueAnimator) {
                mRightCurAnimValue = (Float) param1ValueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator2.setDuration(rightDuration);
        AnimatorSet animatorSet = new AnimatorSet();
        // 左边动画和右边动画一起执行
        animatorSet.playTogether(valueAnimator1, valueAnimator2);
        animatorSet.start();
        orderAnimatorSet = new AnimatorSet();
        // 先执行左边缩放动画,再执行animatorSet
        orderAnimatorSet.playSequentially(objectAnimator1, animatorSet);
    }

    private void initReverseAnimation() {
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "leftZoomValue", 0.2f, 0f);
        objectAnimator.setDuration(200);
        ValueAnimator valueAnimator1 = ValueAnimator.ofFloat(0f, 1.0f);
        valueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            public void onAnimationUpdate(ValueAnimator param1ValueAnimator) {
                mLeftCurAnimValue = (Float) param1ValueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator1.setDuration(leftDuration);
        ValueAnimator valueAnimator2 = ValueAnimator.ofFloat(0f, 1.0f);
        valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            public void onAnimationUpdate(ValueAnimator param1ValueAnimator) {
                mRightCurAnimValue = (Float) param1ValueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator2.setDuration(leftDuration);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(valueAnimator1, valueAnimator2);
        animatorSet.addListener(new Animator.AnimatorListener() {

            public void onAnimationCancel(Animator param1Animator) {
            }

            public void onAnimationEnd(Animator param1Animator) {
                animationStep = 1;
            }

            public void onAnimationRepeat(Animator param1Animator) {
            }

            public void onAnimationStart(Animator param1Animator) {
            }
        });
        animatorSet.start();
        reverseAnimatorSet = new AnimatorSet();
        reverseAnimatorSet.playSequentially(animatorSet, objectAnimator);
    }

    private void pause() {
        animationType = 2;
        reverseAnimatorSet.start();
    }

    private void play() {
        animationType = 1;
        orderAnimatorSet.start();
    }

    public void playOrPause() {
        if (animationType == 1) {
            pause();
        } else if (animationType == 2) {
            play();
        }
    }

    public float getLeftZoomValue() {
        return leftZoomValue;
    }

    public void setLeftZoomValue(float paramFloat) {
        leftZoomValue = paramFloat;
        postInvalidate();
    }

    public float getRightZoomValue() {
        return rightZoomValue;
    }

    public void setRightZoomValue(float paramFloat) {
        rightZoomValue = paramFloat;
        postInvalidate();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    protected void onDraw(Canvas canvas) {
        float leftWidth = (getWidth() - mLineInterval + 2 * lineThickness) / 2;
        float height = getHeight();
        float lineLength = mLineLength;
        float startHeight = (height - lineLength) / 2;
        if (animationStep == 1) {
            // 画左边线条
            canvas.drawLine(leftWidth, startHeight + lineLength * leftZoomValue, leftWidth, startHeight + lineLength, linePaint);
            // 画右边线条
            canvas.drawLine(leftWidth + mLineInterval, startHeight - mLineLength * leftZoomValue, leftWidth + mLineInterval, startHeight + mLineLength * (1 - leftZoomValue), linePaint);
        }
        if (animationStep == 2) {
            // 绘制左边线条动画轨迹
            leftPath.moveTo(leftWidth, startHeight + mLineLength);
            leftPath.lineTo(leftWidth, startHeight + (1 - leftZoomValue) * mLineLength);
            leftPath.lineTo(leftWidth, startHeight);
            leftPath.cubicTo(leftWidth, startHeight, leftWidth + Utils.dp2px(1), startHeight - Utils.dp2px(2), leftWidth + Utils.dp2px(4), startHeight - Utils.dp2px(1));
            leftPath.lineTo(leftWidth + mLineInterval, startHeight + mLineLength / 2 - Utils.dp2px(1));
            leftPath.cubicTo(leftWidth + mLineInterval, startHeight + mLineLength / 2.0F - Utils.dp2px(1), leftWidth + mLineInterval + Utils.dp2px(2), startHeight + mLineLength / 2 - Utils.dp2px(1) + Utils.dp2px(2), leftWidth + mLineInterval, startHeight + mLineLength / 2.0F - Utils.dp2px(1.0F) + Utils.dp2px(4.0F));
            leftPath.lineTo(leftWidth + Utils.dp2px(4), startHeight + mLineLength + Utils.dp2px(1));
            leftPath.cubicTo(leftWidth + Utils.dp2px(4), startHeight + mLineLength + Utils.dp2px(1), leftWidth + Utils.dp2px(1), startHeight + mLineLength + Utils.dp2px(2), leftWidth, startHeight + mLineLength);
            leftPath.lineTo(leftWidth, startHeight + mLineLength / 2);
            leftPathMeasure.setPath(leftPath, false);
            linePaint.setStyle(Paint.Style.STROKE);
            linePaint.setStrokeCap(Paint.Cap.ROUND);
            mLeftDstPath.reset();
            float leftPathMeasureLength = leftPathMeasure.getLength();
            if (animationType == 1) {
                leftPathMeasure.getSegment(mLineLength / 2 * mLeftCurAnimValue, leftPathMeasureLength * mLeftCurAnimValue + mLineLength, mLeftDstPath, true);
            } else {
                leftPathMeasure.getSegment(mLineLength / 2.0F * (1 - mLeftCurAnimValue), leftPathMeasureLength - leftPathMeasureLength * mLeftCurAnimValue + mLineLength, mLeftDstPath, true);
            }
            canvas.drawPath(mLeftDstPath, linePaint);
            // 绘制右边线条动画轨迹
            rightPath.moveTo(leftWidth + mLineInterval, (float) (startHeight - mLineLength * 0.2));
            rightPath.lineTo(leftWidth + mLineInterval, startHeight + mLineLength);
            rightPath.arcTo(leftWidth, startHeight + mLineLength - Utils.dp2px(8), leftWidth + mLineInterval, startHeight + mLineLength + Utils.dp2px(8), 0, 180, false);
            rightPath.lineTo(leftWidth, startHeight);
            rightPathMeasure.setPath(rightPath, false);
            linePaint.setStyle(Paint.Style.STROKE);
            linePaint.setStrokeJoin(Paint.Join.ROUND);
            float rightPathMeasureLength = rightPathMeasure.getLength();
            mRightDstPath.reset();
            if (animationType == 1) {
                rightPathMeasure.getSegment(rightPathMeasureLength * mRightCurAnimValue, rightPathMeasureLength * mRightCurAnimValue + mLineLength, mRightDstPath, true);
            } else {
                rightPathMeasure.getSegment(rightPathMeasureLength - rightPathMeasureLength * mRightCurAnimValue, rightPathMeasureLength - rightPathMeasureLength * mRightCurAnimValue + mLineLength * mRightCurAnimValue, mRightDstPath, true);
            }
            canvas.drawPath(mRightDstPath, linePaint);
        }
    }
}

18 Source : PathMeasurePostTanView.java
with Apache License 2.0
from jiyouliang

/**
 * @author YouLiang.Ji
 * 通过PathMeasure.getPostTan绘制切线
 */
public clreplaced PathMeasurePostTanView extends View {

    private Paint paint;

    private Path mPath;

    private PathMeasure mPathMeasure;

    private float[] mPos;

    private float[] mTan;

    private ValueAnimator mAnimator;

    private float mAnimValue;

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

    public PathMeasurePostTanView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PathMeasurePostTanView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);
        mPos = new float[2];
        mTan = new float[2];
        mPath = new Path();
        mPath.addCircle(0, 0, 100, Path.Direction.CW);
        mPathMeasure = new PathMeasure();
        mPathMeasure.setPath(mPath, true);
        mAnimator = ValueAnimator.ofFloat(0, 1);
        mAnimator.setDuration(1000);
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        mAnimator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 锁定画布
        canvas.save();
        // 移动画布中心
        canvas.translate(200, 200);
        canvas.drawPath(mPath, paint);
        // getPosTan方法获取摸个点上的坐标点和切线坐标,参数1:输入参数;参数2:输出参数,某点对应的坐标;参数3:输出参数,切线坐标
        mPathMeasure.getPosTan(mAnimValue * mPathMeasure.getLength(), mPos, mTan);
        canvas.drawCircle(mPos[0], mPos[1], 10, paint);
        // 计算切线角度
        float degree = (float) (Math.atan2(mTan[1], mTan[0]) * 180 / Math.PI);
        // 旋转画布,不旋转切线永远固定不动
        canvas.rotate(degree);
        // 绘制切线
        canvas.drawLine(0, -100, 100, -100, paint);
        // canvas.drawLine(0, -100, -100, -100, paint);
        // 释放画布
        canvas.restore();
    }
}

18 Source : BezierLoadingView.java
with Apache License 2.0
from jiyouliang

/**
 * @author YouLiang.Ji
 * 贝塞尔曲线实现loading效果
 */
public clreplaced BezierLoadingView extends View {

    private Path mPath;

    // 输出路径
    private Path mDst;

    private Paint paint;

    // 路径测量
    private PathMeasure mPathMeasure;

    // 路径长度
    private float mLength;

    private ValueAnimator animator;

    private float mAnimValue;

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

    public BezierLoadingView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BezierLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(6);
        mPath = new Path();
        mDst = new Path();
        // 添加圆形路径
        mPath.addCircle(400, 400, 100, Path.Direction.CW);
        mPathMeasure = new PathMeasure();
        mPathMeasure.setPath(mPath, true);
        // 获取路径长度
        mLength = mPathMeasure.getLength();
        animator = ValueAnimator.ofFloat(0, 1);
        animator.setDuration(800);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mDst.reset();
        mDst.lineTo(0, 0);
        // float start = 0;
        // 0 ~ 整个圆长度变化,以此形成动画效果
        float stop = mLength * mAnimValue;
        // 当路径绘制到半个圆时,终点快速向起点移动
        float start = (float) (stop - ((0.5 - Math.abs(mAnimValue - 0.5)) * mLength));
        // 获取路径片段:true代表从起点开始截取,将截取片段输出到mDst路径中
        mPathMeasure.getSegment(start, stop, mDst, true);
        canvas.drawPath(mDst, paint);
    }
}

18 Source : SearchView.java
with Apache License 2.0
from jeanboydev

/**
 * Created by jeanboy on 2016/11/9.
 */
public clreplaced SearchView extends View {

    private int mWidth, mHeight;

    private int glreplacedRadius = 50;

    private int searchRadius = glreplacedRadius * 2;

    private float[] glreplacedBarEnd = new float[2];

    private Path mGlreplacedPath = new Path();

    private Path mSearchPath = new Path();

    private PathMeasure pathMeasure = new PathMeasure();

    private Paint mPaint = new Paint();

    // 图标颜色
    public final static int COLOR_PAINT = Color.WHITE;

    // 背景颜色
    public final static int COLOR_BG = 0xFF0082D7;

    private int nowState = STATE_NONE;

    // 定义状态
    public final static int STATE_NONE = 0x000;

    public final static int STATE_LOAD_START = 0x001;

    public final static int STATE_LOADING = 0x002;

    public final static int STATE_LOAD_END = 0x003;

    // 默认动画时长
    private final static int ANIM_DURATION = 1500;

    private float currentValue = 0f;

    private ValueAnimator.AnimatorUpdateListener updateListener;

    private ValueAnimator startValueAnimator;

    private ValueAnimator loadingValueAnimator;

    private ValueAnimator endValueAnimator;

    private boolean isSearching = false;

    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);
        initView();
    }

    private void initView() {
        mPaint.setColor(COLOR_PAINT);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(15f);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        initPath();
        initListener();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.translate(mWidth / 2, mHeight / 2);
        canvas.drawColor(COLOR_BG);
        switch(nowState) {
            case STATE_NONE:
                canvas.drawPath(mGlreplacedPath, mPaint);
                break;
            case STATE_LOAD_START:
                pathMeasure.setPath(mGlreplacedPath, false);
                Path glreplacedStartDst = new Path();
                // 获取绘制路径区间,从0-1
                pathMeasure.getSegment(pathMeasure.getLength() * currentValue, pathMeasure.getLength(), glreplacedStartDst, true);
                canvas.drawPath(glreplacedStartDst, mPaint);
                break;
            case STATE_LOADING:
                pathMeasure.setPath(mSearchPath, false);
                Path searchDst = new Path();
                float stop = pathMeasure.getLength() * currentValue;
                float start = (float) (stop - ((0.5 - Math.abs(currentValue - 0.5)) * searchRadius * 2));
                pathMeasure.getSegment(start, stop, searchDst, true);
                canvas.drawPath(searchDst, mPaint);
                break;
            case STATE_LOAD_END:
                pathMeasure.setPath(mGlreplacedPath, false);
                Path glreplacedEndDst = new Path();
                // 获取绘制路径区间,从1-0
                pathMeasure.getSegment(pathMeasure.getLength() * currentValue, pathMeasure.getLength(), glreplacedEndDst, true);
                canvas.drawPath(glreplacedEndDst, mPaint);
                break;
        }
    }

    private void initPath() {
        // 放大镜路径
        RectF glreplacedRectF = new RectF(-glreplacedRadius, -glreplacedRadius, glreplacedRadius, glreplacedRadius);
        // 不要设置360°内部会自动优化,测量不能取到需要的数值
        mGlreplacedPath.addArc(glreplacedRectF, 45, 359.9f);
        // 搜索路径
        RectF searchRectF = new RectF(-searchRadius, -searchRadius, searchRadius, searchRadius);
        mSearchPath.addArc(searchRectF, 45, 359.9f);
        // 获取放大镜把的结束坐标,也就是搜索路径的结束坐标
        PathMeasure searchMeasure = new PathMeasure(mSearchPath, false);
        searchMeasure.getPosTan(0, glreplacedBarEnd, null);
        mGlreplacedPath.lineTo(glreplacedBarEnd[0], glreplacedBarEnd[1]);
    }

    private void initListener() {
        updateListener = new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentValue = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        };
        startValueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIM_DURATION);
        loadingValueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIM_DURATION);
        loadingValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        loadingValueAnimator.setRepeatMode(ValueAnimator.RESTART);
        endValueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(ANIM_DURATION);
        startValueAnimator.addUpdateListener(updateListener);
        loadingValueAnimator.addUpdateListener(updateListener);
        endValueAnimator.addUpdateListener(updateListener);
        startValueAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                nowState = STATE_LOADING;
                loadingValueAnimator.start();
            }
        });
        endValueAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isSearching = false;
                nowState = STATE_NONE;
            }
        });
    }

    public boolean isSearching() {
        return isSearching;
    }

    public void startSearch() {
        if (isSearching)
            return;
        isSearching = true;
        nowState = STATE_LOAD_START;
        startValueAnimator.start();
    }

    public void stopSearch() {
        if (startValueAnimator.isRunning()) {
            startValueAnimator.end();
        }
        if (loadingValueAnimator.isRunning()) {
            loadingValueAnimator.end();
        }
        nowState = STATE_LOAD_END;
        endValueAnimator.start();
    }
}

18 Source : PathTracingView.java
with Apache License 2.0
from InnoFang

/**
 * Author: Inno Fang
 * Time: 2017/1/12 21:40
 * Description:
 */
public clreplaced PathTracingView extends View {

    private static final String TAG = "PathTracingView";

    private Path mDst;

    private Path mPath;

    private Paint mPaint;

    private float mLength;

    private float mAnimValue;

    private PathMeasure mPathMeasure;

    public PathTracingView(Context context) {
        super(context);
    }

    public PathTracingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPath = new Path();
        mDst = new Path();
        mPath.addCircle(400, 400, 100, Path.Direction.CW);
        mPathMeasure = new PathMeasure();
        mPathMeasure.setPath(mPath, true);
        mLength = mPathMeasure.getLength();
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setDuration(1000);
        animator.setInterpolator(new LinearInterpolator());
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }

    public PathTracingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mDst.reset();
        // 若去掉,geSegment方法可能会失效
        mDst.lineTo(0, 0);
        float stop = mLength * mAnimValue;
        float start = (float) (stop - ((0.5 - Math.abs(mAnimValue - 0.5)) * mLength));
        mPathMeasure.getSegment(start, stop, mDst, true);
        canvas.drawPath(mDst, mPaint);
    }
}

18 Source : PathPosTanView.java
with Apache License 2.0
from InnoFang

/**
 * Author: Inno Fang
 * Time: 2017/1/13 10:21
 * Description:
 */
public clreplaced PathPosTanView extends View implements View.OnClickListener {

    private static final String TAG = "PathPosTanView";

    private Path mPath;

    // 用于存放取出来的点的坐标
    private float[] mPos;

    // 用于取出当前点的运动趋势的坐标
    private float[] mTan;

    private Paint mPaint;

    private PathMeasure mPathMeasure;

    private ValueAnimator mValueAnimator;

    private float mCurrentValue;

    public PathPosTanView(Context context) {
        super(context);
    }

    public PathPosTanView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPath = new Path();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPath.addCircle(0, 0, 200, Path.Direction.CW);
        mPathMeasure = new PathMeasure();
        mPathMeasure.setPath(mPath, false);
        mPos = new float[2];
        mTan = new float[2];
        setOnClickListener(this);
        mValueAnimator = ValueAnimator.ofFloat(0, 1);
        mValueAnimator.setDuration(3000);
        mValueAnimator.setInterpolator(new LinearInterpolator());
        mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
    }

    public PathPosTanView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPathMeasure.getPosTan(mCurrentValue * mPathMeasure.getLength(), mPos, mTan);
        float degree = (float) (Math.atan2(mTan[1], mTan[0]) * 180 / Math.PI);
        canvas.save();
        // 将画布平移到400,400的位置
        canvas.translate(400, 400);
        canvas.drawPath(mPath, mPaint);
        canvas.drawCircle(mPos[0], mPos[1], 10, mPaint);
        canvas.rotate(degree);
        canvas.drawLine(0, -200, 300, -200, mPaint);
        canvas.restore();
    }

    @Override
    public void onClick(View v) {
        mValueAnimator.start();
    }
}

18 Source : PathPaintView.java
with Apache License 2.0
from InnoFang

/**
 * Author: Inno Fang
 * Time: 2017/1/12 21:40
 * Description:
 */
public clreplaced PathPaintView extends View {

    private static final String TAG = "PathTracingView";

    private Path mPath;

    private Paint mPaint;

    private float mLength;

    private float mAnimValue;

    private PathMeasure mPathMeasure;

    private PathEffect mPathEffect;

    public PathPaintView(Context context) {
        super(context);
    }

    public PathPaintView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPath = new Path();
        mPath.moveTo(100, 100);
        mPath.lineTo(100, 500);
        mPath.lineTo(400, 300);
        mPath.close();
        mPathMeasure = new PathMeasure();
        mPathMeasure.setPath(mPath, true);
        mLength = mPathMeasure.getLength();
        ValueAnimator animator = ValueAnimator.ofFloat(1, 0);
        animator.setDuration(2000);
        animator.setInterpolator(new LinearInterpolator());
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimValue = (float) animation.getAnimatedValue();
                mPathEffect = new DashPathEffect(new float[] { mLength, mLength }, mLength * mAnimValue);
                mPaint.setPathEffect(mPathEffect);
                invalidate();
            }
        });
        animator.start();
    }

    public PathPaintView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(mPath, mPaint);
    }
}

18 Source : EasyPathView.java
with Apache License 2.0
from huzenan

/**
 * 带动画的路径视图,将通过设置的路径pathString,自动按顺序生成路径动画,包括绘制动画与清除动画(反方向);
 * 其中fixedWidth和fixedHeight属性必选,用于设置相对于所提供的pathString的固定宽高,以保证在任何实际测量出的宽高中,路径位置正确;
 * strokeFixedWidth属性可选,用于设置相对于所提供的pathString的固定线段宽度,以保证在任何实际测量的宽高中,绘制路径的线段宽度显示比例一致;
 * Created by huzn on 2016/12/21.
 */
public clreplaced EasyPathView extends View {

    // 相对于所提供路径的固定的宽,默认为0
    private int fixedWidth;

    // 相对于所提供路径的固定的高,默认为0
    private int fixedHeight;

    // 需要解析的路径
    private String pathString;

    // 线段颜色,默认Color.BLACK
    private int strokeColor;

    // 线段宽度,默认1dp
    private int strokeWidth;

    // 相对于所提供路径的固定的线段宽度,提供该参数时,无视strokeWidth参数,默认为-1
    private float strokeFixedWidth;

    // 线段是否平滑,默认false
    private boolean strokeIsRound;

    // 动画时间长度集,单位ms,默认为全部500
    private String animDurations;

    // 动画播放模式,为ANIM_MODE_TOGETHER或ANIM_MODE_SEPARATE,默认为ANIM_MODE_TOGETHER
    private int animMode;

    // 状态,为STATE_SHOW或STATE_HIDE,默认为STATE_SHOW
    private int state;

    // 是否为动态的,设置为动态时,将只显示绘制过的路径,不显示路径本身,默认为false
    private boolean dynamic;

    // 最大线段数,默认为10
    private int maxPathCount;

    public static final int STATE_NONE = -1;

    public static final int STATE_SHOW = 0;

    public static final int STATE_HIDE = 1;

    public static final int STATE_ANIM_SHOW = 2;

    public static final int STATE_ANIM_HIDE = 3;

    public static final int ANIM_MODE_TOGETHER = 0;

    public static final int ANIM_MODE_SEPARATE = 1;

    private int pathCount;

    private static final int MAX_PATH_COUNT = 10;

    private Path pathDst;

    private Path[] pathDstList;

    private PathMeasure pm;

    private PathMeasure[] pmList;

    private Paint paint;

    private float factor;

    // 用于在EditMode下Preview
    private Path path;

    // 当animMode为ANIM_MODE_SEPARATE时,记录当前播放的动画
    private int curAnimIndex;

    // 动画时间长度集
    private long[] animDurationArr;

    // 动画时间占比集
    private float[] animDurationRatioArr;

    // 动画默认播放时长
    private static final long ANIM_DURATION_DEFAULT = 500;

    // 动画是否循环
    private boolean isAnimRepeat;

    private ValueAnimator.AnimatorUpdateListener updateListener;

    private float animatorValue;

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

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

    public EasyPathView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EasyPathView, defStyleAttr, 0);
        fixedWidth = a.getInteger(R.styleable.EasyPathView_epvFixedWidth, 0);
        fixedHeight = a.getInteger(R.styleable.EasyPathView_epvFixedHeight, 0);
        pathString = a.getString(R.styleable.EasyPathView_epvPathString);
        strokeColor = a.getColor(R.styleable.EasyPathView_epvStrokeColor, Color.BLACK);
        strokeWidth = a.getDimensionPixelOffset(R.styleable.EasyPathView_epvStrokeWidth, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()));
        strokeFixedWidth = a.getFloat(R.styleable.EasyPathView_epvStrokeFixedWidth, -1.0f);
        strokeIsRound = a.getBoolean(R.styleable.EasyPathView_epvStrokeIsRound, false);
        animDurations = a.getString(R.styleable.EasyPathView_epvAnimDurations);
        animMode = a.getInteger(R.styleable.EasyPathView_epvAnimMode, ANIM_MODE_TOGETHER);
        state = a.getInteger(R.styleable.EasyPathView_epvState, STATE_SHOW);
        dynamic = a.getBoolean(R.styleable.EasyPathView_epvDynamic, false);
        maxPathCount = a.getInteger(R.styleable.EasyPathView_epvMaxPathCount, MAX_PATH_COUNT);
        a.recycle();
        initPath();
        initPaint();
        initListener();
        initDuration();
    }

    private void initPath() {
        pathCount = 0;
        pathDst = new Path();
        pathDstList = new Path[maxPathCount];
        for (int i = 0; i < maxPathCount; i++) pathDstList[i] = new Path();
        pm = new PathMeasure();
        pmList = new PathMeasure[maxPathCount];
        for (int i = 0; i < maxPathCount; i++) pmList[i] = new PathMeasure();
    }

    private void initPaint() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(strokeWidth);
        paint.setColor(strokeColor);
        paint.setStyle(Paint.Style.STROKE);
        if (strokeIsRound) {
            paint.setStrokeCap(Paint.Cap.ROUND);
            paint.setStrokeJoin(Paint.Join.ROUND);
        }
    }

    private void initListener() {
        updateListener = new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animatorValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        };
    }

    private void initDuration() {
        if (TextUtils.isEmpty(animDurations))
            animDurations = String.valueOf(ANIM_DURATION_DEFAULT);
        // 获取动画时间长度集
        String[] splitDur = animDurations.split(",");
        animDurationArr = new long[maxPathCount];
        int splitLen = Math.min(splitDur.length, maxPathCount);
        for (int i = 0; i < splitLen; i++) animDurationArr[i] = Long.parseLong(splitDur[i]);
        for (int i = splitLen; i < maxPathCount; i++) animDurationArr[i] = ANIM_DURATION_DEFAULT;
        animDurationRatioArr = new float[maxPathCount];
        isAnimRepeat = false;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        rebuildPathData();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        width = width + getPaddingLeft() + getPaddingRight();
        if (mode != MeasureSpec.EXACTLY) {
            // wrap_content
            width = fixedWidth;
        }
        int height = MeasureSpec.getSize(heightMeasureSpec);
        mode = MeasureSpec.getMode(heightMeasureSpec);
        height = height + getPaddingTop() + getPaddingBottom();
        if (mode != MeasureSpec.EXACTLY) {
            // wrap_content
            height = fixedHeight;
        }
        // 得到实际测量大小与指定大小的比例factor,当宽、高计算出的比例不同时,使用较小值
        float f1 = 1.0f * width / fixedWidth;
        float f2 = 1.0f * height / fixedHeight;
        factor = Math.min(f1, f2);
        if (strokeFixedWidth != -1)
            paint.setStrokeWidth(strokeFixedWidth * factor);
        rebuildPathData();
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (state == STATE_NONE)
            return;
        // 支持预览
        if (isInEditMode()) {
            canvas.drawPath(path, paint);
            return;
        }
        float startRatio = 0.27f * animatorValue * animatorValue + 0.73f;
        if (animMode == ANIM_MODE_TOGETHER) {
            float startD = 0.0f;
            for (int i = 0; i < pathCount; i++) {
                if (dynamic)
                    startD = pmList[i].getLength() * animatorValue * startRatio;
                pathDst.reset();
                // 兼容KITKAT的硬件加速,详见PathMeasure.getSegment()的注释
                pathDst.rLineTo(0, 0);
                pmList[i].getSegment(startD, pmList[i].getLength() * animatorValue, pathDst, true);
                canvas.drawPath(pathDst, paint);
            }
        } else if (animMode == ANIM_MODE_SEPARATE) {
            if (!dynamic) {
                if (state == STATE_SHOW || state == STATE_ANIM_SHOW) {
                    // 绘制已经完成动画的路径
                    for (int i = 0; i < curAnimIndex; i++) canvas.drawPath(pathDstList[i], paint);
                } else if (state == STATE_HIDE || state == STATE_ANIM_HIDE) {
                    // 绘制未播放动画的路径
                    for (int i = curAnimIndex - 1; i >= 0; i--) canvas.drawPath(pathDstList[i], paint);
                }
            }
            // 绘制正在播放动画的路径
            if (0 <= curAnimIndex && curAnimIndex < pathCount) {
                float startD = 0.0f;
                if (dynamic)
                    startD = pmList[curAnimIndex].getLength() * animatorValue * startRatio;
                pathDst.reset();
                // 兼容KITKAT的硬件加速,详见PathMeasure.getSegment()的注释
                pathDst.rLineTo(0, 0);
                pmList[curAnimIndex].getSegment(startD, pmList[curAnimIndex].getLength() * animatorValue, pathDst, true);
                canvas.drawPath(pathDst, paint);
            }
        }
    }

    private void rebuildPathData() {
        try {
            pathCount = 0;
            path = EasyPathParser.getInstance().parsePath(pathString, this, factor);
            pm.setPath(path, false);
            while (pm.nextContour()) {
                ++pathCount;
                if (pathCount > maxPathCount) {
                    pathCount = maxPathCount;
                    break;
                }
                // 存储所有轮廓
                pathDstList[pathCount - 1].reset();
                // 兼容KITKAT
                pathDstList[pathCount - 1].rLineTo(0, 0);
                pm.getSegment(0, pm.getLength(), pathDstList[pathCount - 1], true);
                pmList[pathCount - 1].setPath(pathDstList[pathCount - 1], false);
            }
            if (state == STATE_SHOW)
                animatorValue = 1.0f;
            if (animMode == ANIM_MODE_SEPARATE)
                curAnimIndex = pathCount;
            // 计算动画时间占比集
            long totalDuration = 0L;
            for (int i = 0; i < pathCount; i++) totalDuration += animDurationArr[i];
            for (int i = 0; i < pathCount; i++) animDurationRatioArr[i] = 1.0f * animDurationArr[i] / totalDuration;
        } catch (ParseException e) {
            e.printStackTrace();
            pathCount = 0;
            pm = null;
            pathDstList = null;
            pmList = null;
            state = STATE_NONE;
            animatorValue = 0.0f;
            curAnimIndex = 0;
        }
    }

    private ValueAnimator getDrawAnimator() {
        ValueAnimator drawAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        drawAnimator.addUpdateListener(updateListener);
        drawAnimator.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
                if (null != onAnimatorListener)
                    onAnimatorListener.onAnimStart(state);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (animMode == ANIM_MODE_SEPARATE) {
                    if (curAnimIndex < pathCount) {
                        ++curAnimIndex;
                        getDrawAnimator().setDuration(animDurationArr[curAnimIndex]).start();
                    } else {
                        if (isAnimRepeat) {
                            if (null != onAnimatorListener)
                                onAnimatorListener.onAnimRepeat(state);
                            startDraw();
                        } else {
                            state = STATE_SHOW;
                        }
                    }
                } else if (animMode == ANIM_MODE_TOGETHER) {
                    if (isAnimRepeat) {
                        if (null != onAnimatorListener)
                            onAnimatorListener.onAnimRepeat(state);
                        startDraw();
                    } else {
                        state = STATE_SHOW;
                    }
                }
                if (null != onAnimatorListener)
                    onAnimatorListener.onAnimEnd(state);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        return drawAnimator;
    }

    private ValueAnimator getEraseAnimator() {
        ValueAnimator eraseAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
        eraseAnimator.addUpdateListener(updateListener);
        eraseAnimator.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
                if (null != onAnimatorListener)
                    onAnimatorListener.onAnimStart(state);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (animMode == ANIM_MODE_SEPARATE) {
                    if (curAnimIndex > 0) {
                        --curAnimIndex;
                        getEraseAnimator().setDuration(animDurationArr[curAnimIndex]).start();
                    } else {
                        if (isAnimRepeat) {
                            if (null != onAnimatorListener)
                                onAnimatorListener.onAnimRepeat(state);
                            startErase();
                        } else {
                            state = STATE_HIDE;
                        }
                    }
                } else if (animMode == ANIM_MODE_TOGETHER) {
                    if (isAnimRepeat) {
                        if (null != onAnimatorListener)
                            onAnimatorListener.onAnimRepeat(state);
                        startErase();
                    } else {
                        state = STATE_HIDE;
                    }
                }
                if (null != onAnimatorListener)
                    onAnimatorListener.onAnimEnd(state);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        return eraseAnimator;
    }

    /**
     * 开始绘制路径
     */
    public void startDraw() {
        state = STATE_ANIM_SHOW;
        if (animMode == ANIM_MODE_SEPARATE)
            curAnimIndex = 0;
        getDrawAnimator().setDuration(animDurationArr[curAnimIndex]).start();
    }

    /**
     * 开始绘制路径
     *
     * @param repeat 动画是否循环绘制
     */
    public void startDraw(boolean repeat) {
        isAnimRepeat = repeat;
        startDraw();
    }

    /**
     * 开始清除路径
     */
    public void startErase() {
        state = STATE_ANIM_HIDE;
        if (animMode == ANIM_MODE_SEPARATE)
            curAnimIndex = pathCount - 1;
        getEraseAnimator().setDuration(animDurationArr[curAnimIndex]).start();
    }

    public void startErase(boolean repeat) {
        isAnimRepeat = true;
        startErase();
    }

    /**
     * 停止循环绘制
     */
    public void stopRepeat() {
        isAnimRepeat = false;
    }

    /**
     * 设置路径动画当前的进度
     *
     * @param progress 路径动画当前的进度,范围0.0f~1.0f
     */
    public void setAnimProgress(float progress) {
        if (progress < 0.0f)
            progress = 0.0f;
        else if (progress > 1.0f)
            progress = 1.0f;
        if (animMode == ANIM_MODE_TOGETHER) {
            animatorValue = progress;
            invalidate();
        } else if (animMode == ANIM_MODE_SEPARATE) {
            float curTotal = 0.0f;
            for (int i = 0; i < pathCount; i++) {
                curTotal += animDurationRatioArr[i];
                if (progress <= curTotal) {
                    curAnimIndex = i;
                    animatorValue = 1.0f - (curTotal - progress) / animDurationRatioArr[i];
                    invalidate();
                    break;
                }
            }
        }
    }

    /**
     * 重置,在修改参数后,startDraw之前需要调用此方法
     */
    public void reset() {
        initPath();
        initPaint();
        initListener();
        initDuration();
        requestLayout();
        invalidate();
    }

    // getters and setters
    public int getFixedWidth() {
        return fixedWidth;
    }

    public void setFixedWidth(int fixedWidth) {
        this.fixedWidth = fixedWidth;
    }

    public int getFixedHeight() {
        return fixedHeight;
    }

    public void setFixedHeight(int fixedHeight) {
        this.fixedHeight = fixedHeight;
    }

    public String getPathString() {
        return pathString;
    }

    public void setPathString(String pathString) {
        this.pathString = pathString;
    }

    public int getStrokeColor() {
        return strokeColor;
    }

    public void setStrokeColor(int strokeColor) {
        this.strokeColor = strokeColor;
    }

    public int getStrokeWidth() {
        return strokeWidth;
    }

    public void setStrokeWidth(int strokeWidth) {
        this.strokeWidth = strokeWidth;
    }

    public float getStrokeFixedWidth() {
        return strokeFixedWidth;
    }

    public void setStrokeFixedWidth(float strokeFixedWidth) {
        this.strokeFixedWidth = strokeFixedWidth;
    }

    public boolean getStrokeIsRound() {
        return strokeIsRound;
    }

    public void setStrokeIsRound(boolean strokeIsRound) {
        this.strokeIsRound = strokeIsRound;
    }

    public String getAnimDurations() {
        return animDurations;
    }

    public void setAnimDurations(String animDurations) {
        this.animDurations = animDurations;
    }

    public int getAnimMode() {
        return animMode;
    }

    public void setAnimMode(int animMode) {
        this.animMode = animMode;
    }

    public boolean isDynamic() {
        return dynamic;
    }

    public void setDynamic(boolean dynamic) {
        this.dynamic = dynamic;
    }

    /**
     * 获取当前状态
     *
     * @return 返回当前状态,包括STATE_NONE,STATE_SHOW,STATE_HIDE,STATE_ANIM_SHOW和STATE_ANIM_HIDE
     */
    public int getState() {
        return state;
    }

    /**
     * 用于监听路径动画,可选择监听哪些动画状态
     */
    public static abstract clreplaced OnAnimatorListener {

        /**
         * 动画开始时回调
         *
         * @param state 当前动画状态,包括STATE_SHOW,STATE_HIDE,STATE_ANIM_SHOW和STATE_ANIM_HIDE
         */
        protected void onAnimStart(int state) {
        }

        /**
         * 动画结束时回调
         *
         * @param state 当前动画状态,包括STATE_SHOW,STATE_HIDE,STATE_ANIM_SHOW和STATE_ANIM_HIDE
         */
        protected void onAnimEnd(int state) {
        }

        /**
         * 动画重复时回调,紧接着会回调onAnimStart
         *
         * @param state 当前动画状态,包括STATE_SHOW,STATE_HIDE,STATE_ANIM_SHOW和STATE_ANIM_HIDE
         */
        protected void onAnimRepeat(int state) {
        }
    }

    private OnAnimatorListener onAnimatorListener;

    /**
     * 添加监听路径动画执行的接口
     *
     * @param onAnimatorListener 用于监听路径动画
     */
    public void addOnAnimatorListener(OnAnimatorListener onAnimatorListener) {
        this.onAnimatorListener = onAnimatorListener;
    }
}

18 Source : FloatParticle.java
with Apache License 2.0
from HpWens

/**
 * Created by wenshi on 2018/7/4.
 * Description 浮点粒子
 */
public clreplaced FloatParticle {

    // 三阶贝塞尔曲线
    private Point startPoint;

    private Point endPoint;

    private Point controlPoint1;

    private Point controlPoint2;

    private Paint mPaint;

    private Path mPath;

    private Random mRandom;

    // 圆半径
    private float mRadius = 5;

    // 控件宽度
    private int mWidth;

    // 控件高度
    private int mHeight;

    private float mCurDistance = 0;

    private static final int DISTANCE = 255;

    private static final float MOVE_PER_FRAME = 1f;

    // 火花外侧阴影大小
    private static final float BLUR_SIZE = 5.0F;

    // 路径测量
    private PathMeasure mPathMeasure;

    private float mMeasureLength;

    public FloatParticle(int width, int height) {
        mWidth = width;
        mHeight = height;
        mRandom = new Random();
        startPoint = new Point((int) (mRandom.nextFloat() * mWidth), (int) (mRandom.nextFloat() * mHeight));
        // 抗锯齿
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.WHITE);
        // 防抖动
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.FILL);
        // 设置模糊效果 边缘模糊
        mPaint.setMaskFilter(new BlurMaskFilter(BLUR_SIZE, BlurMaskFilter.Blur.SOLID));
        mPath = new Path();
        mPathMeasure = new PathMeasure();
        startPoint.x = (int) (mRandom.nextFloat() * mWidth);
        startPoint.y = (int) (mRandom.nextFloat() * mHeight);
    }

    public void drawParticle(Canvas canvas) {
        // 初始化三阶贝塞尔曲线数据
        if (mCurDistance == 0) {
            endPoint = getRandomPointRange(startPoint.x, startPoint.y, DISTANCE);
            controlPoint1 = getRandomPointRange(startPoint.x, startPoint.y, mRandom.nextInt(Math.min(mWidth, mHeight) / 2));
            controlPoint2 = getRandomPointRange(endPoint.x, endPoint.y, mRandom.nextInt(Math.min(mWidth, mHeight) / 2));
            // 添加贝塞尔曲线路径
            mPath.reset();
            mPath.moveTo(startPoint.x, startPoint.y);
            mPath.cubicTo(controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y);
            mPathMeasure.setPath(mPath, false);
            mMeasureLength = mPathMeasure.getLength();
        }
        // 计算当前坐标点
        float[] loc = new float[2];
        mPathMeasure.getPosTan(mCurDistance / DISTANCE * mMeasureLength, loc, null);
        startPoint.x = (int) loc[0];
        startPoint.y = (int) loc[1];
        // 递增1
        mCurDistance += MOVE_PER_FRAME;
        if (mCurDistance >= DISTANCE) {
            mCurDistance = 0;
        }
        canvas.drawCircle(startPoint.x, startPoint.y, mRadius, mPaint);
    }

    /**
     * @param baseX 基准坐标x
     * @param baseY 基准坐标y
     * @param range 指定范围长度
     * @return 根据基准点获取指定范围的随机点
     */
    private Point getRandomPointRange(int baseX, int baseY, int range) {
        int randomX = 0;
        int randomY = 0;
        // range指定长度为255,可以根据实际效果调整
        if (range <= 0) {
            range = 1;
        }
        // 我们知道一点(baseX,baseY)求与它距离长度为range的另一点
        // 两点x方向的距离(随机产生)
        int distanceX = mRandom.nextInt(range);
        // 知道x方向的距离与斜边的距离求y方向的距离
        int distanceY = (int) Math.sqrt(range * range - distanceX * distanceX);
        randomX = baseX + getRandomPNValue(distanceX);
        randomY = baseY + getRandomPNValue(distanceY);
        if (randomX > mWidth) {
            randomX = mWidth - range;
        } else if (randomX < 0) {
            randomX = range;
        } else if (randomY > mHeight) {
            randomY = mHeight - range;
        } else if (randomY < 0) {
            randomY = range;
        }
        return new Point(randomX, randomY);
    }

    /**
     * 获取随机的正负值
     *
     * @return
     */
    private int getRandomPNValue(int value) {
        return mRandom.nextBoolean() ? value : 0 - value;
    }

    /**
     * 设置圆半径
     *
     * @param radius
     */
    public void setRadius(float radius) {
        mRadius = radius;
    }
}

See More Examples