Files
wushu/lib/utils/responsive_layout.dart
2026-03-30 02:35:31 +08:00

253 lines
6.6 KiB
Dart

import 'package:flutter/material.dart';
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget? tablet;
final Widget? desktop;
final Widget? largeDesktop;
const ResponsiveLayout({
super.key,
required this.mobile,
this.tablet,
this.desktop,
this.largeDesktop,
});
static bool isMobile(BuildContext context) {
return MediaQuery.of(context).size.width < 768;
}
static bool isTablet(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width >= 768 && width < 1024;
}
static bool isDesktop(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width >= 1024 && width < 1440;
}
static bool isLargeDesktop(BuildContext context) {
return MediaQuery.of(context).size.width >= 1440;
}
static bool isPortrait(BuildContext context) {
return MediaQuery.of(context).orientation == Orientation.portrait;
}
static bool isLandscape(BuildContext context) {
return MediaQuery.of(context).orientation == Orientation.landscape;
}
static double screenWidth(BuildContext context) {
return MediaQuery.of(context).size.width;
}
static double screenHeight(BuildContext context) {
return MediaQuery.of(context).size.height;
}
static EdgeInsets safePadding(BuildContext context) {
return MediaQuery.of(context).padding;
}
static double safeAreaHeight(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final paddingTop = MediaQuery.of(context).padding.top;
final paddingBottom = MediaQuery.of(context).padding.bottom;
return screenHeight - paddingTop - paddingBottom;
}
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width >= 1440 && largeDesktop != null) {
return largeDesktop!;
} else if (width >= 1024 && desktop != null) {
return desktop!;
} else if (width >= 768 && tablet != null) {
return tablet!;
} else {
return mobile;
}
}
}
class ResponsiveContainer extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final double? maxWidth;
final bool centerContent;
const ResponsiveContainer({
super.key,
required this.child,
this.padding,
this.maxWidth,
this.centerContent = true,
});
@override
Widget build(BuildContext context) {
final screenWidth = ResponsiveLayout.screenWidth(context);
final effectiveMaxWidth = maxWidth ?? (screenWidth < 768 ? screenWidth : 1200.0);
Widget container = Container(
width: double.infinity,
constraints: BoxConstraints(
maxWidth: effectiveMaxWidth,
),
padding: padding ?? const EdgeInsets.all(16),
child: child,
);
if (centerContent && screenWidth > effectiveMaxWidth) {
container = Center(child: container);
}
return container;
}
}
class ResponsiveGrid extends StatelessWidget {
final List<Widget> children;
final int mobileColumns;
final int tabletColumns;
final int desktopColumns;
final int largeDesktopColumns;
final double spacing;
final double runSpacing;
final EdgeInsets? padding;
const ResponsiveGrid({
super.key,
required this.children,
this.mobileColumns = 1,
this.tabletColumns = 2,
this.desktopColumns = 3,
this.largeDesktopColumns = 4,
this.spacing = 16.0,
this.runSpacing = 16.0,
this.padding,
});
@override
Widget build(BuildContext context) {
int columns;
if (ResponsiveLayout.isLargeDesktop(context)) {
columns = largeDesktopColumns;
} else if (ResponsiveLayout.isDesktop(context)) {
columns = desktopColumns;
} else if (ResponsiveLayout.isTablet(context)) {
columns = tabletColumns;
} else {
columns = mobileColumns;
}
return LayoutBuilder(
builder: (context, constraints) {
final childAspectRatio = (constraints.maxWidth - (spacing * (columns - 1))) / columns / 200;
return Padding(
padding: padding ?? EdgeInsets.zero,
child: GridView.count(
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: runSpacing,
childAspectRatio: childAspectRatio,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: children,
),
);
},
);
}
}
class ResponsiveCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final double? borderRadius;
final Color? backgroundColor;
final VoidCallback? onTap;
const ResponsiveCard({
super.key,
required this.child,
this.padding,
this.borderRadius,
this.backgroundColor,
this.onTap,
});
@override
Widget build(BuildContext context) {
final effectiveBorderRadius = borderRadius ?? (ResponsiveLayout.isMobile(context) ? 8.0 : 12.0);
final effectivePadding = padding ?? EdgeInsets.all(ResponsiveLayout.isMobile(context) ? 12.0 : 16.0);
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(effectiveBorderRadius),
),
color: backgroundColor,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(effectiveBorderRadius),
child: Padding(
padding: effectivePadding,
child: child,
),
),
);
}
}
class ResponsiveLayoutBuilder extends StatelessWidget {
final Widget Function(BuildContext context, ScreenSize screenSize) builder;
const ResponsiveLayoutBuilder({
super.key,
required this.builder,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
ScreenSize screenSize;
if (constraints.maxWidth >= 1440) {
screenSize = ScreenSize.largeDesktop;
} else if (constraints.maxWidth >= 1024) {
screenSize = ScreenSize.desktop;
} else if (constraints.maxWidth >= 768) {
screenSize = ScreenSize.tablet;
} else {
screenSize = ScreenSize.mobile;
}
return builder(context, screenSize);
},
);
}
}
enum ScreenSize {
mobile,
tablet,
desktop,
largeDesktop,
}
extension ScreenSizeExtension on ScreenSize {
bool get isMobile => this == ScreenSize.mobile;
bool get isTablet => this == ScreenSize.tablet;
bool get isDesktop => this == ScreenSize.desktop;
bool get isLargeDesktop => this == ScreenSize.largeDesktop;
bool get isDesktopOrLarge => isDesktop || isLargeDesktop;
bool get isTabletOrLarger => isTablet || isDesktopOrLarge;
}