flutter架 构项目基础
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
/// ============================================================
|
||||
/// 【ImgUrl】图片地址工具 —— 对标 UniApp utils/image.js 的 $img
|
||||
///
|
||||
/// 规则(与原 $img 完全一致):
|
||||
/// 1. 空值 / 纯空白 → 返回空串
|
||||
/// 2. 以 http 开头 → 原样返回(已是完整 URL)
|
||||
/// 3. 以 /static/ 开头 → 原样返回(小程序本地资源;Flutter 端此类
|
||||
/// 资源应改用 Image.asset / Material Icon,不能直接加载网络)
|
||||
/// 4. 其余 → 拼 CDN 域名返回完整 URL
|
||||
/// ============================================================
|
||||
class ImgUrl {
|
||||
ImgUrl._();
|
||||
|
||||
/// 图片 CDN 域名(对标 image.js 的 IMG_URL;旧域名 hx.23544.com 已弃用)。
|
||||
/// TODO(业务逻辑): 如需按环境切换,可迁移进 AppEnv 的 _envTable。
|
||||
static const String _cdn = 'https://hx.cnzhyx.cn';
|
||||
|
||||
/// 等价 Vue 的 $img(path)
|
||||
static String build(String? path) {
|
||||
if (path == null) return '';
|
||||
final s = path.trim();
|
||||
if (s.isEmpty) return '';
|
||||
if (s.startsWith('http')) return s;
|
||||
if (s.startsWith('/static/')) return s;
|
||||
return '$_cdn$s';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import 'dart:math' show min;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// 【新项目屏幕适配模板】= 方案 B(平板动态 designSize) + 方案 A(.spMin) + 兜底
|
||||
///
|
||||
/// 使用方法(新项目):
|
||||
/// 1. pubspec.yaml 添加 flutter_screenutil: ^5.9.0;
|
||||
/// 2. 复制本文件到新项目 lib/utils/,入口改成 runApp(const ScreenAdaptApp());
|
||||
/// 3. 页面里统一按下面规则写尺寸:
|
||||
/// 字号:fontSize: 16.spMin ← 大屏只缩不放(关键!)
|
||||
/// 宽度/横向间距:16.w 高度/纵向间距:16.h 圆角:12.r
|
||||
///
|
||||
/// 一、这套方案解决什么问题?
|
||||
/// 默认 .sp = 字号 × 屏宽/375。平板宽 800dp → 字体放大 2.1 倍,爆炸。
|
||||
/// 方案 B:平板(≥600dp)自动切换大 designSize,缩放比例回到 ~1;
|
||||
/// 方案 A:.spMin = min(设计值, .sp),字体只允许缩小,永不超设计稿;
|
||||
/// 兜底:fontSizeResolver 限制字体最大放大 1.2 倍(防横屏/极端宽屏)。
|
||||
///
|
||||
/// 二、注意事项:
|
||||
/// 1. 平板上布局尺寸接近设计稿原值,若想"大屏显示更多内容",
|
||||
/// 需配合 LayoutBuilder / 断点做响应式布局;
|
||||
/// 2. 所有阈值集中在 AppAdapt,一处改、全局生效;
|
||||
/// 3. designSize 用 MediaQueryData.fromView 取物理屏尺寸,
|
||||
/// 不依赖 MaterialApp,启动时即可正确计算。
|
||||
/// ============================================================
|
||||
|
||||
/// ---------- 工具:全局适配配置(一处修改,全局生效) ----------
|
||||
class AppAdapt {
|
||||
AppAdapt._();
|
||||
|
||||
/// 平板判定断点(Material 规范:宽度 ≥ 600dp 视为平板/大屏)
|
||||
static const double tabletBreakpoint = 600;
|
||||
|
||||
/// 手机设计稿(按你的 UI 设计稿尺寸改)
|
||||
static const Size phoneDesign = Size(375, 812);
|
||||
|
||||
/// 字体最大放大倍数兜底(防横屏/极端宽屏字体失控)
|
||||
static const double maxFontScale = 1.2;
|
||||
|
||||
/// 方案 B 核心:根据屏幕尺寸选择设计稿
|
||||
///
|
||||
/// 手机:用 375x812,按宽度比例缩放;
|
||||
/// 平板:设计稿 = 设备自身逻辑尺寸 → scaleWidth 恒为 1,
|
||||
/// 横竖屏 UI 尺寸完全一致,不会旋转后变小/变大。
|
||||
static Size designSizeOf(BuildContext context) {
|
||||
final mq = MediaQuery.maybeOf(context) ??
|
||||
MediaQueryData.fromView(View.of(context));
|
||||
final size = mq.size;
|
||||
// 平板判定用"宽高都 ≥600":避免手机横屏(如 932x430)被误判成平板
|
||||
final isTablet =
|
||||
size.width >= tabletBreakpoint && size.height >= tabletBreakpoint;
|
||||
return isTablet ? size : phoneDesign;
|
||||
}
|
||||
|
||||
/// 方案 A 兜底:自定义字体缩放,放大倍数封顶 maxFontScale
|
||||
static double fontSizeResolver(num fontSize, ScreenUtil instance) {
|
||||
final scaled = fontSize * instance.scaleText;
|
||||
return min(scaled, fontSize * maxFontScale);
|
||||
}
|
||||
}
|
||||
|
||||
/// ---------- 入口模板(新项目直接 runApp 这个) ----------
|
||||
class ScreenAdaptApp extends StatelessWidget {
|
||||
const ScreenAdaptApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: AppAdapt.designSizeOf(context), // 方案 B:平板/手机自动切换
|
||||
minTextAdapt: true,
|
||||
fontSizeResolver: AppAdapt.fontSizeResolver, // 方案 A 兜底
|
||||
builder: (context, child) {
|
||||
return MaterialApp(
|
||||
title: '新项目',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: const Color(0xFF3B82F6),
|
||||
scaffoldBackgroundColor: const Color(0xFFF5F7FA),
|
||||
),
|
||||
home: const _TemplateHome(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 模板首页:演示 .spMin / .w / .h / .r 的写法
|
||||
class _TemplateHome extends StatelessWidget {
|
||||
const _TemplateHome();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('适配模板')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// 关键:字号用 .spMin,平板上不会爆炸
|
||||
Text(
|
||||
'标题 24.spMin',
|
||||
style: TextStyle(fontSize: 24.spMin, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
Text(
|
||||
'正文 16.spMin',
|
||||
style: TextStyle(fontSize: 16.spMin, color: Colors.grey),
|
||||
),
|
||||
SizedBox(height: 24.h),
|
||||
Container(
|
||||
width: 200.w,
|
||||
height: 60.h,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF3B82F6),
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'按钮 200.w x 60.h',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user