如何在 Flutter 中为 TextFormField 添加阴影
How to add drop shadow to TextFormField In Flutter
我有一个 flutter 文本表单域,我想给它添加一个阴影。我该怎么做?
final password = TextFormField(
obscureText: true,
autofocus: false,
decoration: InputDecoration(
icon: new Icon(Icons.lock, color: Color(0xff224597)),
hintText: 'Password',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(borderRadius:BorderRadius.circular(5.0),
borderSide: BorderSide(color: Colors.white, width: 3.0))
),
);
您可以用 Material
小部件包装 TextFormField
并编辑其属性,例如 elevation
和 shadowColor
。
示例:
Material(
elevation: 20.0,
shadowColor: Colors.blue,
child: TextFormField(
obscureText: true,
autofocus: false,
decoration: InputDecoration(
icon: new Icon(Icons.lock, color: Color(0xff224597)),
hintText: 'Password',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(borderRadius:BorderRadius.circular(5.0),
borderSide: BorderSide(color: Colors.white, width: 3.0))
),
),
)
您将获得类似于下图的内容。
使用容器小部件的投影效果
我们可以传颜色,设置偏移值,blurRadius和spreadRadius。让我们看看代码
Container(
child: TextField(
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
),
),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black38,
blurRadius: 25,
offset: const Offset(0, 10),
),
],
),
);
这是一个可能的解决方案,其中 BoxShadow
仅显示在 TextField
后面,但如果显示错误文本则不会垂直扩展。
我的解决方案是使用 Stack
小部件在负责显示阴影的实际 TextField
后面创建一个额外的 Container
。
一个TextPainter
用于根据其样式确定错误文本的高度:
import 'package:flutter/material.dart';
class TextFieldWithBoxShadow extends StatelessWidget {
final String? errorText;
final String? labelText;
final TextEditingController? controller;
final double height;
const TextFieldWithBoxShadow({
Key? key,
this.errorText,
this.labelText,
this.controller,
this.height = 40,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final errorStyle = const TextStyle(
fontSize: 14,
);
// Wrap everything in LayoutBuilder so that the available maxWidth is taken into account for the height calculation (important if you error text exceeds one line)
return LayoutBuilder(builder: (context, constraints) {
// Use tp to calculate the height of the errorText
final textPainter = TextPainter()
..text = TextSpan(text: errorText, style: errorStyle)
..textDirection = TextDirection.ltr
..layout(maxWidth: constraints.maxWidth);
final heightErrorMessage = textPainter.size.height + 8;
return Stack(
children: [
// Separate container with identical height of text field which is placed behind the actual textfield
Container(
height: height,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 3,
offset: Offset(3, 3),
),
],
borderRadius: BorderRadius.circular(
10.0,
),
),
),
Container(
// Add height of error message if it is displayed
height: errorText != null ? height + heightErrorMessage : height,
child: TextField(
decoration: InputDecoration(
fillColor: Colors.black,
filled: true,
errorStyle: errorStyle,
errorText: errorText,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
10.0,
),
),
labelText: labelText,
),
controller: controller,
),
),
],
);
});
}
}
您可以使用 PhysicalModel
在每个小部件上添加阴影,如下所示:
PhysicalModel(
borderRadius: BorderRadius.circular(25),
color: Colors.white,
elevation: 5.0,
shadowColor: Color(0xff44BD32),
child: CustomTextField(...
您可以将 TextFormField 包装到 Container 中,也可以添加阴影 Container这将为您添加阴影 TextFormFieldbut 它还会为 TextFormField.
添加颜色
要从 TextFormField 中删除颜色,请使用 fillColor 并在 TextFormField 上填充 属性。
您可以控制颜色线的不透明度Colors.black.withOpacity(0.3).
结帐代码如下:
final Widget password = Container(
decoration: BoxDecoration(
boxShadow: [
const BoxShadow(
blurRadius: 8,
),
],
borderRadius: BorderRadius.circular(5.0),
),
child: TextFormField(
obscureText: true,
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
prefixIcon: const Icon(
Icons.lock,
color: Color(0xff224597),
),
hintText: 'Password',
contentPadding: const EdgeInsets.fromLTRB(
20.0,
10.0,
20.0,
10.0,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
borderSide: const BorderSide(
color: Colors.white,
width: 3.0,
),
),
),
),
);
YOU CAN CHECKOUT THE OUTPUT HERE
当我们使用容器、Material 或任何其他小部件来包裹输入文本字段以应用阴影时的问题是,如果我们使用提示文本、错误文本或任何其他 属性 改变文本框的大小,设计会被破坏。
您可以使用自定义画家扩展 InputBorder class,而不是将输入包装在另一个小部件中。
例如:
class MyInputBorder extends OutlineInputBorder {}
将以下方法从 OutlineInputBorder 实现(用于此示例)复制到新的 class:
_gapBorderPath
_cornersAreCircular
油漆
然后在paint方法中你可以添加下面几行
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 4, true);
以上几行必须包含在 canvas.drawRRect 行之前:
示例:
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
// paint the shadow for the outlined shape
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, shadowColor!, elevation, true);
canvas.drawRRect(center, paint);
} else {... other code omitted to keep simple}
然后,在您的 Widget 中,使用新的输入边框:
TextField(
decoration: InputDecoration(
border: MyInputBorder()
),
)
生成的结果如下所示,没有任何包装解决方案的问题:
text box with shadow
这是一个完整的示例实现,post 是西班牙语,但它解释了这个想法:Full article for reference
@mrramos 的回答几乎完成,但这段代码不会给出预期的结果,我阅读了建议的文章并实现了我自己的 class,我的用例只是文本字段的阴影它的选择因此命名它。
对此的快速解释,因为它需要阅读很多内容,而且大部分内容都不需要理解,只是为了实现一个简单的阴影。
paint() 方法从 OutlineInputBorder 复制为 _cornersAreCircular() 和 _gapBorderPath()
在paint方法中添加了这几行是为了给阴影。
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 5, true);
final shadowPaint = Paint();
shadowPaint.strokeWidth = 0;
shadowPaint.color = Colors.white;
shadowPaint.style = PaintingStyle.fill;
canvas.drawRRect(center, shadowPaint);
canvas.drawRRect(center, paint);
完成文件 class。
import 'package:flutter/material.dart';
import 'dart:ui' show lerpDouble;
import 'dart:math' as math;
class SelectedInputBorderWithShadow extends OutlineInputBorder {
const SelectedInputBorderWithShadow({
BorderSide borderSide = const BorderSide(),
borderRadius = const BorderRadius.all(Radius.circular(5)),
gapPadding = 4.0,
}) : super(
borderSide: borderSide,
borderRadius: borderRadius,
gapPadding: gapPadding,
);
static bool _cornersAreCircular(BorderRadius borderRadius) {
return borderRadius.topLeft.x == borderRadius.topLeft.y &&
borderRadius.bottomLeft.x == borderRadius.bottomLeft.y &&
borderRadius.topRight.x == borderRadius.topRight.y &&
borderRadius.bottomRight.x == borderRadius.bottomRight.y;
}
Path _gapBorderPath(
Canvas canvas, RRect center, double start, double extent) {
// When the corner radii on any side add up to be greater than the
// given height, each radius has to be scaled to not exceed the
// size of the width/height of the RRect.
final RRect scaledRRect = center.scaleRadii();
final Rect tlCorner = Rect.fromLTWH(
scaledRRect.left,
scaledRRect.top,
scaledRRect.tlRadiusX * 2.0,
scaledRRect.tlRadiusY * 2.0,
);
final Rect trCorner = Rect.fromLTWH(
scaledRRect.right - scaledRRect.trRadiusX * 2.0,
scaledRRect.top,
scaledRRect.trRadiusX * 2.0,
scaledRRect.trRadiusY * 2.0,
);
final Rect brCorner = Rect.fromLTWH(
scaledRRect.right - scaledRRect.brRadiusX * 2.0,
scaledRRect.bottom - scaledRRect.brRadiusY * 2.0,
scaledRRect.brRadiusX * 2.0,
scaledRRect.brRadiusY * 2.0,
);
final Rect blCorner = Rect.fromLTWH(
scaledRRect.left,
scaledRRect.bottom - scaledRRect.blRadiusY * 2.0,
scaledRRect.blRadiusX * 2.0,
scaledRRect.blRadiusX * 2.0,
);
// This assumes that the radius is circular (x and y radius are equal).
// Currently, BorderRadius only supports circular radii.
const double cornerArcSweep = math.pi / 2.0;
final double tlCornerArcSweep = math.acos(
(1 - start / scaledRRect.tlRadiusX).clamp(0.0, 1.0),
);
final Path path = Path()..addArc(tlCorner, math.pi, tlCornerArcSweep);
if (start > scaledRRect.tlRadiusX)
path.lineTo(scaledRRect.left + start, scaledRRect.top);
const double trCornerArcStart = (3 * math.pi) / 2.0;
const double trCornerArcSweep = cornerArcSweep;
if (start + extent < scaledRRect.width - scaledRRect.trRadiusX) {
path.moveTo(scaledRRect.left + start + extent, scaledRRect.top);
path.lineTo(scaledRRect.right - scaledRRect.trRadiusX, scaledRRect.top);
path.addArc(trCorner, trCornerArcStart, trCornerArcSweep);
} else if (start + extent < scaledRRect.width) {
final double dx = scaledRRect.width - (start + extent);
final double sweep = math.asin(
(1 - dx / scaledRRect.trRadiusX).clamp(0.0, 1.0),
);
path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep);
}
return path
..moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY)
..lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY)
..addArc(brCorner, 0.0, cornerArcSweep)
..lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom)
..addArc(blCorner, math.pi / 2.0, cornerArcSweep)
..lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY);
}
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
assert(gapExtent != null);
assert(gapPercentage >= 0.0 && gapPercentage <= 1.0);
assert(_cornersAreCircular(borderRadius));
final Paint paint = borderSide.toPaint();
final RRect outer = borderRadius.toRRect(rect);
final RRect center = outer.deflate(borderSide.width / 2.0);
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 5, true);
final shadowPaint = Paint();
shadowPaint.strokeWidth = 0;
shadowPaint.color = Colors.white;
shadowPaint.style = PaintingStyle.fill;
canvas.drawRRect(center, shadowPaint);
canvas.drawRRect(center, paint);
} else {
final double extent =
lerpDouble(0.0, gapExtent + gapPadding * 2.0, gapPercentage)!;
switch (textDirection!) {
case TextDirection.rtl:
final Path path = _gapBorderPath(canvas, center,
math.max(0.0, gapStart + gapPadding - extent), extent);
canvas.drawPath(path, paint);
break;
case TextDirection.ltr:
final Path path = _gapBorderPath(
canvas, center, math.max(0.0, gapStart - gapPadding), extent);
canvas.drawPath(path, paint);
break;
}
}
}
}
我的结果是这样的。
您可以使用此 class 作为元素边框的包装器。它采用控件的边框并在控件上方的边框上绘制阴影。为了营造阴影在控件后面的错觉,控件上方的阴影区域被截断。
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class DecoratedInputBorder extends InputBorder {
DecoratedInputBorder({
required this.child,
required this.shadow,
}) : super(borderSide: child.borderSide);
final InputBorder child;
final BoxShadow shadow;
@override
bool get isOutline => child.isOutline;
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) => child.getInnerPath(rect, textDirection: textDirection);
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) => child.getOuterPath(rect, textDirection: textDirection);
@override
EdgeInsetsGeometry get dimensions => child.dimensions;
@override
InputBorder copyWith({BorderSide? borderSide, InputBorder? child, BoxShadow? shadow, bool? isOutline}) {
return DecoratedInputBorder(
child: (child ?? this.child).copyWith(borderSide: borderSide),
shadow: shadow ?? this.shadow,
);
}
@override
ShapeBorder scale(double t) {
final scalledChild = child.scale(t);
return DecoratedInputBorder(
child: scalledChild is InputBorder ? scalledChild : child,
shadow: BoxShadow.lerp(null, shadow, t)!,
);
}
@override
void paint(Canvas canvas, Rect rect, {double? gapStart, double gapExtent = 0.0, double gapPercentage = 0.0, TextDirection? textDirection}) {
final clipPath = Path()
..addRect(const Rect.fromLTWH(-5000, -5000, 10000, 10000))
..addPath(getInnerPath(rect), Offset.zero)
..fillType = PathFillType.evenOdd;
canvas.clipPath(clipPath);
final Paint paint = shadow.toPaint();
final Rect bounds = rect.shift(shadow.offset).inflate(shadow.spreadRadius);
canvas.drawPath(getOuterPath(bounds), paint);
child.paint(canvas, rect, gapStart: gapStart, gapExtent: gapExtent, gapPercentage: gapPercentage, textDirection: textDirection);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is DecoratedInputBorder && other.borderSide == borderSide && other.child == child && other.shadow == shadow;
}
@override
int get hashCode => hashValues(borderSide, child, shadow);
@override
String toString() {
return '${objectRuntimeType(this, 'DecoratedInputBorder')}($borderSide, $shadow, $child)';
}
}
MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
inputDecorationTheme: InputDecorationTheme(
border: DecoratedInputBorder(
child: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
shadow: const BoxShadow(
color: Colors.blue,
blurRadius: 15,
),
),
),
),
它应该是这样的:
互动示例:https://dartpad.dev/?id=35f1249b52d177d47bc91c87d0a8c08c
或者,您可以使用我的包 control_style。它实现了这种方法的更深层次的实现。
我有一个 flutter 文本表单域,我想给它添加一个阴影。我该怎么做?
final password = TextFormField(
obscureText: true,
autofocus: false,
decoration: InputDecoration(
icon: new Icon(Icons.lock, color: Color(0xff224597)),
hintText: 'Password',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(borderRadius:BorderRadius.circular(5.0),
borderSide: BorderSide(color: Colors.white, width: 3.0))
),
);
您可以用 Material
小部件包装 TextFormField
并编辑其属性,例如 elevation
和 shadowColor
。
示例:
Material(
elevation: 20.0,
shadowColor: Colors.blue,
child: TextFormField(
obscureText: true,
autofocus: false,
decoration: InputDecoration(
icon: new Icon(Icons.lock, color: Color(0xff224597)),
hintText: 'Password',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(borderRadius:BorderRadius.circular(5.0),
borderSide: BorderSide(color: Colors.white, width: 3.0))
),
),
)
您将获得类似于下图的内容。
使用容器小部件的投影效果
我们可以传颜色,设置偏移值,blurRadius和spreadRadius。让我们看看代码
Container(
child: TextField(
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
),
),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black38,
blurRadius: 25,
offset: const Offset(0, 10),
),
],
),
);
这是一个可能的解决方案,其中 BoxShadow
仅显示在 TextField
后面,但如果显示错误文本则不会垂直扩展。
我的解决方案是使用 Stack
小部件在负责显示阴影的实际 TextField
后面创建一个额外的 Container
。
一个TextPainter
用于根据其样式确定错误文本的高度:
import 'package:flutter/material.dart';
class TextFieldWithBoxShadow extends StatelessWidget {
final String? errorText;
final String? labelText;
final TextEditingController? controller;
final double height;
const TextFieldWithBoxShadow({
Key? key,
this.errorText,
this.labelText,
this.controller,
this.height = 40,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final errorStyle = const TextStyle(
fontSize: 14,
);
// Wrap everything in LayoutBuilder so that the available maxWidth is taken into account for the height calculation (important if you error text exceeds one line)
return LayoutBuilder(builder: (context, constraints) {
// Use tp to calculate the height of the errorText
final textPainter = TextPainter()
..text = TextSpan(text: errorText, style: errorStyle)
..textDirection = TextDirection.ltr
..layout(maxWidth: constraints.maxWidth);
final heightErrorMessage = textPainter.size.height + 8;
return Stack(
children: [
// Separate container with identical height of text field which is placed behind the actual textfield
Container(
height: height,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 3,
offset: Offset(3, 3),
),
],
borderRadius: BorderRadius.circular(
10.0,
),
),
),
Container(
// Add height of error message if it is displayed
height: errorText != null ? height + heightErrorMessage : height,
child: TextField(
decoration: InputDecoration(
fillColor: Colors.black,
filled: true,
errorStyle: errorStyle,
errorText: errorText,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
10.0,
),
),
labelText: labelText,
),
controller: controller,
),
),
],
);
});
}
}
您可以使用 PhysicalModel
在每个小部件上添加阴影,如下所示:
PhysicalModel(
borderRadius: BorderRadius.circular(25),
color: Colors.white,
elevation: 5.0,
shadowColor: Color(0xff44BD32),
child: CustomTextField(...
您可以将 TextFormField 包装到 Container 中,也可以添加阴影 Container这将为您添加阴影 TextFormFieldbut 它还会为 TextFormField.
添加颜色要从 TextFormField 中删除颜色,请使用 fillColor 并在 TextFormField 上填充 属性。
您可以控制颜色线的不透明度Colors.black.withOpacity(0.3).
结帐代码如下:
final Widget password = Container(
decoration: BoxDecoration(
boxShadow: [
const BoxShadow(
blurRadius: 8,
),
],
borderRadius: BorderRadius.circular(5.0),
),
child: TextFormField(
obscureText: true,
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
prefixIcon: const Icon(
Icons.lock,
color: Color(0xff224597),
),
hintText: 'Password',
contentPadding: const EdgeInsets.fromLTRB(
20.0,
10.0,
20.0,
10.0,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
borderSide: const BorderSide(
color: Colors.white,
width: 3.0,
),
),
),
),
);
YOU CAN CHECKOUT THE OUTPUT HERE
当我们使用容器、Material 或任何其他小部件来包裹输入文本字段以应用阴影时的问题是,如果我们使用提示文本、错误文本或任何其他 属性 改变文本框的大小,设计会被破坏。
您可以使用自定义画家扩展 InputBorder class,而不是将输入包装在另一个小部件中。 例如:
class MyInputBorder extends OutlineInputBorder {}
将以下方法从 OutlineInputBorder 实现(用于此示例)复制到新的 class: _gapBorderPath _cornersAreCircular 油漆
然后在paint方法中你可以添加下面几行
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 4, true);
以上几行必须包含在 canvas.drawRRect 行之前: 示例:
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
// paint the shadow for the outlined shape
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, shadowColor!, elevation, true);
canvas.drawRRect(center, paint);
} else {... other code omitted to keep simple}
然后,在您的 Widget 中,使用新的输入边框:
TextField(
decoration: InputDecoration(
border: MyInputBorder()
),
)
生成的结果如下所示,没有任何包装解决方案的问题:
text box with shadow
这是一个完整的示例实现,post 是西班牙语,但它解释了这个想法:Full article for reference
@mrramos 的回答几乎完成,但这段代码不会给出预期的结果,我阅读了建议的文章并实现了我自己的 class,我的用例只是文本字段的阴影它的选择因此命名它。 对此的快速解释,因为它需要阅读很多内容,而且大部分内容都不需要理解,只是为了实现一个简单的阴影。
paint() 方法从 OutlineInputBorder 复制为 _cornersAreCircular() 和 _gapBorderPath()
在paint方法中添加了这几行是为了给阴影。
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 5, true);
final shadowPaint = Paint();
shadowPaint.strokeWidth = 0;
shadowPaint.color = Colors.white;
shadowPaint.style = PaintingStyle.fill;
canvas.drawRRect(center, shadowPaint);
canvas.drawRRect(center, paint);
完成文件 class。
import 'package:flutter/material.dart';
import 'dart:ui' show lerpDouble;
import 'dart:math' as math;
class SelectedInputBorderWithShadow extends OutlineInputBorder {
const SelectedInputBorderWithShadow({
BorderSide borderSide = const BorderSide(),
borderRadius = const BorderRadius.all(Radius.circular(5)),
gapPadding = 4.0,
}) : super(
borderSide: borderSide,
borderRadius: borderRadius,
gapPadding: gapPadding,
);
static bool _cornersAreCircular(BorderRadius borderRadius) {
return borderRadius.topLeft.x == borderRadius.topLeft.y &&
borderRadius.bottomLeft.x == borderRadius.bottomLeft.y &&
borderRadius.topRight.x == borderRadius.topRight.y &&
borderRadius.bottomRight.x == borderRadius.bottomRight.y;
}
Path _gapBorderPath(
Canvas canvas, RRect center, double start, double extent) {
// When the corner radii on any side add up to be greater than the
// given height, each radius has to be scaled to not exceed the
// size of the width/height of the RRect.
final RRect scaledRRect = center.scaleRadii();
final Rect tlCorner = Rect.fromLTWH(
scaledRRect.left,
scaledRRect.top,
scaledRRect.tlRadiusX * 2.0,
scaledRRect.tlRadiusY * 2.0,
);
final Rect trCorner = Rect.fromLTWH(
scaledRRect.right - scaledRRect.trRadiusX * 2.0,
scaledRRect.top,
scaledRRect.trRadiusX * 2.0,
scaledRRect.trRadiusY * 2.0,
);
final Rect brCorner = Rect.fromLTWH(
scaledRRect.right - scaledRRect.brRadiusX * 2.0,
scaledRRect.bottom - scaledRRect.brRadiusY * 2.0,
scaledRRect.brRadiusX * 2.0,
scaledRRect.brRadiusY * 2.0,
);
final Rect blCorner = Rect.fromLTWH(
scaledRRect.left,
scaledRRect.bottom - scaledRRect.blRadiusY * 2.0,
scaledRRect.blRadiusX * 2.0,
scaledRRect.blRadiusX * 2.0,
);
// This assumes that the radius is circular (x and y radius are equal).
// Currently, BorderRadius only supports circular radii.
const double cornerArcSweep = math.pi / 2.0;
final double tlCornerArcSweep = math.acos(
(1 - start / scaledRRect.tlRadiusX).clamp(0.0, 1.0),
);
final Path path = Path()..addArc(tlCorner, math.pi, tlCornerArcSweep);
if (start > scaledRRect.tlRadiusX)
path.lineTo(scaledRRect.left + start, scaledRRect.top);
const double trCornerArcStart = (3 * math.pi) / 2.0;
const double trCornerArcSweep = cornerArcSweep;
if (start + extent < scaledRRect.width - scaledRRect.trRadiusX) {
path.moveTo(scaledRRect.left + start + extent, scaledRRect.top);
path.lineTo(scaledRRect.right - scaledRRect.trRadiusX, scaledRRect.top);
path.addArc(trCorner, trCornerArcStart, trCornerArcSweep);
} else if (start + extent < scaledRRect.width) {
final double dx = scaledRRect.width - (start + extent);
final double sweep = math.asin(
(1 - dx / scaledRRect.trRadiusX).clamp(0.0, 1.0),
);
path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep);
}
return path
..moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY)
..lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY)
..addArc(brCorner, 0.0, cornerArcSweep)
..lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom)
..addArc(blCorner, math.pi / 2.0, cornerArcSweep)
..lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY);
}
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
assert(gapExtent != null);
assert(gapPercentage >= 0.0 && gapPercentage <= 1.0);
assert(_cornersAreCircular(borderRadius));
final Paint paint = borderSide.toPaint();
final RRect outer = borderRadius.toRRect(rect);
final RRect center = outer.deflate(borderSide.width / 2.0);
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
Path path = Path();
path.addRRect(center);
canvas.drawShadow(path, Colors.black, 5, true);
final shadowPaint = Paint();
shadowPaint.strokeWidth = 0;
shadowPaint.color = Colors.white;
shadowPaint.style = PaintingStyle.fill;
canvas.drawRRect(center, shadowPaint);
canvas.drawRRect(center, paint);
} else {
final double extent =
lerpDouble(0.0, gapExtent + gapPadding * 2.0, gapPercentage)!;
switch (textDirection!) {
case TextDirection.rtl:
final Path path = _gapBorderPath(canvas, center,
math.max(0.0, gapStart + gapPadding - extent), extent);
canvas.drawPath(path, paint);
break;
case TextDirection.ltr:
final Path path = _gapBorderPath(
canvas, center, math.max(0.0, gapStart - gapPadding), extent);
canvas.drawPath(path, paint);
break;
}
}
}
}
我的结果是这样的。
您可以使用此 class 作为元素边框的包装器。它采用控件的边框并在控件上方的边框上绘制阴影。为了营造阴影在控件后面的错觉,控件上方的阴影区域被截断。
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class DecoratedInputBorder extends InputBorder {
DecoratedInputBorder({
required this.child,
required this.shadow,
}) : super(borderSide: child.borderSide);
final InputBorder child;
final BoxShadow shadow;
@override
bool get isOutline => child.isOutline;
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) => child.getInnerPath(rect, textDirection: textDirection);
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) => child.getOuterPath(rect, textDirection: textDirection);
@override
EdgeInsetsGeometry get dimensions => child.dimensions;
@override
InputBorder copyWith({BorderSide? borderSide, InputBorder? child, BoxShadow? shadow, bool? isOutline}) {
return DecoratedInputBorder(
child: (child ?? this.child).copyWith(borderSide: borderSide),
shadow: shadow ?? this.shadow,
);
}
@override
ShapeBorder scale(double t) {
final scalledChild = child.scale(t);
return DecoratedInputBorder(
child: scalledChild is InputBorder ? scalledChild : child,
shadow: BoxShadow.lerp(null, shadow, t)!,
);
}
@override
void paint(Canvas canvas, Rect rect, {double? gapStart, double gapExtent = 0.0, double gapPercentage = 0.0, TextDirection? textDirection}) {
final clipPath = Path()
..addRect(const Rect.fromLTWH(-5000, -5000, 10000, 10000))
..addPath(getInnerPath(rect), Offset.zero)
..fillType = PathFillType.evenOdd;
canvas.clipPath(clipPath);
final Paint paint = shadow.toPaint();
final Rect bounds = rect.shift(shadow.offset).inflate(shadow.spreadRadius);
canvas.drawPath(getOuterPath(bounds), paint);
child.paint(canvas, rect, gapStart: gapStart, gapExtent: gapExtent, gapPercentage: gapPercentage, textDirection: textDirection);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is DecoratedInputBorder && other.borderSide == borderSide && other.child == child && other.shadow == shadow;
}
@override
int get hashCode => hashValues(borderSide, child, shadow);
@override
String toString() {
return '${objectRuntimeType(this, 'DecoratedInputBorder')}($borderSide, $shadow, $child)';
}
}
MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
inputDecorationTheme: InputDecorationTheme(
border: DecoratedInputBorder(
child: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
shadow: const BoxShadow(
color: Colors.blue,
blurRadius: 15,
),
),
),
),
它应该是这样的:
互动示例:https://dartpad.dev/?id=35f1249b52d177d47bc91c87d0a8c08c
或者,您可以使用我的包 control_style。它实现了这种方法的更深层次的实现。