今天来自李晨玮分享的直播礼物效果Demo。对于直播中送车,点赞都有借鉴意义。李晨玮的简书为:http://www.jianshu.com/u/b2df0a5ead6f,欢迎大家关注。

国际惯例,先来看下今天要实现的效果图:

仿直播送礼动画

仿饿了么购物车动画

上面两张图分别是仿直播平台送礼动画和饿了么商品加入购物车动画。

1、小试牛刀

我们先来热热身,这里我打算用二阶贝塞尔曲线画出动态波浪的效果,效果如下:

动态波浪

效果还是不错的,很自然的动画呈现,平滑的过渡。
我们来一步步分析下:
1、首先,我们先单纯的思考屏幕内的可见区域,可以把它理解成近似一个周期的sin函数,只是它的幅度没有那么高,类似下图:

sin函数

根据上面的图,其实我们可以发现它的起始点分别是(0,0)和(2π,0),控制点分别是(π/2,1)和(3π/2,-1),由于有两个控制点,所以这里可以用三阶贝塞尔曲线来画,不过我暂时打算先用二阶贝塞尔曲线来画,也就是把上面的图拆分成两部分:
第一部分:起始点为(0,0)和(π,0),控制点为(π/2,1)
第二部分:起始点为(π,0)和(2π,0),控制点为(3π/2,-1)
然后我们把2π的距离当成是屏幕的宽度,那么π的位置就是屏幕宽度的一半,这样分解下来,配合谷歌官方给我们提供的API,我们就可以很好的实现这2段曲线的绘制,我们先暂定波浪的高度为100px,实现代码也就是:

mPath.moveTo(0, mScreenHeight / 2);
mPath.quadTo(mScreenWidth / 4, mScreenHeight / 2 - 100,
mScreenWidth / 2 , mScreenHeight / 2);
mPath.quadTo(mScreenWidth * 3 / 4, mScreenHeight / 2 + 100,
mScreenWidth , mScreenHeight / 2);

然后我们把下面的空白区域铺满:

mPath.lineTo(mScreenWidth, mScreenHeight);
mPath.lineTo(0, mScreenHeight);

来看下此时的效果图:

波浪图

2、实现了初步的效果,那现在我们就应该来思考如何让这个波浪动起来,其实很简单,只需要我们在屏幕外再画出另一周期的曲线,然后让它做平移动画这样就可以了,熟悉sin函数的朋友,肯定能想到下面这幅图:

sin函数

现在我们把屏幕外的另一半也曲线也画出来(具体坐标这里就不再写出来了,大家画下图就能清楚):

mPath.moveTo(-mScreenWidth + mOffset, mScreenHeight / 2);
mPath.quadTo(-mScreenWidth * 3 / 4 + mOffset,
mScreenHeight / 2 - 100, -mScreenWidth / 2
+ mOffset, mScreenHeight / 2);
mPath.quadTo(-mScreenWidth / 4 + mOffset,
mScreenHeight / 2 + 100, 0 + mOffset, mScreenHeight / 2);
mPath.quadTo(mScreenWidth / 4 + mOffset,
mScreenHeight / 2 - 100, mScreenWidth / 2
+ mOffset, mScreenHeight / 2);
mPath.quadTo(mScreenWidth * 3 / 4 + mOffset,
mScreenHeight / 2 + 100, mScreenWidth + mOffset, mScreenHeight / 2);

3、平移动画的实现,这里我们利用到了Android3.0以后给我们提供的属性动画,然后平移长度即为一个周期长度(屏幕宽度):

/*** 设置动画效果*/
private void setViewanimator() {ValueAnimator valueAnimator = ValueAnimator.ofInt(0, mScreenWidth);valueAnimator.setDuration(1200);     valueAnimator.setRepeatCount(ValueAnimator.INFINITE);valueAnimator.setInterpolator(new LinearInterpolator());valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mOffset = (int) animation.getAnimatedValue();//当前平移的值invalidate();}});valueAnimator.start();
}

拿到平移的值后,我们只需要在各点的x轴动态的加上值,这样就会呈现出动态波浪了。

mPath.quadTo(-mScreenWidth * 3 / 4 + mOffset,
mScreenHeight / 2 - 100, -mScreenWidth / 2
+ mOffset, mScreenHeight / 2);
mPath.quadTo(-mScreenWidth / 4 + mOffset,
mScreenHeight / 2 + 100, 0 + mOffset,
mScreenHeight / 2);
mPath.quadTo(mScreenWidth / 4 + mOffset,
mScreenHeight / 2 - 100, mScreenWidth / 2
+ mOffset, mScreenHeight / 2);
mPath.quadTo(mScreenWidth * 3 / 4 + mOffset,
mScreenHeight / 2 + 100, mScreenWidth + mOffset,
mScreenHeight / 2);

2、仿饿了么商品加入动画效果:

如果你理解了上面的“小试牛刀”例子,要实现这个效果就非常容易了,首先我们要确定添加购物车“+”的位置,然后确定购物车的位置,也就是我们贝塞尔曲线的起始点了,然后再给出一个控制点,只需要让它比“+”的位置高一些,让它成抛物线的效果即可。

1、要确定一个View所在屏幕内的位置,我们可以利用谷歌官方给我们提供的API(具体根据界面中的布局来确定):

/*** <p>Computes the coordinates of this view on the screen. The argument* must be an array of two integers. After the method returns, the array* contains the x and y location in that order.</p>** @param outLocation an array of two integers in which to hold the coordinates*/public void getLocationOnScreen(@Size(2) int[] outLocation) {getLocationInWindow(outLocation);final AttachInfo info = mAttachInfo;if (info != null) {outLocation[0] += info.mWindowLeft;outLocation[1] += info.mWindowTop;}}/*** <p>Computes the coordinates of this view in its window. The argument* must be an array of two integers. After the method returns, the array* contains the x and y location in that order.</p>** @param outLocation an array of two integers in which to hold the coordinates*/public void getLocationInWindow(@Size(2) int[] outLocation) {if (outLocation == null || outLocation.length < 2) {throw new IllegalArgumentException("outLocation must be an array of two integers");}outLocation[0] = 0;outLocation[1] = 0;transformFromViewToWindowSpace(outLocation);}

这里可以获取到一个int类型的数组,数组下标0和1分别代表着x和y坐标,需要注意的一点是,别在onCreate里去调用这个方法(点击事件内可以),否则获取到的坐标只会是(0,0),这个方法需要在Activity获取到焦点后调用才有效果。

2、当我们拿到了这3点坐标,我们就可以画出对应的贝塞尔曲线。然后我们只需要让这个小红点在这条曲线路径里去做平滑移动就可以了,由于小红点是带有x,y坐标的,曲线的每一个点也是带有x,y坐标的,聪明的你应该已经想到这里还是一样用到了属性动画,动态的去改变当前小红点的x,y坐标即可。
由于谷歌官方只给我们提供了一些比较基础的插值器,比如Int,Float,Argb等,并没有给我们提供关于坐标的插值器,不过好在它给我们开放了相关接口,我们只需要对应的去实现它即可,这个接口叫TypeEvaluator:

/*** Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators* allow developers to create animations on arbitrary property types, by allowing them to supply* custom evaluators for types that are not automatically understood and used by the animation* system.** @see ValueAnimator#setEvaluator(TypeEvaluator)*/
public interface TypeEvaluator<T> {/*** This function returns the result of linearly interpolating the start and end values, with* <code>fraction</code> representing the proportion between the start and end values. The* calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,* and <code>t</code> is <code>fraction</code>.** @param fraction   The fraction from the starting to the ending values* @param startValue The start value.* @param endValue   The end value.* @return A linear interpolation between the start and end values, given the*         <code>fraction</code> parameter.*/public T evaluate(float fraction, T startValue, T endValue);
}

从注释里我们可以得到这些信息,首先我们需要去实现evaluate方法,然后这里提供了3个回调参数,它们分别代表:
float fraction:动画的完成程度,0~1
T startValue:动画开始值
T endValue: 动画结束值(这里而外补充一点,要想得到当前的动画值其实也很简单,只需要用(动画开始值+动画完成程度*动画结束值))
这里贴下关于小红点移动坐标的插值器代码:(Point是系统自带的类,可以用来记录X,Y坐标点)

/*** 自定义Evaluator*/public class CirclePointEvaluator implements TypeEvaluator {/*** @param t   当前动画进度* @param startValue 开始值* @param endValue   结束值* @return*/@Overridepublic Object evaluate(float t, Object startValue, Object endValue) {Point startPoint = (Point) startValue;Point endPoint = (Point) endValue;int x = (int) (Math.pow((1-t),2)*startPoint.x+2*(1-t)*t*mCircleConPoint.x+Math.pow(t,2)*endPoint.x);int y = (int) (Math.pow((1-t),2)*startPoint.y+2*(1-t)*t*mCircleConPoint.y+Math.pow(t,2)*endPoint.y);return new Point(x,y);}}

这里的x和y是根据二阶贝塞尔曲线计算出来的,对应的公式为:

二阶贝塞尔表达式

然后我们在值变化监听器中去不断绘制这个小红点的位置就可以了。

//设置值动画ValueAnimator valueAnimator = ValueAnimator.ofObject(new CirclePointEvaluator(), mCircleStartPoint, mCircleEndPoint);valueAnimator.setDuration(600);valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {Point goodsViewPoint = (Point) animation.getAnimatedValue();mCircleMovePoint.x = goodsViewPoint.x;mCircleMovePoint.y = goodsViewPoint.y;invalidate();}});

3、仿直播送礼物:

有了前两个例子的基础,现在要做类似于这种运动轨迹的效果是不是很有感觉了?打铁要趁热,我们接着来说直播送礼这个效果。
首先,我们先简化一下,看下图:

仿直播送礼

1、首先我们需要知道这条曲线的路径要怎么画,这里我应该不需要我再说了,三阶贝塞尔曲线,起始点和结束点分别为(屏幕宽度的一半,屏幕高度)和(屏幕宽度的一半,0),然后控制点有2个,分别是(屏幕宽度,四分之三屏幕高度)和(0,四分之一屏幕高度)

mPath.moveTo(mStartPoint.x, mStartPoint.y);
mPath.cubicTo(mConOnePoint.x, mConOnePoint.y,
mConTwoPoint.x, mConTwoPoint.y, mEndPoint.x,
mEndPoint.y);
canvas.drawPath(mPath, mPaint);

2、然后我们来说下关于这个星星的实现,这里是用到一张星星的图片,通过资源文件转Bitmap对象,再赋予给所创建的Canvas画布,然后通过Xfermodes将图片进行渲染变色,最后通过ImageView来加载。

来自Graphics下的XferModes

这里我们取SrcIn模式,也就是我们先绘制Dst(资源文件),然后再绘制Src(画笔颜色),当我们设置SrcIn模式时,自然就剩下的Dst的形状+Src的颜色,也就是不同颜色的星星。

/*** 画星星并随机赋予不同的颜色** @param color* @return*/private Bitmap drawStar(int color) {//创建和资源文件Bitmap相同尺寸的Bitmap填充CanvasBitmap outBitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(outBitmap);canvas.drawBitmap(mBitmap, 0, 0, mPaint);//利用Graphics中的XferModes对Canvas进行着色canvas.drawColor(color, PorterDuff.Mode.SRC_IN);canvas.setBitmap(null);return outBitmap;}

3、接下来就是让星星动起来,老套路,我们利用属性动画,去获取贝塞尔曲线上的各点坐标位置,然后动态的给ImageView设置坐标即可。这里的坐标点我们需要通过三阶贝塞尔曲线公式来计算:

三阶贝塞尔表达式

public class StarTypeEvaluator implements TypeEvaluator<Point> {@Overridepublic Point evaluate(float t, Point startValue, Point endValue) {//利用三阶贝塞尔曲线公式算出中间点坐标int x = (int) (startValue.x * Math.pow((1 - t), 3) + 3 * mConOnePoint.x * t * Math.pow((1 - t), 2) + 3 *mConTwoPoint.x * Math.pow(t, 2) * (1 - t) + endValue.x * Math.pow(t, 3));int y = (int) (startValue.y * Math.pow((1 - t), 3) + 3 * mConOnePoint.y * t * Math.pow((1 - t), 2) + 3 *mConTwoPoint.y * Math.pow(t, 2) * (1 - t) + endValue.y * Math.pow(t, 3));return new Point(x, y);}
}

4、然后再带上一个渐隐(透明度)的属性动画动画即可。

//设置属性动画ValueAnimator valueAnimator = ValueAnimator.ofObject(new StarTypeEvaluator(pointFFirst, pointFSecond), pointFStart,pointFEnd);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {Point point = (Point) animation.getAnimatedValue();imageView.setX(point.x);imageView.setY(point.y);}});valueAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);StarViewGroup.this.removeView(imageView);}});//透明度动画ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 1.0f, 0f);//组合动画AnimatorSet animatorSet = new AnimatorSet();animatorSet.setDuration(3500);animatorSet.play(valueAnimator).with(objectAnimator);animatorSet.start();valueAnimator.start();

这样我们就实现了上面简化版的效果,然后我们来完成下最终满屏星星。
1、首先,这个星星我们是通过资源文件加载到Canvas画布,然后再装载到ImageView里去显示,现在屏幕里有很多星星,所以我们考虑自定义一个ViewGroup,让其继承于RelativeLayout。

2、再来观察下效果图,发现这些星星大致是往一定的轨迹在飘动,但是位置好像又不是一层不变的,所以这里我们可以知道,这4个关键点(起始点,结束点,2个控制点)是会变化的,所以我们只可以监听下这个ViewGroup的onTouch事件,在用户触摸屏幕的时候,去动态生成这几个点的坐标,其他的就没变化了,根据三阶贝塞尔曲线公式就可以星星当前所在的位置,然后进行绘制。

    /*** 监听onTouch事件,动态生成对应坐标* @param event* @return*/@Overridepublic boolean onTouchEvent(MotionEvent event) {mStartPoint = new Point(mScreenWidth / 2, mScreenHeight);mEndPoint = new Point((int) (mScreenWidth / 2 + 150 * mRandom.nextFloat()), 0);mConOnePoint = new Point((int) (mScreenWidth * mRandom.nextFloat()), (int) (mScreenHeight * 3 * mRandom.nextFloat() / 4));mConTwoPoint = new Point(0, (int) (mScreenHeight * mRandom.nextFloat() / 4));addStar();return true;}

好了,文章到这里就结束了,由于篇幅限制,这里不能对一些东西讲的太细,比如一些自定义View的基础,还有属性动画的用法,大家自行查阅相关资料哈。

Android开发之贝塞尔曲线进阶篇(仿直播送礼物,饿了么购物车动画)相关推荐

  1. Path之贝塞尔曲线 进阶篇

    一.Path常用方法表 为了兼容性(偷懒) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法.忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊.宝宝此刻内心也 ...

  2. C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码...

    原文:C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码 前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github. ...

  3. C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码...

    C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码 原文:C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码 前言 系列 ...

  4. android运动轨迹怎么画,Android 利用三阶贝塞尔曲线绘制运动轨迹的示例

    本篇文章主要介绍了Android 利用三阶贝塞尔曲线绘制运动轨迹的示例,分享给大家,具体如下: 实现点赞效果,自定义起始点以及运动轨迹 效果图: xml布局: xmlns:tools="ht ...

  5. C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码

    我记得,之前在写安卓方面的文章的时候,有人就问过我.Xamarin.Android为什么打包出来这么大?随便一个HelloWord就20-30MB? 嗯..今天我们就来解决这个问题.. 我们先从指定一 ...

  6. C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码

    本篇..基本可以算是Xamarin在应用开发过程中的核心了..真的很很很重要.. 想学习的..想用的..建议仔细阅读..嗯..打酱油的 ..快速滑倒下面点个推荐 - - 哈哈哈... 今天的学习内容? ...

  7. 【Android UI】贝塞尔曲线 ⑦ ( 使用 德卡斯特里奥算法 公式计算的 方法绘制三阶贝塞尔曲线示例 )

    文章目录 一.使用 德卡斯特里奥算法 公式计算的 方法绘制三阶贝塞尔曲线 二.代码示例 贝塞尔曲线参考 : https://github.com/venshine/BezierMaker 一.使用 德 ...

  8. C#使用Xamarin开发可移植移动应用进阶篇(10.综合演练,来一份增删改查CRUD)

    说点什么.. 呃 也有半个月没更新了. 本来这篇的Demo早就写完了,文章也构思好了.迟迟没发布..是因为实在太忙.. 项目要上线..各种  你们懂的.. 正赶上自己十一人生大事..结婚..所以..忙 ...

  9. C#使用Xamarin开发可移植移动应用进阶篇(9.混淆代码,防止反编译)

    嗯,既然是客户端应用,自然而然就需要一些防止源码泄漏的手段.通过C#编写的APP,完全是可以直接解压APK,然后得到里面的DLL然后进行反编译的.. 如下图: 嗯..这样就会造成代码泄漏.. 下面就介 ...

  10. 《Android开发艺术探索》完结篇

    笔记链接: <Android开发艺术探索>之Activity的生命周期和启动模式(一) <Android开发艺术探索>之IPC机制上(二) <Android开发艺术探索& ...

最新文章

  1. 海口这家只收5元的理发店火了 顾客求涨价老板都不肯
  2. c语言变量在头文件定义变量吗,在头文件C中声明变量
  3. python学习笔记(七)——类基础
  4. 科大星云诗社动态20210228
  5. pixhawk的姿态控制算法解读
  6. html答题赚钱源码,WTS在线答题系统 v1.0.0
  7. Cloud in Action: Install OpenStack Ocata from scratch
  8. 基于vue2+nuxt构建的高仿饿了么(2018版)
  9. C++多线程的简单例子
  10. python爬虫模拟登陆校园网+连接校园wifi
  11. linux运行qt designer,用快速开发工具Qt Designer编写Qt程序
  12. 网站CDN加速是什么? 看完这篇你就明白了!
  13. 惠普服务器文档,惠普服务器详细整理参数
  14. 【小疯疯】百度云不限速下载
  15. python中用pip安装出现Microsoft Visual C++ 14.0 is required. Get it with Microsoft Visual C++ Build Tools
  16. python编程可以用来干嘛,python程序员是干嘛的
  17. tkinter-pack布局详解
  18. python自动战斗文字小游戏
  19. NDK--利用OpenSL ES实现播放FFmpeg解码后的音频流
  20. #E. 加加加树的边权

热门文章

  1. php前段时间戳转字符串,JavaScript_js获取时间并实现字符串和时间戳之间的转换,废话少说,直接上代码 复制 - phpStudy...
  2. SLAM_kitti数据集求相机cam2到IMU的变换矩阵
  3. 随手记_论文读写策略
  4. OpenGL学习笔记_简介_环境配置_创建一个窗口实例
  5. 轻量级网络模型之EfficientNet
  6. 安装vs2008之后系统好像变慢了?
  7. 个人作业--数组(续一)
  8. Delphi的TDataSetProvider、TDataSet、TAdoQuery、TDataSource、TDataModule控件的组合使用
  9. fetch body里数据为ReadableStream 解决办法
  10. 堆内存与栈内存能不能共享,不能,,通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的...