| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- import 'dart:async';
- import '../../../resource/assets.gen.dart';
- import 'package:clean/utils/expand.dart';
- import 'package:flutter/Material.dart';
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- class InfinitePreviewPageView extends StatefulWidget {
- final List<Widget> items;
- final double height;
- final Duration autoPlayDuration;
- final bool autoPlay;
- const InfinitePreviewPageView({
- super.key,
- required this.items,
- this.height = 98,
- this.autoPlayDuration = const Duration(seconds: 3),
- this.autoPlay = true,
- });
- @override
- State<InfinitePreviewPageView> createState() => _InfinitePreviewPageViewState();
- }
- class _InfinitePreviewPageViewState extends State<InfinitePreviewPageView> {
- late final PageController _pageController;
- Timer? _timer;
- double _currentPage = 0;
- bool _isInitialized = false;
- // 实际页面索引
- int get _actualPage => _currentPage.round() % widget.items.length;
- @override
- void initState() {
- super.initState();
- // 设置初始页面
- final initialPage = widget.items.length * 1000;
- _currentPage = initialPage.toDouble(); // 设置初始currentPage
- _pageController = PageController(
- viewportFraction: 0.8,
- initialPage: initialPage,
- );
- _pageController.addListener(() {
- setState(() {
- _currentPage = _pageController.page ?? initialPage.toDouble();
- });
- });
- // 延迟一帧设置初始化完成
- WidgetsBinding.instance.addPostFrameCallback((_) {
- setState(() {
- _isInitialized = true;
- });
- });
- if (widget.autoPlay) {
- _startAutoPlay();
- }
- }
- void _startAutoPlay() {
- _timer?.cancel();
- _timer = Timer.periodic(widget.autoPlayDuration, (timer) {
- if (widget.items.isNotEmpty) {
- _pageController.nextPage(
- duration: const Duration(milliseconds: 300),
- curve: Curves.easeOut,
- );
- }
- });
- }
- @override
- void dispose() {
- _pageController.dispose();
- _timer?.cancel();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- if (widget.items.isEmpty) return const SizedBox();
- return SizedBox(
- height: widget.height,
- child: PageView.builder(
- controller: _pageController,
- itemBuilder: (context, index) {
- final itemIndex = index % widget.items.length;
- // 如果还没初始化完成,不应用缩放效果
- if (!_isInitialized) {
- return Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8),
- child: Container(
- decoration: BoxDecoration(
- color: const Color(0xFF1E1E1E),
- borderRadius: BorderRadius.circular(16),
- ),
- child: widget.items[itemIndex],
- ),
- );
- }
- // 计算缩放和透明度
- double diffScale = (_currentPage - index).abs();
- double scale = 1 - (diffScale * 0.1).clamp(0.0, 0.3);
- double opacity = 1 - (diffScale * 0.3).clamp(0.0, 0.5);
- return Transform.scale(
- scale: scale,
- child: Opacity(
- opacity: opacity,
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 0),
- child: Container(
- decoration: BoxDecoration(
- color: const Color(0xFF1E1E1E),
- borderRadius: BorderRadius.circular(23.r),
- ),
- child: widget.items[itemIndex],
- ),
- ),
- ),
- );
- },
- ),
- );
- }
- }
- // 单个项目组件
- class PreviewItem extends StatelessWidget {
- final String title;
- final Image icon;
- const PreviewItem({
- super.key,
- required this.title,
- required this.icon
- });
- @override
- Widget build(BuildContext context) {
- return Stack(
- alignment: Alignment.topLeft,
- children: [
- Container(
- padding: EdgeInsets.only(left: 16.w, right: 16.w),
- decoration: BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.centerLeft,
- end: Alignment.centerRight,
- colors: [
- '#36363D'.color,
- '#18181F'.color,
- ],
- ),
- borderRadius: BorderRadius.circular(23.r),
- ),
- child: Row(
- children: [
- Container(
- width: 56.w,
- height: 56.w,
- child: icon,
- ),
- SizedBox(width: 10.w),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(
- title,
- style: TextStyle(
- color: Colors.white,
- fontSize: 15.sp,
- fontWeight: FontWeight.w500,
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- Container(
- padding: EdgeInsets.symmetric(vertical: 2.h, horizontal: 15.w),
- decoration: BoxDecoration(
- color: '#383850'.color,
- borderRadius: BorderRadius.only(topLeft: Radius.circular(100), bottomRight: Radius.circular(100)),
- ),
- child: ShaderMask(
- shaderCallback: (Rect bounds) {
- return LinearGradient(
- begin: Alignment.centerLeft,
- end: Alignment.centerRight,
- colors: [
- '#ffdd55'.color,
- '#ffe89d'.color,
- ],
- ).createShader(bounds);
- },
- child: Text(
- "VIP",
- style: TextStyle(
- color: Colors.white,
- fontSize: 12.sp,
- fontWeight: FontWeight.w700
- )
- ),
- ),
- )
- ],
- );
- }
- }
|