feat:参照研听的基础工程
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user