前言:
之前一直看到 有手机充电的时候 有气泡从Type-C 的位置冒泡上来 慢慢上移, 然后和上面的圆圈 会和,感觉还是挺好看的。今天试了下用 Flutter 实现了一版本。大致效果如下,而且气泡 和 气泡直接还可以粘黏
实现原理:
大致的布局就是这样的: Stack 包裹住所有的元素,需要位置移动的是 AnimatedBuilder,这里是把他们独立出来,方便随机的时候打散处理。
代码实现:
- PageBubble.dart 整个页面 气泡的粘连效果 有点吃性能
import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:untitled1a/pages/example1/bubble_dot.dart'; class PageBubble extends StatefulWidget { const PageBubble({Key? key}) : super(key: key); @override State<PageBubble> createState() => _PageBubbleState(); } class _PageBubbleState extends State<PageBubble> with SingleTickerProviderStateMixin { late AnimationController _animationController; final Random random = Random(); @override void initState() { _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 2500), ); _animationController.repeat(); super.initState(); } @override void dispose() { _animationController.dispose(); // TODO: implement dispose super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text('充电气泡'), ), body: Align( alignment: Alignment(0.0, 1), child: Container( height: MediaQuery.of(context).size.height / 3 * 2, width: 250, // color: Colors.blue, child: Stack( alignment: Alignment.topCenter, children: [ ...buildAnimatedPositioned(), Padding( padding: const EdgeInsets.only( top: 40, left: 0, ), child: RotationTransition( alignment: Alignment.center, turns: _animationController, child: Container( width: 200, height: 200, decoration: const BoxDecoration( color: Colors.deepPurple, borderRadius: BorderRadius.only( topRight: Radius.circular(70), topLeft: Radius.circular(90), bottomRight: Radius.circular(60), bottomLeft: Radius.circular(80), ), ), ), ), ), //这个效果很有意思 就是有费性能 不需要可以移除掉 BackdropFilter( filter: ImageFilter.dilate(radiusX: 3.0, radiusY: 3.0), child: Container(), ), Positioned( left: 35, top: 45, child: Container( width: 180, height: 180, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(90), ), child: const Center( child: Text( '89%', style: TextStyle( fontSize: 40, color: Colors.white, ), ), ), ), ), ], ), ), ), ); } int getRandomNumber(int min, int max) { var random = Random(); return min + random.nextInt(max - min + 1); } List<Widget> buildAnimatedPositioned() { List<Widget> _list = []; List.generate( 9, (index) => { _list.add(BubbleDot(time: getRandomNumber(2000, 3500))), }); return _list; } }
- BubbleDot.dart 气泡效果 之所以把气泡单独出来是为了后面的随机打散操作
import 'dart:math'; import 'package:flutter/material.dart'; class BubbleDot extends StatefulWidget { final int time; const BubbleDot({super.key, required this.time}); @override State<BubbleDot> createState() => _BubbleDotState(); } class _BubbleDotState extends State<BubbleDot> with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation<double> _animation; final Random random = Random(); late double _leftPos = 0; late double _dotWidth = 0; @override void initState() { _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: widget.time), ); _animation = _animationController.drive( Tween<double>(begin: getRandomNumber(660, 800).toDouble(), end: 100)); //_leftPos = random.nextDouble() * 200; _leftPos = getRandomNumber(35, 180).toDouble(); _dotWidth = getRandomNumber(30, 66).toDouble(); _animationController.addStatusListener( (AnimationStatus status) => {print('status $status')}); _animationController.repeat(); // TODO: implement initState super.initState(); } @override void dispose() { // TODO: implement dispose _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (BuildContext context, Widget? child) { return Positioned( top: _animation.value, //top: 240, left: _leftPos, child: ClipOval( child: Container( width: _dotWidth, height: _dotWidth, decoration: BoxDecoration( color: Colors.deepPurple, boxShadow: [ BoxShadow( color: Colors.deepPurple.withOpacity(0.5), spreadRadius: 6, blurRadius: 8, offset: Offset(4, 4), // changes position of shadow ), ], ), ), ), ); }, ); } int getRandomNumber(int min, int max) { return min + random.nextInt(max - min + 1); } }