Files
huangguo_android/lib/hj_page/splash/splash_ad_view.dart
T
2026-09-15 15:44:13 +07:00

257 lines
6.9 KiB
Dart

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<AdsInfoModel>? adData;
final Function? callback;
const SplashAdView({
super.key,
this.adData,
this.callback,
});
@override
State<StatefulWidget> createState() => _SplashAdViewState();
}
class _SplashAdViewState extends State<SplashAdView> {
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: <Widget>[
_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<Offset>(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<AdsInfoModel> adData;
const SplashBannerWidget({
super.key,
required this.adData,
});
@override
State<StatefulWidget> createState() => _SplashBannerWidgetState();
}
class _SplashBannerWidgetState extends State<SplashBannerWidget> {
late final PageController _pageCtr;
Timer? _timer;
List<AdsInfoModel> 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),
),
),
);
}
}