func_page_view.dart 5.1 KB

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