【分析】Flutter 滑動對比圖

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

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 的分析筆記。

相關文章
相關標籤/搜索