Initial commit: Flutter 无书应用项目
This commit is contained in:
252
lib/utils/responsive_layout.dart
Normal file
252
lib/utils/responsive_layout.dart
Normal file
@@ -0,0 +1,252 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user