初始化
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user