169 lines
5.8 KiB
Dart
169 lines
5.8 KiB
Dart
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),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|