import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hgdj/assets_tool/app_colors.dart'; import 'package:hgdj/assets_tool/images.dart'; import 'package:hgdj/hj_utils/screen.dart'; import '../../hj_model/splash/ads_model.dart'; import '../../routers/jump_router.dart'; import '../../tools_base/banner/ads_banner_widget.dart'; import '../../tools_base/banner/ads_item.dart'; import '../../tools_base/widget/net_image_widget.dart'; /// 启动页广告:3 秒倒计时后右上角按钮变为"关闭",点击触发 callback 跳首页 class SplashAdView extends StatefulWidget { final List? adData; final Function? callback; const SplashAdView({ super.key, this.adData, this.callback, }); @override State createState() => _SplashAdViewState(); } class _SplashAdViewState extends State { int count = 3; Timer? timer; @override void initState() { super.initState(); timer = Timer.periodic(const Duration(seconds: 1), (t) { count--; setState(() {}); if (count <= 0) t.cancel(); }); } @override void dispose() { timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Stack( fit: StackFit.expand, children: [ _buildAdContent(), SafeArea( child: Align( alignment: Alignment.topRight, child: Padding( padding: const EdgeInsets.only(top: 28, right: 17), child: _buildCountdownButton(), ), ), ), ], ); } /// 三种展示形态:1 张走单图点击跳转,多张走轮播组件,无广告则显示默认启动图 Widget _buildAdContent() { final dataLen = widget.adData?.length ?? 0; if (dataLen == 1) { final ad = widget.adData!.first; return GestureDetector( onTap: () => pushToPageByLink(ad.href ?? ""), child: Container( alignment: Alignment.center, child: NetworkImageLoader( imageUrl: ad.cover ?? "", width: screen.screenWidth, height: screen.screenHeight, fit: BoxFit.cover, placeHolderWidget: _splashBg(), ), ), ); } if (dataLen > 1) { return Container( color: Color(0xff151515), child: SplashBannerWidget(adData: widget.adData ?? []), ); } return _splashBg(); } Widget _splashBg() => Image.asset( 'ic_splash_bg.webp'.launchPath, fit: BoxFit.fill, width: Get.width, height: Get.height, ); Widget _buildCountdownButton() { return GestureDetector( // 倒计时未结束时点击无效,避免用户误触跳过广告 onTap: () { if (count <= 0) widget.callback?.call(); }, child: Container( height: 36, width: 77, alignment: Alignment.center, decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(18), ), child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (child, anim) { // 倒计时递减:新数字从上滑入、旧数字向下滑出(下翻效果) final isIncoming = child.key == ValueKey(count); final begin = isIncoming ? const Offset(0, -1) : const Offset(0, 1); return ClipRect( child: SlideTransition( position: Tween(begin: begin, end: Offset.zero).animate(anim), child: child, ), ); }, child: count > 0 ? Text( count.toString(), key: ValueKey(count), style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600, ), ) : const Text( "关闭", key: ValueKey(0), style: TextStyle(color: Colors.white, fontSize: 16), ), ), ), ); } } /// 启动页多广告轮播:2.5 秒自动切换,底部圆点指示器,左右双向无限滑 class SplashBannerWidget extends StatefulWidget { final List adData; const SplashBannerWidget({ super.key, required this.adData, }); @override State createState() => _SplashBannerWidgetState(); } class _SplashBannerWidgetState extends State { late final PageController _pageCtr; Timer? _timer; List get _ads => widget.adData; int get _len => _ads.length; @override void initState() { super.initState(); _pageCtr = PageController(initialPage: 0); _pageCtr.addListener(_onPageScroll); // 延迟启动自动轮播,避免和入场动画冲突;mounted 检查防止 dispose 后启动 timer if (_len > 1) { Future.delayed(const Duration(seconds: 1), () { if (!mounted) return; _timer = Timer.periodic(const Duration(milliseconds: 2500), _onTimer); }); } } @override void dispose() { _timer?.cancel(); _pageCtr.removeListener(_onPageScroll); _pageCtr.dispose(); super.dispose(); } void _onPageScroll() { if (mounted) setState(() {}); } void _onTimer(Timer timer) { final page = _pageCtr.page?.toInt() ?? 0; _pageCtr.animateToPage( page + 1, duration: const Duration(milliseconds: 500), curve: Curves.ease, ); } @override Widget build(BuildContext context) { if (_ads.isEmpty) return const SizedBox(); return Stack( fit: StackFit.expand, children: [ PageView.builder( controller: _pageCtr, itemCount: 1000 + _len * 100, allowImplicitScrolling: true, onPageChanged: (index) => setState(() {}), itemBuilder: (ctx, index) { final ad = _ads[index % _len]; return AdsItem( adInfo: ad, showType: AdShowType.splash, ); }, ), Positioned( bottom: 7, child: _buildIndicator(), ), ], ); } Widget _buildIndicator() { int curPageIndex = 0; if (_pageCtr.hasClients && _ads.isNotEmpty) { curPageIndex = ((_pageCtr.offset + 200.0) ~/ screen.screenWidth) % _len; } // 复用共用指示器(自带切换动画);尺寸沿用启动页原值 return SizedBox( width: Get.width - 14 * 2, child: Center( child: CIndicator( itemCount: _len, selectIndex: curPageIndex, space: 10, dotSize: 8, selectWidth: 25, color: Colors.white.withValues(alpha: 0.5), selectColor: AppColors.actionRed.withValues(alpha: 0.5), ), ), ); } }