87 lines
2.8 KiB
Dart
87 lines
2.8 KiB
Dart
import 'dart:math';
|
||
|
||
import 'package:flutter/widgets.dart';
|
||
|
||
import '../theme/spacing.dart';
|
||
|
||
/// iPad 响应式布局:
|
||
/// 宽 ≥ [breakpoint] 进入平板布局——屏边距 36、内容列统一限宽 920 居中
|
||
/// (`padding: max(36, (宽-920)/2)`)、卡片列表两列(列距 18、行内等高)、
|
||
/// 底部 tab 行限宽 600 居中。手机布局完全不变。
|
||
abstract class Adaptive {
|
||
/// 平板断点(iPhone 最宽 ~440,iPad 11″ 竖屏 834 起)
|
||
static const double breakpoint = 700;
|
||
|
||
/// 平板屏边距(demo `--pad-screen: 36px`)
|
||
static const double padTablet = 36;
|
||
|
||
/// 内容列限宽(demo 所有页统一 920 居中)
|
||
static const double contentMaxWidth = 920;
|
||
|
||
/// 两列列表的列间距(demo `column-gap: 18px`)
|
||
static const double columnGap = 18;
|
||
|
||
/// 底部 tab 行限宽(demo `.nav .tabs{max-width:600px}`)
|
||
static const double tabsMaxWidth = 600;
|
||
|
||
static bool isTablet(BuildContext context) =>
|
||
MediaQuery.sizeOf(context).width >= breakpoint;
|
||
|
||
/// 屏水平边距:手机 [Spacing.page];平板 `max(36, (宽-920)/2)`,
|
||
/// 即内容超 920 时左右对称留白实现居中限宽。
|
||
static double hPad(BuildContext context) {
|
||
final w = MediaQuery.sizeOf(context).width;
|
||
if (w < breakpoint) return Spacing.page;
|
||
return max(padTablet, (w - contentMaxWidth) / 2);
|
||
}
|
||
|
||
static EdgeInsets screenPadding(
|
||
BuildContext context, {
|
||
double top = 0,
|
||
double bottom = 0,
|
||
}) {
|
||
final h = hPad(context);
|
||
return EdgeInsets.fromLTRB(h, top, h, bottom);
|
||
}
|
||
}
|
||
|
||
/// 卡片列表自适应 sliver:手机单列(卡间距 [Spacing.cardGap]);
|
||
/// 平板两列——卡片两两成行、行内等高(IntrinsicHeight + stretch,
|
||
/// 对应 demo CSS grid `1fr 1fr` 的等高行行为)。
|
||
SliverList adaptiveCardSliver(
|
||
BuildContext context, {
|
||
required int itemCount,
|
||
required Widget Function(BuildContext, int) itemBuilder,
|
||
double rowGap = Spacing.cardGap,
|
||
}) {
|
||
if (!Adaptive.isTablet(context)) {
|
||
return SliverList.separated(
|
||
itemCount: itemCount,
|
||
separatorBuilder: (_, _) => SizedBox(height: rowGap),
|
||
itemBuilder: itemBuilder,
|
||
);
|
||
}
|
||
final rows = (itemCount + 1) ~/ 2;
|
||
return SliverList.separated(
|
||
itemCount: rows,
|
||
separatorBuilder: (_, _) => SizedBox(height: rowGap),
|
||
itemBuilder: (c, r) {
|
||
final rightIndex = r * 2 + 1;
|
||
return IntrinsicHeight(
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
Expanded(child: itemBuilder(c, r * 2)),
|
||
const SizedBox(width: Adaptive.columnGap),
|
||
Expanded(
|
||
child: rightIndex < itemCount
|
||
? itemBuilder(c, rightIndex)
|
||
: const SizedBox(),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|