func_page_view.dart 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import 'dart:async';
  2. import '../../../resource/assets.gen.dart';
  3. import 'package:clean/utils/expand.dart';
  4. import 'package:flutter/Material.dart';
  5. import 'package:flutter_screenutil/flutter_screenutil.dart';
  6. class InfinitePreviewPageView extends StatefulWidget {
  7. final List<Widget> items;
  8. final double height;
  9. final Duration autoPlayDuration;
  10. final bool autoPlay;
  11. const InfinitePreviewPageView({
  12. super.key,
  13. required this.items,
  14. this.height = 98,
  15. this.autoPlayDuration = const Duration(seconds: 3),
  16. this.autoPlay = true,
  17. });
  18. @override
  19. State<InfinitePreviewPageView> createState() => _InfinitePreviewPageViewState();
  20. }
  21. class _InfinitePreviewPageViewState extends State<InfinitePreviewPageView> {
  22. late final PageController _pageController;
  23. Timer? _timer;
  24. double _currentPage = 0;
  25. bool _isInitialized = false;
  26. // 实际页面索引
  27. int get _actualPage => _currentPage.round() % widget.items.length;
  28. @override
  29. void initState() {
  30. super.initState();
  31. // 设置初始页面
  32. final initialPage = widget.items.length * 1000;
  33. _currentPage = initialPage.toDouble(); // 设置初始currentPage
  34. _pageController = PageController(
  35. viewportFraction: 0.8,
  36. initialPage: initialPage,
  37. );
  38. _pageController.addListener(() {
  39. setState(() {
  40. _currentPage = _pageController.page ?? initialPage.toDouble();
  41. });
  42. });
  43. // 延迟一帧设置初始化完成
  44. WidgetsBinding.instance.addPostFrameCallback((_) {
  45. setState(() {
  46. _isInitialized = true;
  47. });
  48. });
  49. if (widget.autoPlay) {
  50. _startAutoPlay();
  51. }
  52. }
  53. void _startAutoPlay() {
  54. _timer?.cancel();
  55. _timer = Timer.periodic(widget.autoPlayDuration, (timer) {
  56. if (widget.items.isNotEmpty) {
  57. _pageController.nextPage(
  58. duration: const Duration(milliseconds: 300),
  59. curve: Curves.easeOut,
  60. );
  61. }
  62. });
  63. }
  64. @override
  65. void dispose() {
  66. _pageController.dispose();
  67. _timer?.cancel();
  68. super.dispose();
  69. }
  70. @override
  71. Widget build(BuildContext context) {
  72. if (widget.items.isEmpty) return const SizedBox();
  73. return SizedBox(
  74. height: widget.height,
  75. child: PageView.builder(
  76. controller: _pageController,
  77. itemBuilder: (context, index) {
  78. final itemIndex = index % widget.items.length;
  79. // 如果还没初始化完成,不应用缩放效果
  80. if (!_isInitialized) {
  81. return Padding(
  82. padding: const EdgeInsets.symmetric(horizontal: 8),
  83. child: Container(
  84. decoration: BoxDecoration(
  85. color: const Color(0xFF1E1E1E),
  86. borderRadius: BorderRadius.circular(16),
  87. ),
  88. child: widget.items[itemIndex],
  89. ),
  90. );
  91. }
  92. // 计算缩放和透明度
  93. double diffScale = (_currentPage - index).abs();
  94. double scale = 1 - (diffScale * 0.1).clamp(0.0, 0.3);
  95. double opacity = 1 - (diffScale * 0.3).clamp(0.0, 0.5);
  96. return Transform.scale(
  97. scale: scale,
  98. child: Opacity(
  99. opacity: opacity,
  100. child: Padding(
  101. padding: const EdgeInsets.symmetric(horizontal: 0),
  102. child: Container(
  103. decoration: BoxDecoration(
  104. color: const Color(0xFF1E1E1E),
  105. borderRadius: BorderRadius.circular(23.r),
  106. ),
  107. child: widget.items[itemIndex],
  108. ),
  109. ),
  110. ),
  111. );
  112. },
  113. ),
  114. );
  115. }
  116. }
  117. // 单个项目组件
  118. class PreviewItem extends StatelessWidget {
  119. final String title;
  120. final Image icon;
  121. const PreviewItem({
  122. super.key,
  123. required this.title,
  124. required this.icon
  125. });
  126. @override
  127. Widget build(BuildContext context) {
  128. return Stack(
  129. alignment: Alignment.topLeft,
  130. children: [
  131. Container(
  132. padding: EdgeInsets.only(left: 16.w, right: 16.w),
  133. decoration: BoxDecoration(
  134. gradient: LinearGradient(
  135. begin: Alignment.centerLeft,
  136. end: Alignment.centerRight,
  137. colors: [
  138. '#36363D'.color,
  139. '#18181F'.color,
  140. ],
  141. ),
  142. borderRadius: BorderRadius.circular(23.r),
  143. ),
  144. child: Row(
  145. children: [
  146. Container(
  147. width: 56.w,
  148. height: 56.w,
  149. child: icon,
  150. ),
  151. SizedBox(width: 10.w),
  152. Expanded(
  153. child: Column(
  154. crossAxisAlignment: CrossAxisAlignment.start,
  155. mainAxisAlignment: MainAxisAlignment.center,
  156. children: [
  157. Text(
  158. title,
  159. style: TextStyle(
  160. color: Colors.white,
  161. fontSize: 15.sp,
  162. fontWeight: FontWeight.w500,
  163. ),
  164. ),
  165. ],
  166. ),
  167. ),
  168. ],
  169. ),
  170. ),
  171. Container(
  172. padding: EdgeInsets.symmetric(vertical: 2.h, horizontal: 15.w),
  173. decoration: BoxDecoration(
  174. color: '#383850'.color,
  175. borderRadius: BorderRadius.only(topLeft: Radius.circular(100), bottomRight: Radius.circular(100)),
  176. ),
  177. child: ShaderMask(
  178. shaderCallback: (Rect bounds) {
  179. return LinearGradient(
  180. begin: Alignment.centerLeft,
  181. end: Alignment.centerRight,
  182. colors: [
  183. '#ffdd55'.color,
  184. '#ffe89d'.color,
  185. ],
  186. ).createShader(bounds);
  187. },
  188. child: Text(
  189. "VIP",
  190. style: TextStyle(
  191. color: Colors.white,
  192. fontSize: 12.sp,
  193. fontWeight: FontWeight.w700
  194. )
  195. ),
  196. ),
  197. )
  198. ],
  199. );
  200. }
  201. }