import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_infinite_marquee/flutter_infinite_marquee.dart'; import 'package:jydj/hj_utils/screen.dart'; import 'package:jydj/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? 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 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 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 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 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), ), ), ); }, ); } }