美文网首页安卓开发安卓资源收集Android Demo
Android 自定义View之咖啡杯动画

Android 自定义View之咖啡杯动画

作者: Samlss | 来源:发表于2018-10-10 15:44 被阅读58次

    效果

    CoffeeView CoffeeView

    大概思路

    • 自定义view,直接继承view
    • 复写onSizeChanged()方法,在此计算杯垫,杯子,烟雾效果的path
    • 在onDraw()方法中,描绘杯垫,杯子
    • 处理烟雾动画效果

    画杯子

    这里需要画两部分内容,第一部分是杯子,第二部分是杯耳(提手的地方)

    我们可以使用addRoundRect来描绘圆角矩形,并且可指定每个圆角的半径即圆角的程度

    /**
     * Add a closed round-rectangle contour to the path. Each corner receives
     * two radius values [X, Y]. The corners are ordered top-left, top-right,
     * bottom-right, bottom-left
     *
     * @param rect The bounds of a round-rectangle to add to the path
     * @param radii Array of 8 values, 4 pairs of [X,Y] radii
     * @param dir  The direction to wind the round-rectangle's contour
     */
    public void addRoundRect(RectF rect, float[] radii, Direction dir) {
        if (rect == null) {
            throw new NullPointerException("need rect parameter");
        }
        addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir);
    }
    

    以下代码注释:

    //计算view的中心点坐标
    mCenterX = w / 2;
    mCenterY = h / 2;
    
    //杯子的宽,为view的宽度的2/3
    float cupWidth  = w * 2 / 3f;
    
    //杯子的高,为view的高度3/8
    float cupHeight = (h / 2) * 3 / 4f;
    
    //计算出杯子的中心点坐标
    float cupCenterX = mCenterX;
    float cupCenterY = mCenterY + cupHeight / 2;
    
    //计算杯子矩形的左上右上的圆角半径
    float cupTopRoundRadius = Math.min(cupWidth, cupHeight) / 20f;
    
    //计算杯子矩形的左下右下的圆角半径
    float cupBottomRoundRadius = cupTopRoundRadius * 10;
    
    //重置杯子path
    mCupPath.reset();
    
    //添加杯子(杯身)轨迹
    mCupPath.addRoundRect(new RectF(cupCenterX - cupWidth / 2, cupCenterY - cupHeight / 2 - cupHeight / 10, cupCenterX + cupWidth / 2, cupCenterY + cupHeight / 2), new float[]{cupTopRoundRadius, cupTopRoundRadius, cupTopRoundRadius, cupTopRoundRadius,              cupBottomRoundRadius, cupBottomRoundRadius, cupBottomRoundRadius, cupBottomRoundRadius}, Path.Direction.CW);
    
    //计算杯耳宽度
    float cupEarWidth  = (w - cupWidth) * 3 / 4f;
    //计算杯耳高度
    float cupEarHeight = cupHeight / 3;
    
    //计算杯耳的中心点坐标
    float cupEarCenterX = mCenterX + cupWidth / 2;
    float cupEarCenterY = mCenterY + cupHeight / 2;
    
    //计算杯耳的圆角半径
    float cupEarRoundRadius = Math.min(cupEarWidth, cupEarHeight) / 2f;
    
    //设置杯耳画笔的描边宽度
    mCupEarPaint.setStrokeWidth(Math.min(cupEarWidth, cupEarHeight) / 3f);
    
    //重置杯耳path
    mCupEarPath.reset();
    
    //添加杯耳轨迹
    mCupEarPath.addRoundRect(new RectF(cupEarCenterX - cupEarWidth / 2, cupEarCenterY - cupEarHeight / 2 - cupHeight / 10, cupEarCenterX + cupEarWidth / 2,
                    cupEarCenterY + cupEarHeight / 2),
            new float[]{cupEarRoundRadius, cupEarRoundRadius, cupEarRoundRadius, cupEarRoundRadius,
                    cupEarRoundRadius, cupEarRoundRadius, cupEarRoundRadius, cupEarRoundRadius}, Path.Direction.CW);
    

    在onDraw方法中

    canvas.drawPath(mCupEarPath, mCupEarPaint);
    canvas.drawPath(mCupPath, mCupPaint);
    

    画杯垫

    首先计算杯垫path轨迹

    //计算杯垫宽度
    float coasterWidth = cupWidth;
    //计算杯垫高度
    float coasterHeight = (h / 2 - cupHeight) * 1 / 3f;
    
    //计算杯垫中心点坐标
    float coasterCenterX = mCenterX;
    float coasterCenterY = mCenterY + cupHeight + (h / 2 - cupHeight) / 2f;
    
    //计算杯垫圆角半径
    float coasterRoundRadius = Math.min(coasterWidth, coasterHeight) / 2f;
    
    //重置杯垫path
    mCoasterPath.reset();
    
    //添加杯垫轨迹
    mCoasterPath.addRoundRect(new RectF(coasterCenterX - coasterWidth / 2, coasterCenterY - coasterHeight / 2,
                    coasterCenterX + coasterWidth / 2, coasterCenterY + coasterHeight / 2),
            coasterRoundRadius, coasterRoundRadius, Path.Direction.CW);
    

    在onDraw方法中

    canvas.drawPath(mCoasterPath, mCoasterPaint);
    

    画烟雾

    烟雾原理

    • 根据贝塞尔曲线添加波浪轨迹
    • 根据LinearGradient实现颜色渐变效果

    每条烟雾大概如下效果


    CoffeeView

    当移动至烟雾底部的时候,重新将其移动至头部,这样循环动画,就会显示无线的滚动效果

    代码注释如下:

    //计算烟雾的宽度
    float vaporsStrokeWidth = cupWidth / 15f;
    
    //计算烟雾相隔距离大小
    float vaporsGapWidth = (cupWidth - VAPOR_COUNT * vaporsStrokeWidth) / 4f;
    mVaporsHeight   = cupHeight * 4 / 5f;
    
    //设置烟雾画笔描边大小
    mVaporPaint.setStrokeWidth(vaporsStrokeWidth);
    float startX, startY, stopX, stopY;
    
    //设置渐变效果
    LinearGradient linearGradient = new LinearGradient(mCenterX, mCenterY, mCenterX, mCenterY - mVaporsHeight,
            new int[]{mVaporColor, Color.TRANSPARENT},
            null, Shader.TileMode.CLAMP);
            
    //烟雾画笔增加渐变效果的渲染器
    mVaporPaint.setShader(linearGradient);
    
    //增加每条烟雾的path的贝塞尔波浪
    for (int i = 0; i < VAPOR_COUNT; i++) {
        mVaporsPath[i].reset();
    
        startX = (mCenterX - cupWidth / 2) + vaporsStrokeWidth / 2 + i * vaporsStrokeWidth + (i + 1) * vaporsGapWidth;
        startY = mCenterY + mVaporsHeight;
    
        stopX = startX;
        stopY = mCenterY - mVaporsHeight;
    
    
        mVaporsPath[i].moveTo(startX, startY);
        mVaporsPath[i].quadTo(startX - vaporsGapWidth / 2, startY - mVaporsHeight / 4,
                startX, startY - mVaporsHeight / 2);
    
        mVaporsPath[i].quadTo(startX + vaporsGapWidth / 2, startY - mVaporsHeight * 3 / 4,
                startX, mCenterY);
    
        mVaporsPath[i].quadTo(startX - vaporsGapWidth / 2, mCenterY - mVaporsHeight / 4,
                startX, mCenterY - mVaporsHeight / 2);
    
        mVaporsPath[i].quadTo(startX + vaporsGapWidth / 2, mCenterY - mVaporsHeight * 3 / 4,
                stopX, stopY);
    
        //add twice the bezier curve
        mVaporsPath[i].quadTo(startX - vaporsGapWidth / 2, stopY - mVaporsHeight / 4,
                startX, stopY - mVaporsHeight / 2);
    
        mVaporsPath[i].quadTo(startX + vaporsGapWidth / 2, stopY - mVaporsHeight * 3 / 4,
                startX, stopY - mVaporsHeight);
    
        mVaporsPath[i].quadTo(startX - vaporsGapWidth / 2, stopY - mVaporsHeight - mVaporsHeight / 4,
                startX, stopY - mVaporsHeight - mVaporsHeight / 2);
    
        mVaporsPath[i].quadTo(startX + vaporsGapWidth / 2, stopY - mVaporsHeight - mVaporsHeight * 3 / 4,
                stopX, stopY - 2 * mVaporsHeight);
    }
    

    烟雾动画处理:
    每条烟雾都有一个path记录其轨迹,利用path的transform方法可移动path

    /**
     * Transform the points in this path by matrix, and write the answer
     * into dst. If dst is null, then the the original path is modified.
     *
     * @param matrix The matrix to apply to the path
     * @param dst    The transformed path is written here. If dst is null,
     *               then the the original path is modified
     */
    public void transform(Matrix matrix, Path dst) {
        long dstNative = 0;
        if (dst != null) {
            dst.isSimplePath = false;
            dstNative = dst.mNativePath;
        }
        nTransform(mNativePath, matrix.native_instance, dstNative);
    }
    
    ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, -vaporHeight);
        final int finalI = i;
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mAnimatorValues[finalI] = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
    
        valueAnimator.setDuration(1000);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.setRepeatMode(ValueAnimator.RESTART);
        mValueAnimators.add(valueAnimator);
    

    在onDraw方法中,描绘烟雾

    private void drawVapors(Canvas canvas){
    for (int i = 0; i < VAPOR_COUNT; i++){
        mCalculateMatrix.reset();
        mCalculatePath.reset();
    
        float animatedValue = mAnimatorValues[i];
        mCalculateMatrix.postTranslate(0, animatedValue);
        mVaporsPath[i].transform(mCalculateMatrix, mCalculatePath);
    
        canvas.drawPath(mCalculatePath, mVaporPaint);
    }
    }
    

    我这里设置了每条烟雾的移动速度是一样的,你们可以下载源码来修改,看看不同的移动速度的效果

    Github

    相关文章

      网友评论

        本文标题:Android 自定义View之咖啡杯动画

        本文链接:https://www.haomeiwen.com/subject/vgopaftx.html