本文將介紹從零開始實現一個 React Native 版本的九宮格抽獎轉盤,先看最終效果圖html
也能夠直接使用react-native-super-lottery組件開發抽獎功能。react
佈局很簡單,咱們能夠採用flex 3行佈局,也能夠單行、配合flex-wrap子控件自動折行實現。直接上代碼git
const LotteryStyle = StyleSheet.create({ container: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', } }); const img_width = 100; // 圖片的寬度 const img_height = 80; // 圖片的高度 // 觀察上圖的轉盤,會發現存在三種類型的宮格 // 當前被高亮的宮格(增長了蒙層效果) , 正常的功能,以及正中心能夠被點擊的宮格 // 在真實的狀況裏,就須要根據 item、 index、 highLightIndex 等參數處理不一樣的宮格效果 // 具體能夠參考本文最後開源的抽獎組件 function renderItem(item, index, highLightIndex) { const { url } = item; return <Image style={{ width, height }} source={{ uri: url }}/>; } <View style={LotteryStyle.container}> { data.map((item, index) => { return renderItem(item); }) } </View> 複製代碼
接下來重點介紹如何實現轉盤動畫效果,仔細觀察會發現整個轉盤動畫能夠分爲三個階段:github
九宮格的加速和衰減曲線決定了轉盤動畫的流暢程度。後端
實現原理:react-native
轉盤的轉動,能夠採用 setTimeout 快速修改下一個應該高亮的宮格,從而達到轉動的視覺效果markdown
旋轉的速度其實就是 setTimeout 的interval間隔,interval越大速度越慢函數
所以轉盤動畫的流暢程度實際取決於 setTimeout interval 值變化的連續性oop
手動模擬三個階段大致思路以下:佈局
下面的僞代碼:
function startLottery() { this.setState({ highLightIndex: currentIndex }, () => { this.currentIndex += 1; if (this.currentIndex > CYCLE_TIMES + 8 + this.uniformTimes && this.luckyOrder === currentOrder) { clearTimeout(this.lotteryTimer); // 完成抽獎,展現獎品彈窗等 } else { if (this.currentIndex < CYCLE_TIMES) { // CYCLE_TIMES = 30 次, 每次速度遞加 10ms, this.speed -= 10; } else if (this.currentIndex > CYCLE_TIMES + 8 + this.uniformTimes && this.luckyOrder === currentOrder + 1) { // 中獎前一次降速 80 急停效果 this.speed += 80; } else if(this.luckyOrder) { // 後端爲返回結果是勻速旋轉 this.uniformTimes += 1; else { this.speed += 20; } // 確保速度不能低於 50 ms if (this.speed < 50) { this.speed = 50; } this.lotteryTimer = setTimeout(this.startLottery, this.speed); } } ); } 複製代碼
Tween.js是一個JavaScript的動畫補間庫,容許你以平滑的方式更改對象的屬性或者某一個特殊的值。你只需告訴它什麼屬性要更改,當補間結束運行時它們應該具備哪些最終值,須要進過多長時間或者多少次數,補間引擎將負責計算從起始點到結束點任意時間點應該返回的值。
聽起來正是咱們想要的效果,咱們能夠以下定義加速、勻速、減速三個動畫效果:
function animate(): void{ TWEEN.update(); } // 轉盤加速階段 : interval 通過20次 從初始值 100(啓動速度) 下降到 40 (轉盤最高速度) // interval 變化曲線爲 TWEEN.Easing.Quadratic.In // 關於 Tween.js 各類曲線數值變化能夠參考這裏 https://sole.github.io/tween.js/examples/03_graphs.html const speedUpTween = new TWEEN.Tween({ interval: 100 }) .to({ interval: 40 }, 20) .easing(TWEEN.Easing.Quadratic.In) .onUpdate((object) => { // onUpdate 每次數值變化的回調, 能夠拿到本次 interval 的值, 而後 setTimeout 開始動畫 setTimeout(() => { setHighLightIndex(highIndex + 1); animate(); }, object.interval); currentSpeed = object.interval; }) // 加速階段完畢進入勻速階段 .onComplete(() => { speedUniformAnimate(); }) .start(); // 勻速運動 function speedUniformAnimate(): void{ setTimeout(() => { // 若是沒有拿到抽獎結果 轉盤繼續勻速轉動 if (lotteryResult) { setHighLightIndex(highIndex + 1); speedUniformAnimate(); } else { // 勻速階段完畢 開始降速 speedDownTween.start(); } }, currentSpeed); } // 降速階段 從當前速度 currentSpeed 實際是 40 通過 8次衰減 降爲 500, // interval 變化曲線爲 TWEEN.Easing.Quadratic.Out const speedDownTween = new TWEEN.Tween({ interval: currentSpeed }) .to({ interval: 500 }, 8) .easing(TWEEN.Easing.Quadratic.Out) .onUpdate((object) => { setTimeout(() => { setHighLightIndex(highIndex + 1); animate(); }, object.interval); }); 複製代碼
上述Demo有兩個問題:
由於 Tween動畫 必需要指定目標值,但在降速階段有可能通過8次減速 最終中止的位置並非咱們中獎宮格的位置。固然這個問題咱們能夠反向經過控制降速階段的開始時間:在勻速運動階段,只有拿到抽獎結果而且距離中獎位置還有8格的時候纔開始降速
第二個問題是 若是轉盤須要支持屢次抽獎 speedUpTween、 speedDownTween 等預約義動畫須要從新初始化,官方文檔裏並無找到相似reset的方法,暫時只能從新生成一遍。
開源的組件react-native-supper-lottery目前採用的是方案一。這裏提供一個方案二模擬三階段的Demo感興趣的小夥伴能夠仿照實現一版基於Tween.js的動畫。
完成了佈局和動畫等核心功能,以後的封裝組件提供start、stop等抽獎函數就很簡單了,這裏再也不詳述,詳細代碼能夠參考組件react-native-supper-lottery,也能夠直接使用。