当前位置:首页 > 芯闻号 > 充电吧
[导读]  上篇文章Android之自定义Drawable实现灵动的红鲤鱼动画(上篇)我们绘制了可以摆动身体的小鱼,本篇就分享一下如何让小鱼游到手指点击的位置。用到的主要技术如下:1)、三阶贝塞尔曲线 2)、

  
上篇文章Android之自定义Drawable实现灵动的红鲤鱼动画(上篇)我们绘制了可以摆动身体的小鱼,本篇就分享一下如何让小鱼游到手指点击的位置。用到的主要技术如下:

1)、三阶贝塞尔曲线 
2)、Path的Measure

一、动画分析

小鱼的行走不是简单的位移,不难看出在小鱼位移的同时身体的角度还随着前进的方向而变化,所以本篇要解决如下两部分:

1)、鱼身的位移 
2)、鱼身的旋转 
3)、点击处的水波纹

二、技术分析

1)、鱼身的位移

  上篇介绍自定义Drawable的时候分析了Drawable需要作为ImageView的drawable资源或者作为View的background才可以显示出来,那么我们就可以通过ImageView.setImageDrawable()将自定义Drawable和ImageView关联起来,通过位移ImageView来移动小鱼。 
  为了让鱼游动的轨迹更真实,位移路径只有直线是不行的,在鱼需要转身的时候行走路线应该是有弧度的曲线 ,只要涉及到曲线就少不了贝塞尔曲线,涉及到贝塞尔曲线就要涉及到贝塞尔曲线控制点的确定,这里重点介绍一下控制点的确定问题

 
上图对关键点都做了简单标注,控制点确定过程如下: 
1):利用头部圆心、鱼身的重心以及点击点坐标来唯一确定一个特征三角形。 
2):确定鱼身需要向左还是向右转弯,这是个很关键的问题。我们知道,对于同一目的地,向右转和向右转动都可以到达,但是一定有一个最优的方案,假设我们的小鱼有鱼类智商,那么能转45°能到达就肯定不会转315°,结合这个理论和1)的特征三角形,可以知道三角形内角AOB就是我们要的转动的角度,知道转动的角度那么转动方向自然而然就知道了。现在我们只有AOB三点的坐标如何求出夹角呢?我们可以 利用向量的夹角公式计算夹角cosAOB = (OA*OB)/(|OA|*|OB|)其中OA*OB是向量的数量积,计算过程如下 
OA=(Ax-Ox,Ay-Oy) 
OB=(Bx-Ox,By-Oy) 
OA*OB=(Ax-Ox)(Bx-Ox)+(Ay-Oy)(By-Oy) 
|OA|表示线段OA的模即OA的长度,如果对向量不太了解的朋友请自行百度一下。

3):知道了向左转还是向右转就可以确定曲线的控制点,上图控制点是我凭经验和多次实践确定的比较好的方案第一个控制点就是头部的圆心处,第二个控制点就是转动方向的1/2上的一点

好了,上述的控制点确定之后就可以实用A点、A点、C点、M点来确定一条三阶贝塞尔曲线了

4):那么问题来了我们拿到贝塞尔曲线如何让 

ImageView移动呢?我们经常看到各大直播平台送主播礼物时那些小礼物不规则地向上升是怎么实现呢?原理都差不多,无非就是让控件跟着路径走。传统的做法是利用自定义估值器来计算出动画行走路径,还有一种方法可以不用自定义估值器,LOLLIPOP版本出来之后属性动画里新增了一个路径动画,我们只用丢进去一个控件和一条路径以及模板参数就能让控件跟着这个路径走,方法如下


ObjectAnimator animator = ObjectAnimator.ofFloat(ivFish, "x", "y", path);

需要明确一点,这里的位移只是平移,也就是说鱼的角度不会因为控件转动而改变,要想让鱼在转弯的时候沿路径切线方向转动请听我继续分析.

2)、鱼身的旋转

计算鱼身的旋转角度只用计算出路径切线方向即可,数学里的切线和导数是挂钩的,初代版本我是通过自定义估值器来确定路径的,自定义估值器的时候可以求出当前时刻三阶贝塞尔曲线的导数,那是一个痛苦的过程,公式代码写了十几行,而且效果不好。后来发现一个强大的类PathMeasure,我们可以通过

getLength()


计算出一条Path的总长度,还可以通过 
getPosTan(float distance, float pos[], float tan[]) 
根据传入的长度计算出路径的某点坐标和切线方向,简直就是为我们量身定做的。其中参数distance就是我们需要计算切线的点距Path的起点的距离,通过在AnimatorUpdateListener中获取Animator的当前进度,再与路径总长度相乘,就得到了当前动画已行走的路径长度distance,接下来传入两个长度>=2的非空数组pos和tan数据就可以得到坐标和切线角度的相关参数了。 
pos数组的前两个值就是x,y的坐标值 
tan前两个值就是所求角的对边和临边的相对长度值(也有可能是绝对长度,因为无法看到native源码,但是不管是相对的还是绝对的这两个值的比例知道就可以求出对应的角度了)

3)、点击处的水波纹

水波纹效果比较简单,只需改变圆环的大小和透明度即可,代码部分会详细说明。 
分析完位移和旋转,做一个效果图看看大家就更清楚了。为了让大家更清晰地看出效果我把ImageView背景设置成蓝色,可以看出蓝色的ImageView只负责平移并没有旋转,旋转效果是Drawable中的小鱼执行的。

三、代码实现 最重要的特征三角夹角计算代码

注意点: 
1)、变量abc是向量ab和ac的数量积 
2)、angleCos是弧度值表示的,真正的角度需要通过Math.toDegrees转换成角度制

/**
     * 利用向量的夹角公式计算夹角
     * cosBAC = (AB*AC)/(|AB|*|AC|)
     * 其中AB*AC是向量的数量积AB=(Bx-Ax,By-Ay)  AC=(Cx-Ax,Cy-Ay),AB*AC=(Bx-Ax)*(Cx-Ax)+(By-Ay)*(Cy-Ay)
     *
     * @param center 顶点 A
     * @param head   点1  B
     * @param touch  点2  C
     * @return
     */
    public static float includedAngle(PointF center, PointF head, PointF touch) {
        float abc = (head.x - center.x) * (touch.x - center.x) + (head.y - center.y) * (touch.y - center.y);
        float angleCos = (float) (abc /
                ((Math.sqrt((head.x - center.x) * (head.x - center.x) + (head.y - center.y) * (head.y - center.y)))
                        * (Math.sqrt((touch.x - center.x) * (touch.x - center.x) + (touch.y - center.y) * (touch.y - center.y)))));

        float temAngle = (float) Math.toDegrees(Math.acos(angleCos));
        //判断方向  正:左侧  负:右侧 0:线上,但是Android的坐标系Y是朝下的,所以左右颠倒一下
        float direction = (center.x - touch.x) * (head.y - touch.y) - (center.y - touch.y) * (head.x - touch.x);
        //线上还要判断是同向还是逆向
        if (direction == 0) {
            if (abc >= 0) {
                return 0;
            } else
                return 180;
        } else {
            if (direction > 0) {//右侧顺时针为负
                return -temAngle;
            } else {
                return temAngle;
            }
        }
    }

三阶贝塞尔曲线生成代码

其中: 
1)、fishMiddle 是确定鱼身重心 
2)、fishHead获取鱼头圆心 
3)、angle即通过夹角计算方法计算出特征三角形的夹角 
4)、delta是鱼身的角度,angle/2+delta就可以得出特征三角形夹角中线跟x轴正方向的角度了 
有了起点fishMiddle,转动的长度1.6R以及转动的角度(angle/2+delta)就可以通过(上篇)的calculatPoint()方法计算出控制点的坐标了,有了控制点就可以通过cubicTo函数得到三阶贝塞尔曲线了

 Path path = new Path();
 PointF fishMiddle = new PointF(ivFish.getX() + fishDrawable.getMiddlePoint().x, ivFish.getY() + fishDrawable.getMiddlePoint().y);
 PointF fishHead = new PointF(ivFish.getX() + fishDrawable.getHeadPoint().x, ivFish.getY() + fishDrawable.getHeadPoint().y);
 path.moveTo(ivFish.getX(), ivFish.getY());
 final float angle = includedAngle(fishMiddle, fishHead, touch);
 float delta = calcultatAngle(fishMiddle, fishHead);
 PointF controlF = calculatPoint(fishMiddle, 1.6f*fishDrawable.HEAD_RADIUS, angle / 2 + delta);
 path.cubicTo(fishHead.x, fishHead.y, controlF.x, controlF.y, touch.x - fishDrawable.getHeadPoint().x, touch.y - fishDrawable.getHeadPoint().y);

鱼身转动代码

其中: 
1)、tan数组变量就是我们存取正切值的两个边的信息数组,通过public static native double atan2(double y, double x);得到切角的弧度值,转换为角度即可算出转动角度。细心的朋友发现Math.atan2(-tan[1], tan[0])中的y值前边有一个负号“-”,这是为了适配Android坐标Y的正方向和自然直角左边系Y轴方向相反的情况。 
2)、因为我们用不到坐标点信息所以在getPosTan(float distance, float pos[], float tan[])中传入的pos数组是null 
3)、在动画监听回调中获取到实时角度angle = (float) (Math.toDegrees(Math.atan2(-tan[1], tan[0])))

       final float[] tan = new float[2];
        //设置为false代表不强制把Path闭合
        final PathMeasure pathMeasure = new PathMeasure(path, false);

        animator = ObjectAnimator.ofFloat(ivFish, "x", "y", path);
        animator.setDuration(2 * 1000);
        animator.setInterpolator(new AccelerateDecelerateInterpolator());

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction = animation.getAnimatedFraction();
                pathMeasure.getPosTan(pathMeasure.getLength() * fraction, null, tan);
                float angle = (float) (Math.toDegrees(Math.atan2(-tan[1], tan[0])));
                fishDrawable.setMainAngle(angle);
            }
        });

水波纹代码

代码比较简单,需要注意的是ofFloat中的“radius”关键字,我们知道默认的属性动画关键字有"alpha""scaleX""scaleY""rotationX""rotationY""Y"等等,唯独没有“radius”关键字,对的我们自己定义的,ObjectAnimator的ofFloat(Object target, String propertyName, float... values)方法会通过反射技术在参数target中寻找关键字对应的set方法,即我们需要在“this”类中定义一个setRadius(参数)方法,其中的参数是我们定义的浮点数0~1中的过程值,通过setRadius方法改变水波纹的alpha和半径值,形成水波纹扩散和渐隐的效果

rippleAnimator = ObjectAnimator.ofFloat(this, "radius", 0f, 1f).setDuration(1000);

public void setRadius(float currentValue) {
        alpha = (int) (100 * (1 - currentValue) / 2);
        radius = DEFAULT_RADIUS * currentValue;
        invalidate();

    }

最后需要注意一点如上代码都是写在一个继承了RelativeLayout的自定义ViewGroup中,ViewGroup中onDraw的触发和View中不一样,需要在绘制前写上一句setWillNotDraw(false)来打开强制绘制功能,否则水波纹无法显示。

四、结语

(上篇)得到了很多朋友的支持,非常感动,谢谢大家给予我的鼓励。 动画是个很灵活的事情其实大家可以找找不同的思路来实现,本篇小鱼的转动并不完美,但是我还没找到更好的转弯方法,希望有有更好思路的朋友多多交流。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭