flutter架 构项目基础
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// Stack + Positioned 层叠布局演示页
|
||||
///
|
||||
/// 一、两种使用方式:
|
||||
/// 1. 直接运行本页面:runApp(const MaterialApp(home: StackDemoPage()))
|
||||
/// 2. 复制某个 Demo 的 Stack 代码到任意 Scaffold 的 body 中。
|
||||
///
|
||||
/// 二、作用:
|
||||
/// Stack 实现 UI 重叠,类似 CSS 的 position:absolute。
|
||||
/// Stack = 图层容器(后写的子组件盖在上面)
|
||||
/// Positioned = 给子组件做绝对定位(只能直接放在 Stack 的 children 里!)
|
||||
///
|
||||
/// 三、核心属性:
|
||||
/// Stack.alignment : 对【没有被 Positioned 包裹】的子组件做默认对齐
|
||||
/// Stack.clipBehavior: Clip.none 允许子组件绘制超出 Stack 边界
|
||||
/// (默认 Clip.hardEdge,超出会被裁剪看不见)
|
||||
/// Positioned.top/bottom/left/right : 距离 Stack 四边的距离
|
||||
/// Positioned.fill : 填满整个 Stack 容器(相当于做蒙层)
|
||||
///
|
||||
/// 四、三大高频坑:
|
||||
/// 1. 全部子组件都是 Positioned 时 → Stack 尺寸异常!
|
||||
/// 真实行为:size = 父约束最大值(constraints.biggest),不按内容算。
|
||||
/// 放在无界约束(如 Column 裸放)→ debug 断言崩溃 "A Stack requires
|
||||
/// bounded constraints",整个 body 都画不出来;
|
||||
/// 放在有界约束 → Stack 占满整个可用区域。
|
||||
/// 修复A:给 Stack 套 SizedBox 固定宽高;
|
||||
/// 修复B:放一个非 Positioned 的底层组件撑起大小。
|
||||
/// 2. Positioned 只能直接作为 Stack 的 children,套其他 Widget 中间层会失效。
|
||||
/// 3. 做悬浮角标务必 clipBehavior: Clip.none,否则负偏移部分被裁剪掉。
|
||||
/// ============================================================
|
||||
class StackDemoPage extends StatelessWidget {
|
||||
const StackDemoPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Stack 层叠布局 Demo')),
|
||||
body: SingleChildScrollView(
|
||||
// AlwaysScrollableScrollPhysics() 永远可以滚动,内容很短也能拽动回弹
|
||||
// BouncingScrollPhysics() 越界回弹(iOS 风格,拉出来空白再弹回)
|
||||
// NeverScrollableScrollPhysics() ❌完全禁止滚动,就算内容溢出也不能滑动
|
||||
physics: BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.all(26.w),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const _SectionTitle('Demo1:基础重叠 + 右上角角标'),
|
||||
// 注意:Flutter 3.44 的 Container 已不是 const 构造,外层不能再写 const Center
|
||||
Center(
|
||||
child: Stack(
|
||||
// 未被 Positioned 的子组件默认居中
|
||||
alignment: Alignment.topLeft,
|
||||
// 允许内容超出 Stack,角标才能"溢出"到盒子外
|
||||
clipBehavior: Clip.none, // 允许超出 Stack 边界 (默认 Clip.hardEdge,超出会被裁剪看不见)
|
||||
children: [
|
||||
// 底层盒子
|
||||
Container(
|
||||
width: 250.w,
|
||||
height: 250.h,
|
||||
color: Colors.blue,
|
||||
),
|
||||
// 绝对定位:右上角,负偏移让它一半悬在盒子外
|
||||
Positioned(
|
||||
top: -10,
|
||||
right: -10,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(6.w),
|
||||
color: Colors.red,
|
||||
child: Text(
|
||||
"角标",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 普通子组件,受 Stack.alignment 控制居中
|
||||
Text(
|
||||
"中间文字",
|
||||
style: TextStyle(color: Colors.white, fontSize: 20.sp),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
const _SectionTitle('Demo2:Positioned.fill 蒙层'),
|
||||
Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
// 底层组件撑起 Stack 的尺寸(250x250)
|
||||
Container(width: 250.w, height: 250.h, color: Colors.grey),
|
||||
// Positioned.fill 填满整个 Stack,做半透明蒙层
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.black54,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"蒙层",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
const _SectionTitle('坑①:全部 Positioned → Stack 尺寸异常'),
|
||||
Text(
|
||||
'全部子组件都是 Positioned 时,Stack 尺寸不由内容决定,'
|
||||
'而是直接取父约束最大值。套上 SizedBox 有界约束后,'
|
||||
'Stack 会占满 300x60(而非收缩到内容大小);'
|
||||
'若裸放在 Column 这种无界高度里,debug 模式会直接断言崩溃,'
|
||||
'整个 body 都画不出来——这就是之前页面只剩标题的原因。',
|
||||
style: TextStyle(fontSize: 13.sp, color: Color(0xFF888888)),
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
// 有界约束 + 全 Positioned:Stack 占满 SizedBox 给的 300x60
|
||||
SizedBox(
|
||||
width: 300.w,
|
||||
height: 60.h,
|
||||
child: ColoredBox(
|
||||
color: Color(0xFFF5F7FA),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(top: 10, left: 10, child: Text("A")),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
const _SectionTitle('坑①修复方案1:SizedBox 固定宽高'),
|
||||
SizedBox(
|
||||
width: 300.w,
|
||||
height: 80.h,
|
||||
child: ColoredBox(
|
||||
color: Color(0xFFF5F7FA),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(top: 10, left: 10, child: Text("A")),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
const _SectionTitle('坑①修复方案2:非 Positioned 底层组件撑起尺寸'),
|
||||
Stack(
|
||||
children: [
|
||||
// 非 Positioned 底层组件:由它决定 Stack 尺寸
|
||||
ColoredBox(
|
||||
color: Color(0xFFE8E8E8),
|
||||
child: SizedBox(width: 300.w, height: 80.h),
|
||||
),
|
||||
Positioned(top: 10, left: 10, child: Text("A")),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 区块标题
|
||||
class _SectionTitle extends StatelessWidget {
|
||||
const _SectionTitle(this.title);
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 12.h),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF333333),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user