本文將介紹從零開始實現一個 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 快速修改下一個應該高亮的宮格,從而達到轉動的視覺效果函數
旋轉的速度其實就是 setTimeout 的interval間隔,interval越大速度越慢佈局
所以轉盤動畫的流暢程度實際取決於 setTimeout interval 值變化的連續性flex
手動模擬三個階段大致思路以下:動畫
下面的僞代碼:
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,也能夠直接使用。