Android自定义View动画--一个绳子拉动的弹弹球

运行效果

灵感来源

原版

分析运动规律

平行时.jpg
最低点.jpg
最高点.jpg

这是三个状态的截图


组成

  • 左右俩个圆点
  • 俩个圆角的正方形
  • 一条曲线
  • 一个圆球

运动规律

1.颜色渐变:

状态 左正方形中点颜色值 线中点颜色值 右正方形中点颜色值
最高 #E42A34 #FF8ED0 #D867DF
平行 #E72F2C #FD76C1 #C118D9
最低 #EF2539 #F1358A #BD19D3

2.运动状态:

  • 俩个正方形中点是静止不变的
  • 正方形和线:正方形来回上下旋转,线是向下弯接着向上弯,可以看到一开始线从水平往下拉的过程,速度是先块后慢的(动画可以选用先加速后减速的插值器AccelerateDecelerateInterpolator
  • 球:由图可以看出,它是1.最低到最高减速,2.最高到水平加速,3.水平到最低减速(例子里简化了该状态,用了匀速上下运动替代了整个过程)

关键代码

1.设置画笔颜色渐变和初始化画笔

        //设置渐变,从点A到点B线性渐变
        shader = new LinearGradient(leftRectPointF.x - 10 * factor, leftRectPointF.y, rightRectPointF.x + 10 * factor, rightRectPointF.y, leftGradientColor,
                rightGradientColor, Shader.TileMode.CLAMP);
        mPaint.setShader(shader);
        //设置拐角圆角
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeWidth(15);
        //设置画笔为圆笔头
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStyle(Paint.Style.STROKE);

AB俩点指的正方形(旋转后45度后的)左右俩个端点,leftRectPointF和rightRectPointF看下面2中代码端注释

2. 画圆角正方形并旋转45度(右边正方形为135度)

    /**
     * 正方形在动画里旋转的角度
     */
    private float degree = 0;
     /**
     * 左正方形中点,屏幕中点到左右正方形中心点的距离是45*factor
     */
    private PointF leftRectPointF = new PointF(mScreenWidth / 2 - 45 * factor, mScreenHeight / 2);
    /**
     * 右正方形中点
     */
    private PointF rightRectPointF = new PointF(mScreenWidth / 2 + 45 * factor, mScreenHeight / 2);
    /**
     * 画左右俩旋转45度的正方形
     */
    private void drawLiftAndRightRect(Canvas canvas) {
        //画左边正方形
        canvas.save();
        //这里的旋转要放在最上面,因为canvas的变换是反着来的,这里需要的是先画出正方形再旋转画布
        canvas.rotate(45 - degree, leftRectPointF.x, leftRectPointF.y);
        //正方形的边长为14*factor,一半也就是7*factor
        canvas.drawRoundRect(leftRectPointF.x - 7 * factor, leftRectPointF.y - 7 * factor, leftRectPointF.x + 7 * factor, leftRectPointF.y + 7 * factor, 15, 15, mPaint);
        canvas.restore();

        //画右边正方形
        canvas.save();
        //角度值因为是左右相反,和上面相反
        canvas.rotate(45 + degree, rightRectPointF.x, rightRectPointF.y);
        canvas.drawRoundRect(rightRectPointF.x - 7 * factor, rightRectPointF.y - 7 * factor, rightRectPointF.x + 7 * factor, rightRectPointF.y + 7 * factor, 15, 15, mPaint);
        canvas.restore();
    }

3.画贝塞尔曲线

示例曲线运动规律大致如下

3.gif

左端点右端点不变,然后不断变化控制点高度即可。

本示例的弹弹球的是左右端点上下变换,控制点也上下变化的。

     /**
     * 画曲线
     */
    private void drawLine(Canvas canvas) {
        mPath.reset();
        mPath.moveTo(lineLeftEndPointF.x, lineLeftEndPointF.y);
        mPath.quadTo(quadControllerPointF.x, quadControllerPointF.y, lineRightEndPointF.x, lineRightEndPointF.y);
        canvas.drawPath(mPath, mPaint);
    }

用degree的角度来计算线与正方形接点的坐标

public void setDegree(float degree) {
        this.degree = degree;
        //算出左边正方形和线连接点坐标
        lineLeftEndPointF = calculatPoint(leftRectPointF, rectDiagonalHalf, degree);
        //右边角度的是180的(余)数
        lineRightEndPointF = calculatPoint(rightRectPointF, rectDiagonalHalf, 180 - degree);
        //控制点
        quadControllerPointF = calculatQuadControllerPointF(45 * factor, degree);

        ...

        invalidate();
    }

其中calculatPoint函数和calculatQuadControllerPointF如下

/**
     * 输入起点、长度、旋转角度计算终点
     * <p>
     * 知道一个线段,一个定点,线段旋转角度求终点坐标
     * 根据极坐标系原理 x = pcos(a), y = psin(a)
     *
     * @param startPoint 起点
     * @param length     长度
     * @param angle      旋转角度
     * @return 计算结果点
     */
    private static PointF calculatPoint(PointF startPoint, float length, float angle) {
        float deltaX = (float) Math.cos(Math.toRadians(angle)) * length;
        //符合Android坐标的y轴朝下的标准,和y轴有关的统一减180度
        float deltaY = (float) Math.sin(Math.toRadians(angle - 180)) * length;
        return new PointF(startPoint.x + deltaX, startPoint.y + deltaY);
    }

calculatPoint()是根据极坐标定律,知道起点和线段长度和旋转角度计算端点坐标

/**
     * 计算控制点
     */
    private PointF calculatQuadControllerPointF(float length, float degree) {
        
        //提高控制点高度
        length += 20 * factor;
        float height = -(float) (Math.tan(Math.toRadians(degree)) * length);
        Log.d("height", "height:" + height);
        return new PointF(mScreenWidth / 2, mScreenHeight / 2 + height);
    }

本来贝塞尔曲线的控制点应该是在曲线中点连线上,但是我试了一下效果,觉得曲线不够弯,所以给它加了20*factor

4.画球

/**
     * 画球
     */
    private void drawCircle(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(circlePointF.x, circlePointF.y, 9 * factor, mPaint);
    }

5.degree数值变化引擎

        //初始化角度引擎
        ObjectAnimator degreeAnimator = ObjectAnimator.ofFloat(this, "degree", 0f, -30f, 0f, 15f, 0, -10, 0, 5, 0);
        degreeAnimator.setDuration(1200);
        degreeAnimator.setRepeatCount(ValueAnimator.INFINITE);
        degreeAnimator.setRepeatMode(ValueAnimator.RESTART);
        degreeAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        degreeAnimator.start();

用了属性动画,设置加速减速插值器,模拟绳子反弹力越来越小的效果

6.颜色渐变引擎

      //左边渐变边界点颜色值的变化引擎
        ObjectAnimator leftGradientColorAnimator = ObjectAnimator.ofArgb(this, "leftGradientColor", 0xFFE42A34, 0xFFE72F2C, 0xFFEF2539);
        leftGradientColorAnimator.setDuration(1200);
        leftGradientColorAnimator.setRepeatCount(ValueAnimator.INFINITE);
        leftGradientColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
        leftGradientColorAnimator.setInterpolator(new LinearInterpolator());
        leftGradientColorAnimator.start();
        //右边渐变边界点颜色值的变化引擎
        ObjectAnimator rightGradientColorAnimator = ObjectAnimator.ofArgb(this, "rightGradientColor", 0xFFD767DF, 0xFFC118D9, 0xFFBD19D3);
        rightGradientColorAnimator.setDuration(1200);
        rightGradientColorAnimator.setRepeatCount(ValueAnimator.INFINITE);
        rightGradientColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
        rightGradientColorAnimator.setInterpolator(new LinearInterpolator());
        rightGradientColorAnimator.start();

7.球的动画引擎

      //最低往返最高
        final ObjectAnimator circleHeightAnimator2 = ObjectAnimator.ofFloat(this, "circleBottomHeight", 960 + 20 * factor, 960 - 70 * factor);
        circleHeightAnimator2.setDuration(600);
        circleHeightAnimator2.setRepeatCount(ValueAnimator.INFINITE);
        circleHeightAnimator2.setRepeatMode(ValueAnimator.REVERSE);
        circleHeightAnimator2.setInterpolator(new DecelerateInterpolator());
        //延迟160ms,等线先到最低点,再开始球的周期运动
        circleHeightAnimator2.setStartDelay(200);
        circleHeightAnimator2.start();

这里circleBottomHeight定义的是球的底部的y轴坐标值,球中心坐标在setDegree()里实时算出

     public void setDegree(float degree) {
        ...
        
        //球的半径是10*factor
        circlePointF = new PointF(540f, circleBottomHeight - 10 * factor);
        ...
    }

8.其他的看源代码吧....


不足

  • 球和绳子的互动不是很和谐(因为简化了球的运动步骤)

源代码:

CustomViewSet


end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容

  • 引子 每天我们晚上加班回家,可能都会用到滴滴或者共享单车。打开 app 会看到如下的界面: app 界面上会显示出...
    一缕殇流化隐半边冰霜阅读 45,400评论 51 188
  • 周一的魔都早高峰习惯性伴随着暴雨,今天一早为小欧二期的开学典礼,虽然因为暴雨的影响,早上虽然迟到了几分钟,但是开学...
    yang_x阅读 126评论 0 3
  • 中医讲究辨证论治。所谓“辨证”,就是将四诊(望、闻、问、切)所收集病人的症状和体征,通过综合分析,辨清疾病的原因、...
    澄熵阅读 1,124评论 0 3
  • “我国历史文化悠久,历史经验告诉我们……” 这句话对吗? 从历史角度是不对的。 错在哪? 错在“历史经验”。 要从...
    马小渣阅读 474评论 0 0
  • 如果那年 青春是一首诗 诗起是你 诗尾却不是我 温暖的夏夜里 听你讲你和北斗星的故事 一句一字 埋没在风里 你不在...
    我吃西兰花阅读 418评论 3 2