开发指导—利用组件&插值器动画实现HarmonyOS动效
一. 组件动画
在组件上创建和运行动画的快捷方式。具体用法请参考通用方法。
获取动画对象
通过调用animate方法获得animation对象,animation对象支持动画属性、动画方法和动画事件。
<!-- xxx.hml --> <div class="container"> <div id="content" class="box" onclick="Show"></div> </div>
/* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; } .box{ width: 200px; height: 200px; background-color: #ff0000; margin-top: 30px; }
/* xxx.js */ export default { data: { animation: '', }, onInit() { }, onShow() { var options = { duration: 1500, }; var frames = [ { width:200,height:200, }, { width:300,height:300, } ]; this.animation = this.$element('content').animate(frames, options); //获取动画对象 }, Show() { this.animation.play(); } }

说明
● 使用animate方法时必须传入Keyframes和Options参数。
● 多次调用animate方法时,采用replace策略,即最后一次调用时传入的参数生效。
设置动画参数
在获取动画对象后,通过设置参数Keyframes设置动画在组件上的样式。
<!-- xxx.hml --> <div class="container"> <div id="content" class="box" onclick="Show"></div> </div>
/* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; } .box{ width: 200px; height: 200px; #ff0000; margin-top: 30px; }
/* xxx.js */ export default { data: { animation: '', keyframes:{}, options:{} }, onInit() { this.options = { duration: 4000, } this.keyframes = [ { transform: { translate: '-120px -0px', scale: 1, rotate: 0 }, transformOrigin: '100px 100px', offset: 0.0, width: 200, height: 200 }, { transform: { translate: '120px 0px', scale: 1.5, rotate: 90 }, transformOrigin: '100px 100px', offset: 1.0, width: 300, height: 300 } ] }, Show() { this.animation = this.$element('content').animate(this.keyframes, this.options) this.animation.play() } }

说明
● translate、scale和rtotate的先后顺序会影响动画效果。
● transformOrigin只对scale和rtotate起作用。
在获取动画对象后,通过设置参数Options来设置动画的属性。
<!-- xxx.hml --> <div class="container"> <div id="content" class="box" onclick="Show"></div> </div>
/* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; } .box{ width: 200px; height: 200px; background-color: #ff0000; margin-top: 30px; }
/* xxx.js */ export default { data: { animation: '', }, onInit() { }, onShow() { var options = { duration: 1500, easing: 'ease-in', delay: 5, iterations: 2, direction: 'normal', }; var frames = [ { transform: { translate: '-150px -0px' } }, { transform: { translate: '150px 0px' } } ]; this.animation = this.$element('content').animate(frames, options); }, Show() { this.animation.play(); } }

说明
direction:指定动画的播放模式。
normal: 动画正向循环播放。
reverse: 动画反向循环播放。
alternate:动画交替循环播放,奇数次正向播放,偶数次反向播放。
alternate-reverse:动画反向交替循环播放,奇数次反向播放,偶数次正向播放。
二. 插值器动画
动画动效
通过设置插值器来实现动画效果。(从API Version 6 开始支持。)
创建动画对象
通过createAnimator创建一个动画对象,通过设置参数options来设置动画的属性。
<!-- xxx.hml --> <div class="container"> <div style="width: 300px;height: 300px;margin-top: 100px;background: linear-gradient(pink, purple);transform: translate({{translateVal}});"> </div> <div class="row"> <button type="capsule" value="play" onclick="playAnimation"></button> </div> </div>
/* xxx.css */ .container { width:100%; height:100%; flex-direction: column; align-items: center; justify-content: center; } button{ width: 200px; } .row{ width: 65%; height: 100px; align-items: center; justify-content: space-between; margin-top: 50px; margin-left: 260px; }
// xxx.js import animator from '@ohos.animator'; export default { data: { translateVal: 0, animation: null }, onInit() {}, onShow(){ var options = { duration: 3000, easing:"friction", delay:"1000", fill: 'forwards', direction:'alternate', iterations: 2, begin: 0, end: 180 };//设置参数 this.animation = animator.createAnimator(options)//创建动画 }, playAnimation() { var _this = this; this.animation.onframe = function(value) { _this.translateVal= value }; this.animation.play(); } }
说明
● 使用createAnimator创建动画对象时必须传入options参数。
● begin插值起点,不设置时默认为0。
● end插值终点,不设置时默认为1。
添加动画事件和调用接口
animator支持事件和接口,可以通过添加frame、cancel、repeat、finish事件和调用update、play、pause、cancel、reverse、finish接口自定义动画效果。animator支持的事件和接口具体见动画中的createAnimator。
<!-- xxx.hml --> <div style="flex-direction: column;align-items: center;width: 100%;height: 100%;"> <div style="width:200px;height: 200px;margin-top: 100px;background: linear-gradient(#b30d29, #dcac1b); transform: scale({{scaleVal}});"></div> <div style="width: {{DivWidth}};height: {{DivHeight}};margin-top: 200px; background: linear-gradient(pink, purple);margin-top: 200px;transform:translateY({{translateVal}});"> </div> <div class="row"> <button type="capsule" value="play" onclick="playAnimation"></button> <button type="capsule" value="update" onclick="updateAnimation"></button> </div> <div class="row1"> <button type="capsule" value="pause" onclick="pauseAnimation"></button> <button type="capsule" value="finish" onclick="finishAnimation"></button> </div> <div class="row2"> <button type="capsule" value="cancel" onclick="cancelAnimation"></button> <button type="capsule" value="reverse" onclick="reverseAnimation"></button> </div> </div>
/* xxx.css */ button{ width: 200px; } .row{ width: 65%; height: 100px; align-items: center; justify-content: space-between; margin-top: 150px; position: fixed; top: 52%; left: 120px; } .row1{ width: 65%; height: 100px; align-items: center; justify-content: space-between; margin-top: 120px; position: fixed; top: 65%; left: 120px; } .row2{ width: 65%; height: 100px; align-items: center; justify-content: space-between; margin-top: 100px; position: fixed; top: 75%; left: 120px; }
// xxx.js import animator from '@ohos.animator'; import promptAction from '@ohos.promptAction'; export default { data: { scaleVal:1, DivWidth:200, DivHeight:200, translateVal:0, animation: null }, onInit() { var options = { duration: 3000, fill: 'forwards', begin: 200, end: 270 }; this.animation = animator.createAnimator(options); }, onShow() { var _this= this; //添加动画重放事件 this.animation.onrepeat = function() { promptAction.showToast({ message: 'repeat' }); var repeatoptions = { duration: 2000, iterations: 1, direction: 'alternate', begin: 180, end: 240 }; _this.animation.update(repeatoptions); _this.animation.play(); }; }, playAnimation() { var _this= this; //添加动画逐帧插值回调事件 this.animation.onframe = function(value) { _this. scaleVal= value/150, _this.DivWidth = value, _this.DivHeight = value, _this.translateVal = value-180 }; this.animation.play(); }, updateAnimation() { var newoptions = { duration: 5000, iterations: 2, begin: 120, end: 180 }; this.animation.update(newoptions); this.animation.play();//调用动画播放接口 }, pauseAnimation() { this.animation.pause();//调用动画暂停接口 }, finishAnimation() { var _this= this; //添加动画完成事件 this.animation.onfinish = function() { promptAction.showToast({ message: 'finish' }) }; this.animation.finish(); //调用动画完成接口 }, cancelAnimation(){ this.animation.cancel();//调用动画取消接口 }, reverseAnimation(){ this.animation.reverse();//调用动画倒放接口 } }
说明
在调用update接口的过程中可以使用这个接口更新动画参数,入参与createAnimator一致。
动画帧
请求动画帧
请求动画帧时通过requestAnimationFrame函数逐帧回调,在调用该函数时传入一个回调函数。
runframe在调用requestAnimationFrame时传入带有timestamp参数的回调函数step,将step中的timestamp赋予起始的startTime。当timestamp与startTime的差值小于规定的时间时将再次调用requestAnimationFrame,最终动画将会停止。
<!-- xxx.hml --> <div class="container"> <tabs onchange="changecontent"> <tab-content> <div class="container"> <stack style="width: 300px;height: 300px;margin-top: 100px;margin-bottom: 100px;"> <canvas id="mycanvas" style="width: 100%;height: 100%;coral;"> </canvas> <div style="width: 50px;height: 50px;border-radius: 25px;indigo;position: absolute;left: {{left}};top: {{top}};"> </div> </stack> <button type="capsule" value="play" onclick="runframe"></button> </div> </tab-content> </tabs> </div>
/* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; } button{ width: 300px; }
// xxx.js export default { data: { timer: null, left: 0, top: 0, flag: true, animation: null, startTime: 0, }, onShow() { var test = this.$element("mycanvas"); var ctx = test.getContext("2d"); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(300, 300); ctx.lineWidth = 5; ctx.strokeStyle = "red"; ctx.stroke(); }, runframe() { this.left = 0; this.top = 0; this.flag = true; this.animation = requestAnimationFrame(this.step); }, step(timestamp) { if (this.flag) { this.left += 5; this.top += 5; if (this.startTime == 0) { this.startTime = timestamp; } var elapsed = timestamp - this.startTime; if (elapsed < 500) { console.log('callback step timestamp: ' + timestamp); this.animation = requestAnimationFrame(this.step); } } else { this.left -= 5; this.top -= 5; this.animation = requestAnimationFrame(this.step); } if (this.left == 250 || this.left == 0) { this.flag = !this.flag } }, onDestroy() { cancelAnimationFrame(this.animation); } }
说明
requestAnimationFrame函数在调用回调函数时在第一个参数位置传入timestamp时间戳,表示requestAnimationFrame开始去执行回调函数的时刻。
取消动画帧
通过cancelAnimationFrame函数取消逐帧回调,在调用cancelAnimationFrame函数时取消requestAnimationFrame函数的请求。
<!-- xxx.hml --> <div class="container"> <tabs onchange="changecontent"> <tab-content> <div class="container"> <stack style="width: 300px;height: 300px;margin-top: 100px;margin-bottom: 100px;"> <canvas id="mycanvas" style="width: 100%;height: 100%;coral;"> </canvas> <div style="width: 50px;height: 50px;border-radius: 25px;indigo;position: absolute;left: {{left}};top: {{top}};"> </div> </stack> <button type="capsule" value="play" onclick="runframe"></button> </div> </tab-content> </tabs> </div>
/* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; } button{ width: 300px; }
// xxx.js export default { data: { timer: null, left: 0, top: 0, flag: true, animation: null }, onShow() { var test = this.$element("mycanvas"); var ctx = test.getContext("2d"); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(300, 300); ctx.lineWidth = 5; ctx.strokeStyle = "red"; ctx.stroke(); }, runframe() { this.left = 0; this.top = 0; this.flag = true; this.animation = requestAnimationFrame(this.step); }, step(timestamp) { if (this.flag) { this.left += 5; this.top += 5; this.animation = requestAnimationFrame(this.step); } else { this.left -= 5; this.top -= 5; this.animation = requestAnimationFrame(this.step); } if (this.left == 250 || this.left == 0) { this.flag = !this.flag } }, onDestroy() { cancelAnimationFrame(this.animation); } }
说明
在调用该函数时需传入一个具有标识id的参数。