Files
谢宇宁andClaude Opus 5.5 2d26ea8f90 Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 10:38:11 +07:00

169 lines
5.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_infinite_marquee/flutter_infinite_marquee.dart';
import 'package:hjsq/hj_utils/screen.dart';
import 'package:hjsq/tools_base/ad_manager.dart';
import '../../hj_model/splash/ads_model.dart';
import 'ads_banner.dart';
import 'ads_item.dart';
/// 广告位展示样式,由广告的 coverImgSize 映射而来(见 AdsInfoModel.adStyle)
enum AdStyle {
normal, //两排共 10 个(固定样式时用)
banner, //大 banner 轮播
twoAutoScroll, //双排:上排固定,下排自动滚动
oneScrollBig, //单排自动滚动的大图
}
/// 广告位通用容器:按 [AdStyle] 切换九宫格 / banner / 双排滚动 / 大图滚动
class AdsGridView extends StatelessWidget {
final int position; //广告位标识,ads 为空时按此取本地缓存广告
final List<AdsInfoModel>? ads; //外部直接传入的广告,优先于 position
final bool autoStyle; //true:按第一个广告的 coverImgSize 选样式;false:固定两排九宫格
final EdgeInsetsGeometry? padding;
const AdsGridView(
this.position, {
super.key,
this.ads,
this.autoStyle = false,
this.padding,
});
@override
Widget build(BuildContext context) {
//一次 build 只取一次,自动滚动的 itemBuilder 复用同一份,避免反复全表 filter
final list = ads ?? AdManager().adsByType(position);
//无数据或 ab 测未命中广告,直接占位不渲染
if (list.isEmpty || !AdManager().showAbTestAd) return const SizedBox();
final style = autoStyle ? list.first.adStyle : AdStyle.normal;
return switch (style) {
AdStyle.normal => _normal(list),
AdStyle.banner => _banner(list),
AdStyle.twoAutoScroll => _twoAutoScroll(list),
AdStyle.oneScrollBig => _oneScrollBig(list),
};
}
//统一构造广告项
Widget _item(AdsInfoModel model, {double? aspectRatio}) {
return AdsItem(adInfo: model, aspectRatio: aspectRatio);
}
//两排共 10 个
Widget _normal(List<AdsInfoModel> list) {
return GridView.builder(
padding: padding,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
mainAxisSpacing: 12,
crossAxisSpacing: 10,
childAspectRatio: 60 / 82,
),
itemCount: min(10, list.length),
itemBuilder: (_, index) => _item(list[index]),
);
}
//大 banner:单张轮播,最多 20 张
Widget _banner(List<AdsInfoModel> list) {
return Container(
padding: padding,
alignment: Alignment.center,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: AspectRatio(
aspectRatio: 720 / 150,
child: AdsBanner(
list.length > 20 ? list.take(20).toList() : list,
autoPlayMs: 2000,
),
),
),
);
}
//双排:不足 11 个全放九宫格;否则上排固定 5 个,其余在下排自动滚动
Widget _twoAutoScroll(List<AdsInfoModel> list) {
if (list.length < 11) {
return GridView.builder(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
mainAxisSpacing: 12,
crossAxisSpacing: 10,
childAspectRatio: 60 / 82,
),
itemCount: list.length,
itemBuilder: (_, index) => _item(list[index]),
);
}
final head = list.take(5).toList();
final foot = list.sublist(5);
return LayoutBuilder(
builder: (_, constraints) {
//下排一格和上排九宫格一样宽:4 个列间距共 40 + 左右边距各 16
final itemWidth = ((constraints.maxWidth - 40 - 16 * 2) / 5);
return Column(
children: [
GridView.builder(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
mainAxisSpacing: 12,
crossAxisSpacing: 10,
childAspectRatio: 60 / 82,
),
itemCount: head.length,
itemBuilder: (_, index) => _item(head[index]),
),
12.sizeBoxH,
SizedBox(
height: itemWidth / (60 / 82), //宽高比和上排一致
child: InfiniteMarquee(
frequency: const Duration(milliseconds: 20),
itemBuilder: (_, index) => Container(
width: itemWidth,
margin: const EdgeInsets.only(right: 7),
child: _item(foot[index % foot.length]),
),
),
),
12.sizeBoxH,
],
);
},
);
}
//单排自动滚动的大图,最多 20 个;一屏 3.2 个,露出下一张引导滑动
Widget _oneScrollBig(List<AdsInfoModel> list) {
return LayoutBuilder(
builder: (_, constraints) {
final data = list.length > 20 ? list.take(20).toList() : list;
final itemWidth = ((constraints.maxWidth - 7 * 2 - 16 * 2) / 3.2);
return Container(
margin: padding,
height: itemWidth / (104 / 176),
child: InfiniteMarquee(
frequency: const Duration(milliseconds: 20),
itemBuilder: (_, index) => Container(
width: itemWidth,
margin: const EdgeInsets.only(right: 7),
child: _item(data[index % data.length], aspectRatio: 104 / 152),
),
),
);
},
);
}
}