寫在前面
用Flutter實現一個簡單的九宮格抽獎
步驟
- 實現一個九宮格的頁面
- 實現一個控制器用於控制抽獎開始
- 實現抽獎的動畫
過程
定義數據類型
class SimpleLotteryValue {
SimpleLotteryValue(
{this.target = 0, this.isFinish = false, this.isPlaying = false});
/// 中獎目標
int target = 0;
bool isPlaying = false;
bool isFinish = false;
SimpleLotteryValue copyWith({
int target = 0,
bool isPlaying = false,
bool isFinish = false,
}) {
return SimpleLotteryValue(
target: target, isFinish: isFinish, isPlaying: isPlaying);
}
@override
String toString() {
return "target : $target , isPlaying : $isPlaying , isFinish : $isFinish";
}
}
定義控制器
class SimpleLotteryController extends ValueNotifier<SimpleLotteryValue> {
SimpleLotteryController() : super(SimpleLotteryValue());
/// 開啓抽獎
///
/// [target] 中獎目標
void start(int target) {
// 九宮格抽獎裏範圍爲0~8
assert(target >= 0 && target <= 8);
if (value.isPlaying) {
return;
}
value = value.copyWith(target: target, isPlaying: true);
}
void finish() {
value = value.copyWith(isFinish: true);
}
}
實現九宮格頁面
class SimpleLotteryWidget extends StatefulWidget {
final SimpleLotteryController controller;
final Function onPress;
SimpleLotteryWidget({Key key, @required this.controller, this.onPress});
@override
_SimpleLotteryWidgetState createState() => _SimpleLotteryWidgetState();
}
class _SimpleLotteryWidgetState extends State<SimpleLotteryWidget> {
@override
Widget build(BuildContext context) {
return Container(
width: 220,
height: 220,
child: GridView.builder(
itemCount: 9,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 4, mainAxisSpacing: 4),
itemBuilder: (context, index) {
if (index != 4) {
return Container(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
width: 50,
height: 50,
color: Colors.amber,
),
],
),
);
}
return GestureDetector(
onTap: () => widget.onPress(),
child: Container(
color: Colors.red,
),
);
}));
}
}
實現抽獎動畫
Animation<int> _selectedIndexTween;
AnimationController _startAnimateController;
int _currentSelect = -1;
int _target = 0;
/// 旋轉的圈數
final int repeatRound = 3;
VoidCallback _listener;
_SimpleLotteryWidgetState() {
_listener = () {
// 開啓抽獎動畫
if (widget.controller.value.isPlaying) {
_startAnimateController.reset();
_target = widget.controller.value.target;
_selectedIndexTween = _initSelectIndexTween(_target);
_startAnimateController.forward();
}
};
}
/// 選中下標的映射
/// 因爲動畫的數值爲0~8的增長,而宮格的實際下標順序是0,3,6,7,8,5,2,1,因此做了映射處理
final Map<int, int> _selectMap = {
0: 0,
1: 3,
2: 6,
3: 7,
4: 8,
5: 5,
6: 2,
7: 1
};
@override
void initState() {
_startAnimateController =
AnimationController(vsync: this, duration: Duration(seconds: 5));
_selectedIndexTween = _initSelectIndexTween(_target);
// 動畫監聽
_startAnimateController.addListener(() {
// 更新選中的下標
_currentSelect = _selectMap[_selectedIndexTween.value % 8];
if (_startAnimateController.isCompleted) {
widget.controller.finish();
}
setState(() {});
});
// 控制監聽
widget.controller.addListener(_listener);
super.initState();
}
完整代碼
class SimpleLotteryWidget extends StatefulWidget {
final SimpleLotteryController controller;
final Function onPress;
SimpleLotteryWidget({Key key, @required this.controller, this.onPress});
@override
_SimpleLotteryWidgetState createState() => _SimpleLotteryWidgetState();
}
class _SimpleLotteryWidgetState extends State<SimpleLotteryWidget>
with TickerProviderStateMixin {
Animation<int> _selectedIndexTween;
AnimationController _startAnimateController;
int _currentSelect = -1;
int _target = 0;
/// 旋轉的圈數
final int repeatRound = 3;
VoidCallback _listener;
/// 選中下標的映射
final Map<int, int> _selectMap = {
0: 0,
1: 3,
2: 6,
3: 7,
4: 8,
5: 5,
6: 2,
7: 1
};
_SimpleLotteryWidgetState() {
_listener = () {
// 開啓抽獎動畫
if (widget.controller.value.isPlaying) {
_startAnimateController.reset();
_target = widget.controller.value.target;
_selectedIndexTween = _initSelectIndexTween(_target);
_startAnimateController.forward();
}
};
}
/// 初始化tween
///
/// [target] 中獎的目標
Animation _initSelectIndexTween(int target) =>
StepTween(begin: 0, end: repeatRound * 8 + target).animate(
CurvedAnimation(
parent: _startAnimateController, curve: Curves.easeOutQuart));
@override
void initState() {
_startAnimateController =
AnimationController(vsync: this, duration: Duration(seconds: 5));
_selectedIndexTween = _initSelectIndexTween(_target);
// 動畫監聽
_startAnimateController.addListener(() {
// 更新選中的下標
_currentSelect = _selectMap[_selectedIndexTween.value % 8];
if (_startAnimateController.isCompleted) {
widget.controller.finish();
}
setState(() {});
});
// 控制監聽
widget.controller.addListener(_listener);
super.initState();
}
@override
void deactivate() {
widget.controller.removeListener(_listener);
super.deactivate();
}
@override
void dispose() {
_startAnimateController.dispose();
widget.controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
width: 220,
height: 220,
child: GridView.builder(
itemCount: 9,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 4, mainAxisSpacing: 4),
itemBuilder: (context, index) {
if (index != 4) {
return Container(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
width: 50,
height: 50,
color: index == _currentSelect
? Colors.blue
: Colors.amber,
),
],
),
);
}
return GestureDetector(
onTap: () => widget.onPress(),
child: Container(
color: Colors.red,
),
);
}));
}
}