本文仅作分析。项目做者见 github
GitHub 源地址: Before Aftergit
rect_clipper.dart
custom_widget.dart
custom_widget.dart
是控件的主要实现。
github
rect_clipper.dart
是对不一样方向 (裁剪)的实现。继承于 CustomClipper<Path> canvas
rect_clipper.dart
: SizedImagebash
/// 使用 [SizedBox] 来限制图像大小
class SizedImage extends StatelessWidget {
/// 传入的 WIDGET
final Widget _image;
/// 用做 [SizedBox] 的宽高,以限制 [_image] 的大小
final double _height, _width, _imageCornerRadius;
const SizedImage(
this._image, this._height, this._width, this._imageCornerRadius,
{Key key})
: super(key: key);
@override
Widget build(BuildContext context) {
// 裁剪
return ClipRRect(
borderRadius: BorderRadius.circular(_imageCornerRadius),
// 固定宽高
child: SizedBox(
height: _height,
width: _width,
child: _image,
),
);
}
}复制代码
这里使用 SizedBox 来限制传入的 widget 宽高。less
rect_clipper.dart
: CustomThumbShape
ide
class CustomThumbShape extends SliderComponentShape {
/// 滑块半径
final double _thumbRadius;
/// 滑块颜色
final Color _thumbColor;
CustomThumbShape(this._thumbRadius, this._thumbColor);
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(_thumbRadius);
}
@override
void paint(PaintingContext context, Offset center,
{Animation<double> activationAnimation,
Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
SliderThemeData sliderTheme,
TextDirection textDirection,
double value}) {
final Canvas canvas = context.canvas;
// 内圈
final Paint paint = Paint()
// 抗锯齿
..isAntiAlias = true
// 描边宽度
..strokeWidth = 4.0
..color = _thumbColor
..style = PaintingStyle.fill;
// 外圈
final Paint paintStroke = Paint()
..isAntiAlias = true
..strokeWidth = 4.0
..color = _thumbColor
..style = PaintingStyle.stroke;
canvas.drawCircle(
center,
_thumbRadius,
paintStroke,
);
canvas.drawCircle(
center,
_thumbRadius - 6,
paint,
);
// 画出一条"直线"
canvas.drawRect(
Rect.fromCenter(
center: center, width: 4.0, height: parentBox.size.height),
paint);
}
}
复制代码
CustomThumbShape 就是这个(绿圈内):
ui
至于 CustomThumbShape 用在何处下面解释。this
rect_clipper.dart
: BeforeAfter, _BeforeAfterspa
class BeforeAfter extends StatefulWidget {
/// image1
final Widget beforeImage;
// image2
final Widget afterImage;
/// 图像宽高
final double imageHeight;
final double imageWidth;
/// 图像四角裁剪半径
final double imageCornerRadius;
/// 拖动指示器颜色
final Color thumbColor;
/// 指示器半径
final double thumbRadius;
/// 点击指示器的背景颜色
final Color overlayColor;
/// 拖动方向
final bool isVertical;
const BeforeAfter({
Key key,
@required this.beforeImage,
@required this.afterImage,
this.imageHeight,
this.imageWidth,
this.imageCornerRadius = 8.0,
this.thumbColor = Colors.white,
this.thumbRadius = 16.0,
this.overlayColor,
this.isVertical = false,
}) : assert(beforeImage != null),
assert(afterImage != null),
super(key: key);
@override
_BeforeAfterState createState() => _BeforeAfterState();
}
class _BeforeAfterState extends State<BeforeAfter> {
/// 裁剪程度
double _clipFactor = 0.5;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: <Widget>[
// AFTER
Padding(
padding: widget.isVertical
? const EdgeInsets.symmetric(vertical: 24.0)
: const EdgeInsets.symmetric(horizontal: 24.0),
child: SizedImage(
widget.afterImage,
widget.imageHeight,
widget.imageWidth,
widget.imageCornerRadius,
),
),
/// BEFORE
Padding(
padding: widget.isVertical
? const EdgeInsets.symmetric(vertical: 24.0)
: const EdgeInsets.symmetric(horizontal: 24.0),
child: ClipPath(
clipper: widget.isVertical
? RectClipperVertical(_clipFactor)
: RectClipper(_clipFactor),
child: SizedImage(
widget.beforeImage,
widget.imageHeight,
widget.imageWidth,
widget.imageCornerRadius,
),
),
),
Positioned.fill(
child: SliderTheme(
data: SliderThemeData(
// slider 宽度调整为 0
trackHeight: 0,
overlayColor: widget.overlayColor,
thumbShape:
CustomThumbShape(widget.thumbRadius, widget.thumbColor),
),
child: widget.isVertical
? RotatedBox(
quarterTurns: 1,
child: Slider(
value: _clipFactor,
onChanged: (double factor) =>
setState(() => this._clipFactor = factor),
),
)
: Slider(
value: _clipFactor,
onChanged: (double factor) =>
setState(() => this._clipFactor = factor),
),
),
),
],
);
}
}复制代码
从中咱们能够看出两张图片置于 Stack
中,底部图片用 SizedImage
进行限制大小,顶部图片通用限制大小后被 ClipPath
包裹,ClipPath
顾名思义,用于裁剪 widgetcode
咱们根据是否垂直来使用不一样的 clipper
。什么是垂直,见下图:
咱们能够看到 RectClipper
传入了一个值,_clipFactor
,此值用于控制裁剪的程度(裁剪的百分比),后面又具体解释。
custom_widget.dart
: RectClipper
class RectClipper extends CustomClipper<Path> {
final double clipFactor;
RectClipper(this.clipFactor);
@override
Path getClip(Size size) {
Path path = Path();
path.lineTo(size.width * clipFactor, 0.0);
path.lineTo(size.width * clipFactor, size.height);
path.lineTo(0.0, size.height);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
复制代码
这里代码用一张图解释:
clipFactor
取值 0 ~ 1 。
至于这三行代码,咱们继续用图表示:
path.lineTo(size.width * clipFactor, 0);
path.lineTo(size.width * clipFactor, size.height);
path.lineTo(0.0, size.height);复制代码
下面具体说一下 CustomThumbShape
Positioned.fill(
child: SliderTheme(
data: SliderThemeData(
// slider 宽度调整为 0
trackHeight: 0,
overlayColor: widget.overlayColor,
thumbShape:
CustomThumbShape(widget.thumbRadius, widget.thumbColor),
),
child: widget.isVertical
? RotatedBox(
quarterTurns: 1,
child: Slider(
value: _clipFactor,
onChanged: (double factor) =>
setState(() => this._clipFactor = factor),
),
)
: Slider(
value: _clipFactor,
onChanged: (double factor) =>
setState(() => this._clipFactor = factor),
),
),
),复制代码
使用 SliderTheme 加上 SliderThemeData
创造一个自定义 slider, trackHeight: 0
, 把 slider 进度条调整为 0 。
上图是不调整 trackHeight
的效果。
而后经过判断是否为垂直,来决定是否使用 RotatedBox
旋转 Slider
。
以上就是 Before After 的分析笔记。