本文僅作分析。項目做者見 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 的分析筆記。