-
2021-12-14 10:00:02
Compose 页面切换动画
前因后果
Compose
正式版已经发布了一个多月了,从Compose beta
版本发布之后各大网站中热度就一直不减,官方也一直在为开发者们推出学习Compose
的文章,更加说明了Android
开发的未来趋势。在之前我写了
Compose
版本的玩安卓,当然也有 MVVM 版本的,只是不同分支而已,这是 Github地址:https://github.com/zhujiang521/PlayAndroid但之前一直存在着一个问题,就是页面切换的时候没有动画,也不能说没有动画,可以通过
Crossfade
来实现两个页面之间的淡入淡出,但这就够了吗?完全不够啊!咱们的产品和UI第一个不答应,只是淡入淡出是绝对不行的!开始尝试
其实
Compose
中的Navigation
就是之前 Jetpack 中的Navigation
改的,所以之前的 api 还是存在的,然后用上试试呗!
navController.navigate(route) { anim { enter = R.anim.in_from_right exit = R.anim.out_to_left popEnter = R.anim.in_from_right popExit = R.anim.out_to_left } }
然后就有了上面的代码进行尝试,其实写的时候也想过不行,因为在
Compose
中动画有自己的一套实现方式,并不是像之前那样放在 anim 文件夹下的 xml 文件,但还是抱着试一试的态度,最后发现。。。果然不行。。。于是开始一顿乱找,后来发现在官方文档中已经写明了:
注意:
anim
块不能与 Navigation Compose 一起使用。系统会在此功能请求中跟踪 Navigation Compose 中的转换动画。然后就没有然后了,就开始等。。。。这一等就是好久。(其实这篇文章是之前写的,但一直没发)
终于在前段时间,这个问题有了眉目,Google 并没有将这个功能放到
Navigation
库中,而是重新创建了一个库:navigation-animation
,使用的时候同时引入即可进行使用。开始撸码
首先需要做的肯定是添加依赖:
现在应用级的 build.gradle 中添加:
repositories { mavenCentral() }
然后在 Module 级的 build.gradle 中添加:
// Navigation 动画 implementation "com.google.accompanist:accompanist-navigation-animation:$accompanist_version"
接下来需要做的是迁移之前写的
Navigation
的代码,先来看看之前的写法吧:@ExperimentalPagingApi @Composable fun NavGraph( startDestination: String = MainDestinations.HOME_PAGE_ROUTE ) { val navController = rememberNavController() val actions = remember(navController) { MainActions(navController) } NavHost( navController = navController, startDestination = startDestination ) { composable(MainDestinations.HOME_PAGE_ROUTE) { Home(actions) } } }
需要做的迁移有:
- 替换
rememberNavController()
为rememberAnimatedNavController()
- 替换
NavHost
为AnimatedNavHost
- 替换
import androidx.navigation.compose.navigation
为import com.google.accompanist.navigation.animation.navigation
- 替换
import androidx.navigation.compose.composable
为import com.google.accompanist.navigation.animation.composable
那就来吧:
@OptIn(ExperimentalAnimationApi::class, ExperimentalPagerApi::class) @Composable fun NavGraph( startDestination: String = PlayDestinations.HOME_PAGE_ROUTE, ) { val navController = rememberAnimatedNavController() val actions = remember(navController) { PlayActions(navController) } AnimatedNavHost( navController = navController, startDestination = startDestination ) { setComposable(PlayDestinations.HOME_PAGE_ROUTE) { WeatherViewPager( toCityList = actions.toCityList, toWeatherList = actions.toWeatherList ) } } }
下面就来看看如何使用这个库为页面之间切换添加动画吧:
@ExperimentalAnimationApi public fun NavGraphBuilder.navigation( startDestination: String, route: String, enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null, exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null, popEnterTransition: ( AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition? )? = enterTransition, popExitTransition: ( AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition? )? = exitTransition, builder: NavGraphBuilder.() -> Unit )
上面这段代码是
navigation-animation
库中的源码,可以看到除了之前Navigation
库中的一些参数外还多了几个参数用来设置动画,来具体看看吧:- **enterTransition:**在此 NavGraph 中定义目的地的输入转换动画
- **exitTransition:**在此 NavGraph 中为目的地定义退出转换动画
- **popEnterTransition:**在此 NavGraph 中定义目的地的弹出输入转换动画
- **popExitTransition:**在此 NavGraph 中为目的地定义弹出退出转换动画
再来看看具体使用吧:
composable( route = route, arguments = arguments, deepLinks = deepLinks, enterTransition = { expandVertically(animationSpec = tween(300)) }, exitTransition = { shrinkOut(animationSpec = tween(300)) }, popEnterTransition = { expandVertically(animationSpec = tween(300)) }, popExitTransition = { shrinkOut(animationSpec = tween(300)) }, content = content, )
OK,这就可以了。大家可以多种组合动画尝试下,可以实现各种你想要的动画。
仓促的结尾
本篇文章到这里就差不多了,但如果大家没有使用过
Compose
或者对之前使用的Navigation
并不熟悉的话可以先去看看我之前的文章,大家可以购买我的新书《Jetpack Compose:Android全新UI编程》进行阅读,里面有完整的 Compose 框架供大家学习。呸呸呸,太不要脸了,又在推荐自己的新书。。。
如果对你有帮助的话,别忘记点个 Star,感激不尽。
更多相关内容 - 替换
-
vue 的4种实现动画方式
2020-09-26 15:24:311. 合使用第三方 JavaScript 动画库,如 Velocity.js <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial...效果图如下:
1. 合使用第三方 JavaScript 动画库,如 Velocity.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <button @click="onOff">动画开关</button> <message v-show="isShow" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"> </script> <script> let message = { template: ` <transition name="fade" :css="false" @before-enter="beforeEnter" @enter="enter" @before-leave="beforeLeave" @leave="leave"> <div style="width:100px; height:100px; background: red"> 我显示了 </div> </transition> `, methods: { beforeEnter(el) { el.style.opacity = 0 }, enter(el, done) { Velocity(el, { opacity: 1 }, { duration: 500, complete: done }) }, beforeLeave(el) { el.style.opacity = 1 }, leave(el, done) { Velocity(el, { opacity: 0 }, { duration: 500, complete: done }) } } } let vm = new Vue({ el:'#app', data:{ isShow: false, //控制动画 true:显示 }, components:{ message }, methods:{ onOff(){ this.isShow = !this.isShow; } } }) </script> </body> </html>
2. 通过CSS 过渡和动画中自动应用 class
<style> /* 定义过度动画 v-enter:定义元素进入的初始状态 v-enter-active:定义元素进入过渡动画的 transition v-enter-to:定义元素进入动画结束时的状态 v-leave:定义元素离开的初始状态 v-leave-active:定义元素离开过渡动画的 transition v-leave-to:定义元素离开动画结束时的状态 .v-enter,.v-leave-to{ //元素进入前和离开结束时的状态 } .v-enter-to,.v-leave{ //这组css会在过渡结束后被移除,立即恢复,没有过渡动画 //所以为了保证有过渡,元素本身也需要有transition才能有过渡动画来恢复初始样式 } */ .fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to { opacity: 0; } </style> <script> Vue.component('message', { //Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡 // 使用transition组件应用过度动画 条件渲染 (使用 v-if ) 条件展示 (使用 v-show ) template: ` <transition name="fade"> ... </transition> `, }) </script>
3. 使用第三方 CSS 动画库,如 Animate.css
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css"> //使用 <transition enter-active-class="animated bounceIn" leave-active-class="animated bounceOut" > <div> Animate.css </div> ></transition> //动画查看: https://www.dowebok.com/demo/2014/98/
4. 在过渡钩子函数中使用 JavaScript 直接操作 DOM
JavaScript 钩子
可以在属性中声明 JavaScript 钩子,使用JS实现动画。
Vue.component('message', { template: ` <transition v-on:before-enter="beforeEnter" // 动画开始前,设置初始状态 v-on:enter="enter" // 执行动画 v-on:after-enter="afterEnter" // 动画结束,清理工作 v-on:enter-cancelled="enterCancelled" // 取消动画 v-on:before-leave="beforeLeave" v-on:leave="leave" v-on:after-leave="afterLeave" v-on:leave-cancelled="leaveCancelled" > <div> 动画 </div> </transition> `, methods: { //用js操作css 实现动画 beforeEnter(el) { el.style.opacity = 0 // 设置初始状态 }, enter(el, done) { document.body.offsetHeight; // 触发回流激活动画 el.style.opacity = 1 // 设置结束状态 } }, })
-
帧动画的多种实现方式与性能对比
2018-07-11 10:42:47前面我分享了《Web动画形式》,各种动画形式都可以制作出一种类型的动画,那就是帧动画,也叫序列帧动画,定格动画,逐帧动画等,这里我们统一用帧动画来表述,接下来我们就来看看帧动画有哪些打开方式吧。...
Web动画形式
首先我们来了解一下Web有哪些动画形式
1. CSS3动画 Transform(变形) Transition(过渡) Animation(动画) 2. JS动画(操作DOM、修改CSS属性值) 3. Canvas动画 4. SVG动画 5. 以Three.js为首的3D动画
以上各种动画形式都可以制作出一种类型的动画,那就是帧动画,也叫序列帧动画,定格动画,逐帧动画等,这里我们统一用帧动画来表述。
应用场景
帧动画一般用来实现稍微复杂一点的动画效果,同时希望动画更细腻,设计师更自由的发挥。他可以定义到每一个时间刻度上的展现内容,我们一般用帧动画来做页面的Loading,小人物,小物体元素的简单动画。我们想象中的帧动画应该有以下几个特点:
- 可以自由控制播放、暂停和停止
- 可以控制播放次数,播放速度
- 可以添加交互,在播放完成后添加事件
- 浏览器兼容性好
素材准备
帧动画的素材一般是先由设计师在PS中的时间轴上设计好了,然后导出图片给前端人员,PS制作时间轴动画一般是用来制作稍微简单的动画,操作简单,方便。
或者是由设计师在AE的时间轴进行设计,因为AE内置了更丰富的动作效果,比如转换,翻转之类的,AE可以帮助我们实现更复杂的效果,然后再导出图片给前端人员。
这里帧动画素材的要求,每一帧的图片最好是偶数宽高,偶数张,最好周围能有一些留白。
实现方案
将目前想到的解决方案梳理如下图,同时我们将对每种方案进行详细介绍。
一、GIF图
我们可以将上面制作的帧动画导出成GIF图,GIF图会连续播放,无法暂停,它往往用来实现小细节动画,成本较低、使用方便。但其缺点也是很明显的:
- 画质上,gif 支持颜色少(最大256色)、Alpha 透明度支持差,图像锯齿毛边比较严重;
- 交互上,不能直接控制播放、暂停、播放次数,灵活性差;
- 性能上,gif 会引起页面周期性的绘画,性能较差。
二、CSS3帧动画
CSS3帧动画是我们今天需要重点介绍的方案,最核心的是利用CSS3中Animation动画,确切的说是使用
animation-timing-function
的阶梯函数steps(number_of_steps, direction)
来实现逐帧动画的连续播放。帧动画的实现原理是不断切换视觉内图片内容,利用视觉滞留生理现象来实现连续播放的动画效果,下面我们来介绍制作CSS3帧动画的几种方案。
(1)连续切换动画图片地址src(不推荐)
我们将图片放到元素的背景中(
background-image
),通过更改background-image
的值实现帧的切换。但是这种方式会有以下几个缺点,所以该方案不推荐。- 多张图片会带来多个 HTTP 请求
- 每张图片首次加载会造成图片切换时的闪烁
- 不利于文件的管理
(2)连续切换雪碧图位置(推荐)
我们将所有的帧动画图片合并成一张雪碧图,通过改变
background-position
的值来实现动画帧切换。分两步进行:步骤一: 将动画帧合并为雪碧图,雪碧图的要求可以看上面素材准备,比如下面这张帧动画雪碧图,共20帧。
步骤二: 使用steps阶梯函数切换雪碧图位置
先看写法一:
<div class="sprite"></div> .sprite { width: 300px; height: 300px; background-repeat: no-repeat; background-image: url(frame.png); animation: frame 333ms steps(1,end) both infinite; } @keyframes frame { 0% {background-position: 0 0;} 5% {background-position: -300px 0;} 10% {background-position: -600px 0;} 15% {background-position: -900px 0;} 20% {background-position: -1200px 0;} 25% {background-position: -1500px 0;} 30% {background-position: -1800px 0;} 35% {background-position: -2100px 0;} 40% {background-position: -2400px 0;} 45% {background-position: -2700px 0;} 50% {background-position: -3000px 0;} 55% {background-position: -3300px 0;} 60% {background-position: -3600px 0;} 65% {background-position: -3900px 0;} 70% {background-position: -4200px 0;} 75% {background-position: -4500px 0;} 80% {background-position: -4800px 0;} 85% {background-position: -5100px 0;} 90% {background-position: -5400px 0;} 95% {background-position: -5700px 0;} 100% {background-position: -6000px 0;} }
针对以上动画有疑问?
问题一:既然都详细定义关键帧了,是不是可以不用steps函数了,直接定义linear变化不就好了吗?
animation: frame 10s linear both infinite;
如果我们定义成这样,动画是不会阶梯状,一步一步执行的,而是会连续的变化背景图位置,是移动的效果,而不是切换的效果,如下图:
问题二:不是应该设置为20步吗,怎么变成了1?
这里我们先来了解下
animation-timing-function
属性。CSS
animation-timing-function
属性定义CSS动画在每一动画周期中执行的节奏。对于关键帧动画来说,timing function作用于一个关键帧周期而非整个动画周期,即从关键帧开始开始,到关键帧结束结束。timing-function 作用于每两个关键帧之间,而不是整个动画。
接着我们来了解下steps() 函数:
- steps 函数指定了一个阶跃函数,它接受两个参数。
- 第一个参数接受一个整数值,表示两个关键帧之间分几步完成。
- 第二个参数有两个值< start > or < end >。默认值为< end > 。
- step-start 等同于 step(1, start)。step-end 等同于 step(1, end)。
综上我们可以知道,因为我们详细定义了一个关键帧周期,从开始到结束,每两个关键帧之间分 1 步展示完,也就是说0% ~ 5%之间变化一次,5% ~ 10%变化一次,所以我们这样写才能达到想要的效果。
再看写法二:
<div class="sprite"></div> .sprite { width: 300px; height: 300px; background-repeat: no-repeat; background-image: url(frame.png); animation: frame 333ms steps(20) both infinite; } @keyframes frame { 0% {background-position: 0 0;}//可省略 100% {background-position: -6000px 0;} }
这里我们定义了关键帧的开始和结束,也就是定义了一个关键帧周期,但因为我们没有详细的定义每一帧的展示,所以我们要将0%~100%这个区间分成20步来阶段性展示。
也可以换成关键字的写法,还可以只定义最后一帧,因为默认第一帧就是初始位置。
@keyframes frame { from {background-position: 0 0;}//可省略 to {background-position: -6000px 0;} }
(3)连续移动雪碧图位置(移动端推荐)
跟第二种基本一致,只是切换雪碧图的位置过程换成了
transform:translate3d()
来实现,不过要加多一层overflow: hidden;
的容器包裹,这里我们以只定义初始和结束帧为例,使用transform可以开启GPU加速,提高机器渲染效果,还能有效解决移动端帧动画抖动的问题。<div class="sprite-wp"> <div class="sprite"></div> </div> .sprite-wp { width: 300px; height: 300px; overflow: hidden; } .sprite { width: 6000px; height: 300px; will-change: transform; background: url(frame.png) no-repeat center; animation: frame 333ms steps(20) both infinite; } @keyframes frame { 0% {transform: translate3d(0,0,0);} 100% {transform: translate3d(-6000px,0,0);} }
三、JS帧动画
(1)通过JS来控制img的src属性切换(不推荐)
和上面CSS3帧动画里面切换元素
background-image
属性一样,会存在多个请求等问题,所以该方案我们不推荐,但是这是一种解决思路。(2)通过JS来控制Canvas图像绘制
通过Canvas制作帧动画的原理是用drawImage方法将图片绘制到Canvas上,不断擦除和重绘就能得到我们想要的效果。
<canvas id="canvas" width="300" height="300"></canvas> (function () { var timer = null, canvas = document.getElementById("canvas"), context = canvas.getContext('2d'), img = new Image(), width = 300, height = 300, k = 20, i = 0; img.src = "frame.png"; function drawImg() { context.clearRect(0, 0, width, height); i++; if (i == k) { i = 0; } context.drawImage(img, i * width, 0, width, height, 0, 0, width, height); window.requestAnimationFrame(drawImg); } img.onload = function () { window.requestAnimationFrame(drawImg); } })();
上面是通过改变裁剪图像的X坐标位置来实现动画效果的,也可以通过改变画布上放置图像的坐标位置实现,如下:
context.drawImage(img, 0, 0, width*k, height,-i*width,0,width*k,height);
。(3)通过JS来控制CSS属性值变化
这种方式和前面CSS3帧动画一样,有三种方式,一种是通过JS切换元素背景图片地址
background-image
,一种是通过JS切换元素背景图片定位background-position
,最后一种是通过JS移动元素transform:translate3d()
,第一种不做介绍,因为同样会存在多个请求等问题,不推荐使用,这里实现后面两种。- 切换元素背景图片位置
background-position
.sprite { width: 300px; height: 300px; background: url(frame.png) no-repeat 0 0; } <div class="sprite" id="sprite"></div> (function(){ var sprite = document.getElementById("sprite"), picWidth = 300, k = 20, i = 0, timer = null; // 重置背景图片位置 sprite.style = "background-position: 0 0"; // 改变背景图位置 function changePosition(){ sprite.style = "background-position: "+(-picWidth*i)+"px 0"; i++; if(i == k){ i = 0; } window.requestAnimationFrame(changePosition); } window.requestAnimationFrame(changePosition); })();
- 移动元素背景图片位置
transform:translate3d()
.sprite-wp { width: 300px; height: 300px; overflow: hidden; } .sprite { width: 6000px; height: 300px; will-change: transform; background: url(frame.png) no-repeat center; } <div class="sprite-wp"> <div class="sprite" id="sprite"></div> </div> (function () { var sprite = document.getElementById("sprite"), picWidth = 300, k = 20, i = 0, timer = null; // 重置背景图片位置 sprite.style = "transform: translate3d(0,0,0)"; // 改变背景图移动 function changePosition() { sprite.style = "transform: translate3d(" + (-picWidth * i) + "px,0,0)"; i++; if (i == k) { i = 0; } window.requestAnimationFrame(changePosition); } window.requestAnimationFrame(changePosition); })();
方案总结
总结以上几种方案,我们可以看到GIF图有一定的优点同时缺点和局限性也比较明显,所以这种方案看情况选择使用。
其他实现方案的性能如何呢,我们来比较一下,如果测试结果出现偏差,可能与测试环境变化有关。
测试环境:
系统:Windows 10 专业版 处理器:Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz 3.41GHz RAM: 8.00GB 浏览器:Chrome 72.0
CSS
transform:translate3d()
方案性能数据如上图,我们通过Chrome浏览器的各种工具,查看了每种方案的 FPS、CPU占用率、GPU占用、Scripting、Rendering、Painting、内存的使用情况,得到以下数据:
性能-方案 css background-position
css transform:translate3d()
JS Canvas JS background-position
JS transform:translate3d()
FPS 60 51 60 60 60 CPU 5%-6.2% 0.3%-1% 7%-8% 6%-8% 6%-8% GPU 3.8MB 4-10MB 0 3.8MB 4-11MB Scripting 0 0 2.51% 2.61% 3.18% Rendering 1.17% 0.141% 0.84% 1.65% 2.71% Painting 1.58% 0.01% 1.63% 1.75% 1.05% 内存 20112K 21120K 21588K 20756K 21576K 通过分析以上数据我们可以得出以下几点:
- 除了css
transform:translate3d()
方案,其他方案的FPS都能达到60FPS的流畅程度,但该方案的FPS也不是很低。 - CPU占用率最低的方案是 css
transform:translate3d()
方案。 - GPU占用最低的方案是 JS Canvas 绘制方案。
- CSS 方案没有脚本开销
- Rendering 最少的是 css
transform:translate3d()
方案。 - Painting 最少的是 css
transform:translate3d()
方案。 - 各方案内存占用区别不大。
结论:我们看到,在7个指标中,css
transform:translate3d()
方案将其中的4个指标做到了最低,从这点看,我们完全有理由选择这种方案来实现CSS帧动画。至于其他方案的绝对比较暂时没法给出结论,看具体情况来选择,也看开发者对哪个性能指标的追求。
延伸来看我们的Web动画,每种形式的动画都有其各自的有点,比如大量的粒子效果用Canvas绘制方案肯定要比DOM+CSS实现要好的,大量的CSS属性值变换,使用
transform
实现性能是要更好的。注意事项
素材:动画图片宽高最好是偶数,总帧数最好是偶数,图片拼接处最好有一定的留白。
适配:移动端适配最好不用rem,因为rem的计算会造成小数四舍五入,造成一定的抖动效果,建议直接用px作为单位,同时辅助以scale(zoom)媒体查询进行适配。如果使用rem适配,试试使用transform的方案,抖动问题可以得到优化解决。
对于帧与帧之间的盈亏互补现象导致动画抖动,想要了解更多,可以阅读《CSS技巧:逐帧动画抖动解决方案》。
tips:使用
will-change
可以在元素属性真正发生变化之前提前做好对应准备。总结
本文我们主要梳理了目前实现帧动画的几种方案,同时对各种方案进行效果实现,优劣讨论,性能对比,同时简单介绍了帧动画实现过程的注意事项,最后我们得出结论,css
transform:translate3d()
方案在实现和性能上都明显优于其他方案。参考来源:
-
动画:这一次用动画搞懂递归!
2020-03-23 18:53:55一个蛤蟆二条腿,两只蛤蟆四条腿 三只…,不对,应该是蛤蟆一次可能跳上一个台阶,也有可能一次跳两个台阶,那么 n 个台阶,有几种跳跃方式呢? 这个咋就和上边排队不一样了呢?嗯,不知道如何理解和下手。 其实这...递归这玩意儿,尤其是对于初学者,在数据结构和算法中归为“玄学”一类。咳咳,如果今天鹿哥能把这玄学的玩意儿讲明白,岂不是要上天。同样讲不明白的后果,鹿哥将会被后台的石锤大队石锤…
其实不学递归也没啥,但是学到后边会发现,什么 0-1 背包问题(动态规划内容,也可以用递归解决)、深度广度优先遍历、八皇后问题,回溯算法等,反正各种内容都会涉及到递归。所以,今天放句狠话,必须搞它,搞的它明明白白的。
(此篇调动全身的细胞去讲明白递归,虽然看起来很简单,但是给一个没有接触过递归的人讲懂很难)
什么是递归
对于玄学,绝不能用正常思维去搞它,那绝对是搞不它的。既然是玄学,那我们就采用玄学思维去理解和分析递归。
为啥叫它玄学?不是有多难,而是有些人,一看就明白,一用就是精髓。而有些人呢,比如我,一开始咋看咋觉得这东西就像是“黑洞”,递进去,就归不出来了。
划重点,递归,必须有递有归才叫递归。为了能够形象的代表一下,先了解一下递归的操作,小二,上动画。
从一个简单问题入手
要想搞懂递归,不能直接上手复杂的递归问题,而是先从一个简单的问题入手。
我们就拿排队打饭的例子,如果鹿哥要去餐厅,人太多,不得不去排队,但是此时我想知道自己是队伍中第几个,由于队伍太长,我看不到队伍的第一个人,自然而然没法一个个的数。
那么玄学递归排上用场了,我会让我前边的人依次问自己是当前第几个人,然后依次类推,直到问到第一个人,那么这个过程是“递”的过程。
第一个人不耐烦吼了一声,你眼X啊,没看到我是第一个人,正在打饭吗?
这个人吼了一声,这个就是所谓的终止条件,也就是递的终点。
此时,鹿哥肚子咕咕的叫了两声,当然,我现在还不知道自己是队伍第几个。此时队伍中的第二个人知道位置了,然后告诉第三个人,第三个人告诉第四个人…
最后,我前一个人告诉他目前在队伍中是第几个,那么鹿哥就知道自己此时的位置。很容易理解,这个过程就是“归”的过程。
遇到的问题
由此看来,鹿哥的道德素质没的说,从来不插队,而且还能理解递归,一个字“骚”,两个字“真骚”。
这时候有人要说了,这个过程俺早就理解了,但是对于有些递归问题和代码,脑子里总是想不明白层层嵌套的过程,而且总是想着想着就递进去,归不出来了,哎,我这脑子,是不是很笨呢。
鹿哥告诉大家,其实不用担心,凡事都要有个过程,要有个思想转化的过程,要用心去感受,感受它的抚摸,感受它带给你的快感。
这时候,很多人的告诉你的解决办法是,不要去想这个过程,要去寻找终止条件和递推公式,然后按照套路写出代码。
嗯~ 对~,鹿哥认为上边确实说的没错,但是这类玄学玩家并没有搞明白为什么初学者往往去想这个过程,进入这个过程圈套呢?
鹿哥想了一想,结合自己学习递归的经验,越是想不明白,就越想弄明白递归过程,这不叫刨根问底,这叫“骚气风发”。
假如我们搞明白一次递归的整个过程,那么以后遇到其他的递归就无需去想这个过程了,因为其他的递归几乎一样的套路,我们心理上接受之后,假装自己知道了所有的递归过程,以后再遇递归,不再去想这个过程,而是直接按照公式套用不就 OK 了吗?
鹿哥,你说起来容易,你能用什么方法让我们这些刚接触过递归的人明白这个递归过程。其实这件事困扰了鹿哥好久,作为一个为被称为写文风骚,动画风骚的鹿哥,今天斗胆尝试一下,看懂了就给我疯狂在底部点赞,把鹿哥按到地上摩擦,这才能凸显出鹿哥的 Sao 气。
用动画理解递归
继续拿排队那个例子,我们已经知道了终止条件和层与层之间的关系。
终止条件为:f(1) = 1,也就是第一个人知道自己的位置的。然后层与层之间的关系为 + 1 的关系,也就是说求第 n 个人在队伍中的位置 f(n),那么就问一下前一个人,也就是 n-1 在队伍当中的位置 f(n-1) 加上 1,也就是 f(n-1) + 1。
有点绕口,咱们看完整的实现代码:
如果你还是有点懵逼的话,还是不能理解整个程序的运作,小二,上图解和动画。
上边的动画是我们人脑的理解,而下边的动画才是程序真正的调用过程。每个颜色的方块代表的是该每一层递归的函数,而程序是自上而下执行滴,也就是下边这种形式。
如果你理解函数的执行过程就是入栈过程的话,其实递归就是不断入栈的过程,然后最里层的函数执行完毕,然后函数依次出栈。
如果你寥寥草草的阅读完毕,并没有感觉看懂递归过程,这可真不怪你们鹿哥了,你鹿哥已经尽力用动画去还原程序递归过程和大脑的递归过程了。
光说不练假把式
如果上边你能够吃透,那么下面的那些题型以及以后的文章所提到的各种有关递归的过程,真的不在话下。有时候只是换了一个角度理解,换了一个角度理解,换了一个角度理解,鹿哥重要的 Sao 话说三遍。
再来一个叫什么斐波那契数列的那家伙,杠杠滴。找规律 0、1、1、2、3、5、8、13、21… 没错,除了第一第二个,剩余的数据都是前两个数据之和,那么求第 n 个数据是多少?
其实这个问题对你来说已经小 K 死了,就是换了个模样,前两个数不就是换了一个问法,也就是上边排队的时候,鹿哥前边两个人嘛?一个叫 n-1 一个叫 n-2,终止条件变成两个窗口,同时给第一个人和第二个人打饭,也就是 f(1) = 0,f(2) = 1。
直接出结果,代码如下:
我会变形,专门误导你
其实相同的递归公式确有不同的理解方式,这你敢信?难道这就是递归归为玄学的地方,不信你看,下面说来就来了。
一个蛤蟆二条腿,两只蛤蟆四条腿 三只…,不对,应该是蛤蟆一次可能跳上一个台阶,也有可能一次跳两个台阶,那么 n 个台阶,有几种跳跃方式呢?
这个咋就和上边排队不一样了呢?嗯,不知道如何理解和下手。
其实这个数蛤蟆腿,不对,这个蛤蟆跳台阶的问题只不过换了一种思考方式,我们来看这种思想是如何绕晕我们的。
一个台阶,只存在一种可能,那就是让蛤蟆跳一次。二个台阶,有两种可能,那就是让蛤蟆一次只跳一个台阶,或者一次性挑两个台阶。那么三个台阶,你自己数数去吧,我就不啰嗦了。
那么 n 个台阶有几次可能,这样一个个数太费劲,我们换种思想,也就是递归的思想。我们将蛤蟆跳台阶分为 m 次跳上去。第一次跳,有两种方式,那就是要么一次性跳一个台阶,剩余的 n-1 个台阶我先不管。或者第一次跳两个台阶,剩余的 n-2 个台阶我暂时不管。
如果 f(n) = m,也就是当前有多少次跳法。我想把上边的两种相加,剩余的暂时先不管,也就是 f(n) = f(n-1) + f(n-2)。
那么神奇的一刻就要来了,n 个台阶,我决定了第一次只跳一个台阶之后,那剩余 n-1 个台阶的每个台阶就会面临同样的选择。如果我决定了第一次只跳 2 个台阶之后,那么剩余的 n-2 个台阶的每个台阶也会面临同样的选择。
蛤蟆的的第一次就这样交在鹿哥手中,要么你第一次跳一个,要么跳两个,想一步登天吃天鹅肉,没门。剩余的 f(n-1) 和 f(n-2) 面临同样的选择呀。
只要把 f(n - 1) + f(n - 2) 相加,就是 n 个台阶有几种跳法了。我知道,大部分初学者还是不理解这个思想,图解来一波。
思考总结
你会发现蛤蟆跳台阶的问题,得出来的递推公式竟然和斐波那契数列的递推公式竟然相同,但是终止条件不同的。
这时你必须像鹿哥一样陷入玄学递归的大思考,同样的一个递推公式,竟然有两种思想,感觉这世界还有这么 TM 神奇的事情,这两者让我陷入了对人生的大思考…
今天的递归基础理解内容就到这里了,后边还会继续分享更复杂的递归,虽然表面上假装让它复杂,但是就是换汤不换药,比如深度广度优先遍历,八皇后问题,回溯算法等,都是和今天分享的八九不离十,最重要的是多去思考和感悟以及总结,这个过程值的你去做。
骚气的鹿哥写文不易啊,不要手下留赞,因为这将决定下一篇你鹿哥输出的动力,点了赞你就是鹿哥的人,鹿哥今后罩着你。
1、老铁们,关注我的原创微信公众号 「 小鹿动画学编程」,专注于数据结构和算法,网络原理,计算机基础、Web 大前端等,欢迎来鹿哥公众号搞事情。一天一篇动画家属技术搞定你。。2、给俺点个赞呗,可以让更多的人看到这篇文章,顺便激励下我,就这么脸皮厚!
❤️ 不要忘记留下你学习的脚印 [点赞 + 收藏 + 评论]
文章写了了好几个小时,不妨点赞支持一下。嘻嘻,你不点赞说明你很自私,你怕那么好的文章让别人也看到。开个小小玩笑。
-
IOS中的几种动画的实现方式
2016-04-11 00:49:45在我们开发中,经常会遇到一些需要动画特效的展示,下面来总结一些开发中常见的动画实现方式 第一,序列帧动画,通过大量的UIImage来展示动画效果,网络请求等待加载动画效果 核心代码: //创建可变数组,存放UIImage对象 ... -
Android的三种动画详解(帧动画、View动画、属性动画)
2019-06-21 22:54:03Android的动画分为了三种, 分别是 帧动画、View动画、属性动画。 1、帧动画 帧动画就是顺序播放一组预先定义好的图片,就类似于我们观看视频,就是一张一张的图片连续播放。 帧动画的使用很简单,总共就两个步骤... -
Android实现动画组合的四种方式
2018-12-30 15:33:25先上动图,本文介绍实现下面动图中组合动画的四种方式 方式一:视图动画之AnimationSet xml方式实现 &lt;?xml version="1.0" encoding="utf-8"?&gt; &... -
动画:Flex布局 | 别再用传统方式进行网页布局了(上)
2019-11-24 18:54:21作为一个前端初学者,在学习的时候可能会用到一些盒模型传统的布局方式(display + position + float),很多小伙伴用着用着就觉得这种传统布局写一些样式比较麻烦、不方便,比如垂直居中的传统实... -
5种暂停动画的方式,你都会?
2021-08-20 15:28:10前言 最近公司网站上线新的官网,上面飘着...当然暂停动画不止这一种方案,其他的方案我们也会在本文讨论。 JavaScript 暂停动画 在 JavaScript 中,该属性是animationPlayState。 使用语法 element.style.animationPla -
【前端动画】实现动画的6种方式
2017-11-21 10:01:30通常在前端中,实现动画的方案主要有6种: javascript直接实现; SVG(可伸缩矢量图形); CSS3 transition; CSS3 animation; Canvas动画; requestAnimationFrame; javascript 直接实现动画 其主要思想... -
unity之spine骨骼动画使用
2021-11-09 11:47:12spine官网下载Unity插件包:spine官网 打开Unity工程 ...双击后unity开始准备资源包 准备完成后会弹出一个是否确认导入该包的提示,点击Import: unity将会开始导包。 导包前工程目录如下: 导包后 ... -
Android Activity切换动画多种实现方式与封装
2016-09-12 19:17:29关于Activity动画那些事关于activity的动画,相信大家再熟悉不过了,我们开发中经常用到,网上资料也很多,但是也有一些小细节需要我们注意,今天这篇文章我总结了几种常用的动画实现方式,通过这篇文章,你可以了解... -
Vue学习之旅Part7:三种Vue的动画实现方式及列表的动画效果
2020-04-20 17:36:51在Vue中 有三种动画的实现方式 分别是: 使用过渡类名实现 使用第三方animate.css实现 使用钩子函数实现 下面将一一介绍: 1、使用过渡类名实现 首先 来了解一下动画的过渡类名: ( 这是我在Vue官网找到的一个... -
Android 动画实现方式以及对比(GIF和WebP,Lottie和SVGA,原生动画)
2019-12-17 11:34:04最近需求涉及到较为复杂的动画实现,虽然后来完成与今天所要讲的毫无关系(实现方式会在下篇讲)。但是也是由此萌生了我对市面上较为流行动画的实现方式产生了兴趣,到底是谁好用呢?是谁更优秀呢? GIF和WebP GIF... -
Jetpack Compose动画
2022-03-10 15:01:48前面讲到布局基础和图像绘制,本篇来讲下Jetpack Compose动画。 介绍动画主要从下图中几点进行讲解 一、内容动画 与布局内容变化相关的几种动画,官方称之为高级别动画API。 AnimatedVisibility,实验性功能,可... -
swiper的animation动画实现方式
2019-02-18 15:06:02关注小编微信公众号公众号【前端基础教程从0开始】回复“1”,拉你进程序员技术讨论群,群内有大神,可以免费提供问题解答。公众号回复“小程序”,领取300个优秀的小程序开源代码+一套入门教程。公众号回复“领取... -
HTML5实现动画三种方式
2017-09-08 12:41:34HTML5实现动画三种方式 -
HTML实现动画
2020-06-19 17:17:43很多前端新入门的同学都不太懂如何去实现一个简单的动画,比如轮播图效果等,当初我也是,初学者还不知道有组件库这东西,什么效果都是自己去写,又不懂得如何写,只能去找,有不知道有哪些方法,如 animation 和 ... -
Android属性动画完全解析(上),初识属性动画的基本用法
2015-04-09 09:56:15在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系统在一开始的时候就给我们提供了两种实现动画效果的方式,逐帧动画(frame-by-frame animation)和补间动画(tweened animation)。逐帧动画的工作原理很... -
用 JavaScript 实现时间轴与动画 - 前端组件化
2021-04-05 09:06:09我们一起来先实现一个动画库,一起来看一看我们应该如何去抽象这些开发组件所需要的底层能力。 -
Unity动画系统详解3:如何播放、切换动画?
2020-05-31 19:37:49大智:“小新,还记得Unity的动画来源有哪些么?” 小新:“有Unity中制作和外部导入两种,哦对!还可以用代码写动画,不过我不会,嘿嘿” 大智:“没错,前两天我们学习的其实主要是Animation Clip的内容,也就是一... -
5分钟实现漂亮的CSS加载动画,纯CSS实现加载动画
2020-12-27 21:52:383 动画简写方式 动画简写方式 /* animation: 动画名称 持续时间 运动曲线 何时开始 播放次数 是否反方向 起始与结束状态 */ animation: name duration timing-function delay iteration-count direction fill-mode ... -
HarmonyOS之帧动画、数值动画、属性动画和组合动画的效果实现
2021-06-27 21:47:02动画是组件的基础特性之一,精心设计的动画使UI变化更直观,有助于改进应用程序的外观并改善用户体验。 Java UI 框架提供了帧动画、数值动画和属性动画,并提供了将多个动画同时操作的动画集合。 二、帧动画 帧... -
vue中实现动画效果--三种方式
2020-06-12 20:14:56一、普通动画实现 效果 用一个按钮, 控制一个 div 的淡入和淡出 步骤 使用 js 控制 div 的 class name 点击 function anime() { var boxDom = document.querySelector('#box'); if (boxDom.className == "show") ... -
iOS之深入探究动画渲染降帧
2021-10-16 20:50:40所以给动画降帧,实际上是一种用体验换性能的决策,在动画不复杂但是数量很多的情况下(比如一些弹幕动画、点赞动画),给动画降帧并不会影响动画效果,此时降帧就能累计节约大量的 GPU 性能。 二、动画渲染对性能 -
安卓项目实战之设置Activity跳转动画的5种实现方式
2018-11-11 12:03:45在介绍activity的切换动画之前我们先来说明一下实现切换activity的两种方式: 1,调用startActivity方法启动一个新的Activity并跳转其页面 2,调用finish方法销毁当前的Activity返回上一个Activity界面 当调用... -
Github项目解析(九)-->实现Activity跳转动画的五种方式
2016-07-08 23:15:36文本中我们将讲解activity切换动画相关的知识点,这里的切换动画指的是是activity跳转时的...这里总结了一下,有五种方式实现activity切换时实现动画效果。下面我将依次介绍一下每种实现activity切换动画效果的实现方式 -
7 Babylonjs基础入门 动画
2019-03-06 00:33:26设置场景中的动画主要有两种方式。第一种是方式是定义一组对象,并在每一个对象上面定义模型的情况。第二种方法更复杂的动画,就是在动画运行时修改。 基本动画 我们需要使用Babylon.js封装的Animation类去实现动画... -
用 JavaScript 实现三次贝塞尔动画库 - 前端组件化
2021-04-05 10:31:57这期我们来完善上一期的动画库。在 Animation 类中的 constructor 的参数,我们发现其他的参数都用上了。但是 timingFunction 我们是还没有使用上的。这里我们就来一起处理这个问题。