You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
4.7 KiB
160 lines
4.7 KiB
import 'package:flutter/foundation.dart'; |
|
import 'package:flutter/material.dart'; |
|
|
|
import 'dart:math' as math; |
|
|
|
class UITest extends StatefulWidget { |
|
@override |
|
State<StatefulWidget> createState() { |
|
return _UITest(); |
|
} |
|
} |
|
|
|
class _UITest extends State<UITest> { |
|
@override |
|
Widget build(BuildContext context) { |
|
return Scaffold( |
|
appBar: AppBar( |
|
backgroundColor: Color(0xFFF7F7F7), |
|
elevation: 0, |
|
title: Text( |
|
"测试", |
|
style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold), |
|
), |
|
leading: GestureDetector( |
|
onTap: () { |
|
Navigator.of(context).pop(); |
|
}, |
|
child: Container( |
|
alignment: Alignment.centerRight, |
|
margin: EdgeInsets.only(left: 10), |
|
padding: EdgeInsets.all(6), |
|
child: Icon( |
|
Icons.arrow_back_ios, |
|
color: Colors.black, |
|
size: 24, |
|
), |
|
), |
|
), |
|
titleSpacing: 2, |
|
leadingWidth: 56, |
|
), |
|
body: AspectRatio( |
|
aspectRatio: 1, |
|
child: PhysicalShape( |
|
color: ElevationOverlay.applyOverlay(context, Colors.white, 2), |
|
elevation: 2, |
|
clipper: BottomAppBarClipper( |
|
shape: CircularHorizontalNotchedRectangle(), |
|
), |
|
child: Container( |
|
margin: EdgeInsets.all(50), |
|
color: Colors.blue.withAlpha(123), |
|
alignment: Alignment.center, |
|
child: Text("主体内容"), |
|
), |
|
), |
|
), |
|
endDrawer: Drawer(), |
|
bottomNavigationBar: BottomAppBar( |
|
color: Colors.deepPurpleAccent, |
|
shape: CircularNotchedRectangle(), |
|
child: Container( |
|
height: 50.0, |
|
), |
|
), |
|
extendBody: true, |
|
floatingActionButton: FloatingActionButton( |
|
onPressed: () { |
|
print("点击"); |
|
}, |
|
child: Icon(Icons.add), |
|
), |
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, |
|
); |
|
} |
|
|
|
} |
|
|
|
class BottomAppBarClipper extends CustomClipper<Path> { |
|
|
|
final NotchedShape shape; |
|
|
|
const BottomAppBarClipper({this.shape}); |
|
|
|
@override |
|
Path getClip(Size size) { |
|
final Rect button = Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: 20); |
|
return shape.getOuterPath(Offset.zero & size, button?.inflate(2)); |
|
} |
|
|
|
@override |
|
bool shouldReclip(BottomAppBarClipper oldClipper) { |
|
return true; |
|
} |
|
} |
|
|
|
class CircularHorizontalNotchedRectangle extends NotchedShape { |
|
@override |
|
Path getOuterPath(Rect host, Rect guest) { |
|
if (guest == null || !host.overlaps(guest)) |
|
return Path()..addRect(host); |
|
|
|
// 客人的形状是一个以客人矩形为边界的圆形。 |
|
// 所以客人的半径是客人宽度的一半。 |
|
final double notchRadius = guest.width / 2.0; |
|
|
|
// 我们从 3 段为缺口构建路径: |
|
// A 段 - 从主机顶部边缘到 B 段的贝塞尔曲线。 |
|
// B 段 - 半径为 notchRadius 的圆弧。段 |
|
// C - 从段 B 返回到主机顶部边缘的贝塞尔曲线。 |
|
// 以下公式的详细解释和推导可在以下网址获得:https:goo.glUfzrqn |
|
|
|
const double s1 = 15.0; |
|
const double s2 = 1.0; |
|
|
|
final double r = notchRadius; |
|
final double a = -1.0 * r - s2; // 半径+s2 |
|
final double b = host.top - guest.center.dy; // 圆心到矩形的y轴距离 |
|
|
|
final double n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r)); |
|
final double p2xA = ((a * r * r) - n2) / (a * a + b * b); |
|
final double p2xB = ((a * r * r) + n2) / (a * a + b * b); |
|
final double p2yA = math.sqrt(r * r - p2xA * p2xA); |
|
final double p2yB = math.sqrt(r * r - p2xB * p2xB); |
|
|
|
final List<Offset> p = List<Offset>.filled(6, null, growable: false); |
|
|
|
// p0、p1 和 p2 是线段 A 的控制点。 |
|
p[0] = Offset(a - s1, b); |
|
p[1] = Offset(a, b); |
|
final double cmp = b < 0 ? -1.0 : 1.0; |
|
p[2] = cmp * p2yA > cmp * p2yB ? Offset(p2xA, p2yA) : Offset(p2xB, p2yB); |
|
|
|
// p3、p4 和 p5 是线段 B 的控制点,它是线段 A 绕 y 轴的镜像。 |
|
p[3] = Offset(-1.0 * p[2].dx, p[2].dy); |
|
p[4] = Offset(-1.0 * p[1].dx, p[1].dy); |
|
p[5] = Offset(-1.0 * p[0].dx, p[0].dy); |
|
|
|
// 将所有点转换回绝对坐标系。 |
|
for (int i = 0; i < p.length; i += 1) |
|
p[i] = p[i] + guest.center; |
|
|
|
return Path() |
|
..moveTo(host.left, host.top) |
|
..lineTo(p[0].dx, p[0].dy) |
|
..quadraticBezierTo(p[1].dx, p[1].dy, p[2].dx, p[2].dy) |
|
..arcToPoint( |
|
p[3], |
|
radius: Radius.circular(notchRadius), |
|
clockwise: false, |
|
) |
|
..quadraticBezierTo(p[4].dx, p[4].dy, p[5].dx, p[5].dy) |
|
..lineTo(host.right, host.top) |
|
..lineTo(host.right, host.bottom) |
|
..lineTo(host.left, host.bottom) |
|
..close(); |
|
} |
|
|
|
} |
|
|
|
|