UNPKG

@quick-tv/react

Version:

QuickTV react framework

360 lines (321 loc) 11.6 kB
/* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { HippyEventEmitter, HippyEventRevoker } from '../event'; import { Bridge, Device } from '../native'; import { warn } from '../utils'; import { repeatCountDict } from '../utils/animation'; import { colorParse } from '../color'; import { Platform } from '../types'; type AnimationValue = number | { animationId: number} | string; type AnimationCallback = () => void; type AnimationDirection = 'left' | 'right' | 'top' | 'bottom' | 'center'; interface AnimationOptions { /** * Initial value at `Animation` start */ startValue: AnimationValue; /** * End value when `Animation` end. */ toValue: AnimationValue; /** * Animation execution time */ duration: number; /** * Timeline mode of animation */ mode?: 'timing'; // TODO: fill more options /** * Delay starting time */ delay?: number; /** * Value type, leave it blank in most case, except use rotate/color related * animation, set it to be 'deg' or 'color'. */ valueType?: 'deg' | 'rad' | 'color'; // TODO: fill more options /** * Animation start position */ direction?: AnimationDirection; /** * Animation interpolation type */ timingFunction?: 'linear' | 'ease' | 'bezier' | 'in' | 'ease-in' | 'out' | 'ease-out' | 'inOut' | 'ease-in-out' | (string & {}); /** * Animation repeat times, use 'loop' to be always repeating. */ repeatCount?: number | 'loop'; inputRange?: any[]; outputRange?: any[]; } interface Animation extends AnimationOptions { animationId: number; onAnimationStartCallback?: AnimationCallback; onAnimationEndCallback?: AnimationCallback; onAnimationCancelCallback?: AnimationCallback; onAnimationRepeatCallback?: AnimationCallback; animationStartListener?: HippyEventRevoker; animationEndListener?: HippyEventRevoker; animationCancelListener?: HippyEventRevoker; animationRepeatListener?: HippyEventRevoker; // Fallback event handlers onRNfqbAnimationStart?: Function; onRNfqbAnimationEnd?: Function; onRNfqbAnimationCancel?: Function; onRNfqbAnimationRepeat?: Function; onHippyAnimationStart?: Function; onHippyAnimationEnd?: Function; onHippyAnimationCancel?: Function; onHippyAnimationRepeat?: Function; } const AnimationEventEmitter = new HippyEventEmitter(); /** * parse value of special value type * @param valueType * @param originalValue */ function parseValue(valueType: string | undefined, originalValue: number | string) { if (valueType === 'color' && ['number', 'string'].indexOf(typeof originalValue) >= 0) { return colorParse(originalValue); } return originalValue; } /** * Better performance of Animation solution. * * It pushes the animation scheme to native at once. */ class Animation implements Animation { public constructor(config: AnimationOptions) { let startValue: AnimationValue = 0; if (config.startValue?.constructor && config.startValue.constructor.name === 'Animation') { startValue = { animationId: (config.startValue as Animation).animationId }; } else { const { startValue: tempStartValue } = config; startValue = parseValue(config.valueType, tempStartValue as number|string); } const toValue = parseValue(config.valueType, config.toValue as number|string); this.mode = config.mode || 'timing'; this.delay = config.delay || 0; this.startValue = startValue || 0; this.toValue = toValue || 0; this.valueType = config.valueType || undefined; this.duration = config.duration || 0; this.direction = config.direction || 'center'; this.timingFunction = config.timingFunction || 'linear'; this.repeatCount = repeatCountDict(config.repeatCount || 0); this.inputRange = config.inputRange || []; this.outputRange = config.outputRange || []; this.animationId = Bridge.callNativeWithCallbackId('AnimationModule', 'createAnimation', true, this.mode, Object.assign({ delay: this.delay, startValue: this.startValue, toValue: this.toValue, duration: this.duration, direction: this.direction, timingFunction: this.timingFunction, repeatCount: this.repeatCount, inputRange: this.inputRange, outputRange: this.outputRange, }, (this.valueType ? { valueType: this.valueType } : {}))); this.destroy = this.destroy.bind(this); // TODO: Deprecated compatible, will remove soon. this.onRNfqbAnimationStart = this.onAnimationStart.bind(this); this.onRNfqbAnimationEnd = this.onAnimationEnd.bind(this); this.onRNfqbAnimationCancel = this.onAnimationCancel.bind(this); this.onRNfqbAnimationRepeat = this.onAnimationRepeat.bind(this); this.onHippyAnimationStart = this.onAnimationStart.bind(this); this.onHippyAnimationEnd = this.onAnimationEnd.bind(this); this.onHippyAnimationCancel = this.onAnimationCancel.bind(this); this.onHippyAnimationRepeat = this.onAnimationRepeat.bind(this); } /** * Remove all of animation event listener */ public removeEventListener() { if (this.animationStartListener) { this.animationStartListener.remove(); } if (this.animationEndListener) { this.animationEndListener.remove(); } if (this.animationCancelListener) { this.animationCancelListener.remove(); } if (this.animationRepeatListener) { this.animationRepeatListener.remove(); } } /** * Start animation execution */ public start() { this.removeEventListener(); // Set as iOS default let animationEventName = 'onAnimation'; // If running in Android, change it. if (__PLATFORM__ === Platform.android || Device.platform.OS === Platform.android) { animationEventName = 'onHippyAnimation'; } if (typeof this.onAnimationStartCallback === 'function') { this.animationStartListener = AnimationEventEmitter.addListener(`${animationEventName}Start`, (animationId) => { if (animationId === this.animationId) { (this.animationStartListener as HippyEventRevoker).remove(); if (typeof this.onAnimationStartCallback === 'function') { this.onAnimationStartCallback(); } } }); } if (typeof this.onAnimationEndCallback === 'function') { this.animationEndListener = AnimationEventEmitter.addListener(`${animationEventName}End`, (animationId) => { if (animationId === this.animationId) { (this.animationEndListener as HippyEventRevoker).remove(); if (typeof this.onAnimationEndCallback === 'function') { this.onAnimationEndCallback(); } } }); } if (typeof this.onAnimationCancelCallback === 'function') { this.animationCancelListener = AnimationEventEmitter.addListener(`${animationEventName}Cancel`, (animationId) => { if (animationId === this.animationId) { (this.animationCancelListener as HippyEventRevoker).remove(); if (typeof this.onAnimationCancelCallback === 'function') { this.onAnimationCancelCallback(); } } }); } if (typeof this.onAnimationRepeatCallback === 'function') { this.animationRepeatListener = AnimationEventEmitter.addListener(`${animationEventName}Repeat`, (animationId) => { if (animationId === this.animationId) { if (typeof this.onAnimationRepeatCallback === 'function') { this.onAnimationRepeatCallback(); } } }); } Bridge.callNative('AnimationModule', 'startAnimation', this.animationId); } /** * Use destroy() to destroy animation. */ public destory() { warn('Animation.destory() method will be deprecated soon, please use Animation.destroy() as soon as possible'); this.destroy(); } /** * Destroy the animation */ public destroy() { this.removeEventListener(); Bridge.callNative('AnimationModule', 'destroyAnimation', this.animationId); } /** * Pause the running animation */ public pause() { Bridge.callNative('AnimationModule', 'pauseAnimation', this.animationId); } /** * Resume execution of paused animation */ public resume() { Bridge.callNative('AnimationModule', 'resumeAnimation', this.animationId); } /** * Update to new animation scheme * * @param {Object} newConfig - new animation schema */ public updateAnimation(newConfig: Partial<AnimationOptions>) { if (typeof newConfig !== 'object') { throw new TypeError('Invalid arguments'); } if (typeof newConfig.mode === 'string' && newConfig.mode !== this.mode) { throw new TypeError('Update animation mode not supported'); } (Object.keys(newConfig) as (keyof AnimationOptions)[]).forEach((prop) => { const value = newConfig[prop]; if (prop === 'startValue') { let startValue: AnimationValue = 0; if (newConfig.startValue instanceof Animation) { startValue = { animationId: newConfig.startValue.animationId }; } else { const { startValue: tempStartValue } = newConfig; startValue = parseValue(this.valueType, tempStartValue as number|string); } this.startValue = startValue || 0; } else if (prop === 'repeatCount') { this.repeatCount = repeatCountDict(newConfig.repeatCount || 0); } else { Object.defineProperty(this, prop, { value, }); } }); Bridge.callNative('AnimationModule', 'updateAnimation', this.animationId, Object.assign({ delay: this.delay, startValue: this.startValue, toValue: parseValue(this.valueType, this.toValue as number|string), duration: this.duration, direction: this.direction, timingFunction: this.timingFunction, repeatCount: this.repeatCount, inputRange: this.inputRange, outputRange: this.outputRange, }, (this.valueType ? { valueType: this.valueType } : {}))); } /** * Call when animation started. * @param {Function} cb - callback when animation started. */ public onAnimationStart(cb: AnimationCallback) { this.onAnimationStartCallback = cb; } /** * Call when animation is ended. * @param {Function} cb - callback when animation started. */ public onAnimationEnd(cb: AnimationCallback) { this.onAnimationEndCallback = cb; } /** * Call when animation is canceled. * @param {Function} cb - callback when animation started. */ public onAnimationCancel(cb: AnimationCallback) { this.onAnimationCancelCallback = cb; } /** * Call when animation is repeated. * @param {Function} cb - callback when animation started. */ public onAnimationRepeat(cb: AnimationCallback) { this.onAnimationRepeatCallback = cb; } } export default Animation; export { Animation, AnimationCallback, };