flutterflutter-layoutflutter-canvas

flutter design Curves layout as single widget


Here solved question about design this layout.

enter image description here

I have a problem to using that, because of this curve on right of screen is not widget and when I want to have some other widgets in green side, I can't, because designed curve is not widget its clipped from green layout.

Suppose I want to have this curve on right of screen and some widget in below of that.

Source code

import 'package:flutter/material.dart';

void main() {
    runApp(MaterialApp(home: Scaffold(body: Test())));
}

class Test extends StatefulWidget {
    @override
    _TestState createState() => _TestState();
}

class _TestState extends State<Test> {
    double _height = 0.0;
    double _width = 0.0;
    double _rightPadding = 2.0;
    double _btnSize = 25.0;
    double _btnY = 0.0;

    @override
    Widget build(BuildContext context) {
    if (_height == 0.0)
        setState(() {
        _height = MediaQuery.of(context).size.height;
        _width = MediaQuery.of(context).size.width;
        _btnY = _height / 3 * 2;
        });
    return _height == 0.0
        ? Container()
        : Stack(
            children: <Widget>[
                Container(
                color: Colors.white,
                ),
                CustomPaint(
                size: Size(_width - _rightPadding, _height),
                painter: CurvedPainter(_btnSize, _btnY),
                ),
            ],
            );
    }
}

class CurvedPainter extends CustomPainter {
    CurvedPainter(this.btnSize, this.btnY);

    final double btnSize;
    final double btnY;

    @override
    void paint(Canvas canvas, Size size) {
    Path path = Path();
    path.moveTo(0.0, 0.0);
    path.lineTo(size.width, 0.0);
    path.lineTo(size.width, btnY - btnSize * 2);

    path.cubicTo(size.width, btnY - btnSize * 0.3, size.width - btnSize * 0.95, btnY - btnSize * 0.9, size.width - btnSize, btnY);
    path.cubicTo(size.width - btnSize * 0.95, btnY + btnSize * 0.9, size.width, btnY + btnSize * 0.3, size.width, btnY + btnSize * 2);

    path.lineTo(size.width, size.height);
    path.lineTo(0.0, size.height);
    path.lineTo(0.0, 0.0);
    canvas.drawPath(
        path,
        Paint()
            ..color = Colors.transparent
            ..style = PaintingStyle.fill);
    }

    @override
    bool shouldRepaint(CurvedPainter oldDelegate) => oldDelegate.btnY != btnY;
}

I want to use right curve as an widget on top of all widgets, without having green side, you suppose green side is as ListView.


Solution

  • It seems that you need something like this given in the image below.

    Update: Dragging control added

    enter image description here

    import 'dart:math' as math;
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: Scaffold(
            body: Test(),
          ),
        );
      }
    }
    
    class Test extends StatefulWidget {
      @override
      _TestState createState() => _TestState();
    }
    
    enum dragType { vertical, horizontal }
    
    class _TestState extends State<Test> {
      final double _btnSize = 48.0;
      final double _rightPadding = 0;
    
      double _extraOffcet = 0;
      double _btnY;
      double _currentX;
      double _currentY;
      double _height;
      double _width;
    
      bool _isHorizontalActive = true;
      bool _isVerticalActive = true;
    
      @override
      void initState() {
        WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    
        super.initState();
      }
    
      _afterLayout(_) {
        _height = MediaQuery.of(context).size.height;
        _width = MediaQuery.of(context).size.width;
        _btnY = _height / 2;
        setState(() {});
      }
    
      _onDrag(details) {
        _updateCoordinates(
          details.globalPosition.dx,
          details.globalPosition.dy,
        );
      }
    
      _updateCoordinates(double x, double y) {
        setState(() {
          if (_isHorizontalActive) {
            _updateX(x);
          }
          if (_isVerticalActive) {
            _updateY(y);
          }
        });
      }
    
      _updateX(x) {
        var dx = _currentX - x;
        _currentX = x;
        _extraOffcet = _extraOffcet + dx;
        _extraOffcet = math.max(_extraOffcet, _rightPadding);
        _extraOffcet = math.min(_extraOffcet, _width - _btnSize);
      }
    
      _updateY(y) {
        var dy = _currentY - y;
        _currentY = y;
        _btnY = _btnY - dy;
        _btnY = math.max(_btnY, _btnSize);
        _btnY = math.min(_btnY, _height - _btnSize);
      }
    
      _listItem(String text, double height) {
        return Container(
          height: height,
          child: Text(text, style: TextStyle(fontSize: 20.0)),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return _height == null
            ? Container()
            : Stack(
                children: <Widget>[
                  Container(
                    padding: EdgeInsets.only(right: 30),
                    color: Colors.blue,
                    height: double.infinity,
                    width: double.infinity,
                    child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
                      _buildSwitch(type: dragType.vertical),
                      _buildSwitch(type: dragType.horizontal),
                      ...List.generate(
                        4,
                        (index) => _listItem('inside', 80),
                      ),
                    ]),
                  ),
                  Positioned(
                    right: _extraOffcet + _rightPadding,
                    child: CustomPaint(
                      size: Size(_btnSize, _height),
                      painter: CurvedPainter(_btnSize, _btnY),
                    ),
                  ),
                  Positioned(
                    top: _btnY - _btnSize / 2 + 5,
                    right: _extraOffcet + _rightPadding + 5,
                    child: GestureDetector(
                      onPanDown: (details) {
                        _currentX = details.globalPosition.dx;
                        _currentY = details.globalPosition.dy;
                      },
                      onPanStart: _onDrag,
                      onPanUpdate: _onDrag,
                      child: Material(
                        type: MaterialType.circle,
                        color: Colors.white,
                        elevation: 8.0,
                        child: Container(
                          width: _btnSize - 10,
                          height: _btnSize - 10,
                          child: Icon(Icons.add),
                        ),
                      ),
                    ),
                  ),
                  Positioned(
                    top: 0,
                    left: _width - _extraOffcet,
                    child: Container(
                      color: Colors.white,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: List.generate(8, (index) => _listItem('from right', 20)),
                      ),
                      height: _height,
                      width: _width,
                    ),
                  ),
                ],
              );
      }
    
      SwitchListTile _buildSwitch({dragType type}) {
        Function onChange;
        String titleText, sutitleText;
        bool value;
    
        if (type == dragType.horizontal) {
          value = _isHorizontalActive;
          titleText = 'Horizontal dragging';
          onChange = (newValue) => setState(() => _isHorizontalActive = newValue);
        } else {
          value = _isVerticalActive;
          titleText = 'Vertical dragging';
          onChange = (newValue) => setState(() => _isVerticalActive = newValue);
        }
        sutitleText = value ? 'active' : 'disabled';
    
        return SwitchListTile(
          value: value,
          onChanged: onChange,
          title: Text(titleText),
          subtitle: Text(sutitleText),
        );
      }
    }
    
    class CurvedPainter extends CustomPainter {
      CurvedPainter(this.btnSize, this.btnY);
    
      final double btnSize;
      final double btnY;
    
      @override
      void paint(Canvas canvas, Size size) {
        var halfBtnSize = btnSize / 2;
        var xMax = size.width;
        var yMax = size.height;
    
        var path = Path()
          ..moveTo(halfBtnSize, yMax)
          ..lineTo(halfBtnSize, btnY + halfBtnSize * 2)
          ..cubicTo(halfBtnSize, btnY + halfBtnSize, 0, btnY + halfBtnSize, 0, btnY)
          ..cubicTo(0, btnY - halfBtnSize, halfBtnSize, btnY - halfBtnSize, halfBtnSize,
              btnY - halfBtnSize * 2)
          ..lineTo(halfBtnSize, 0)
          ..lineTo(xMax, 0)
          ..lineTo(xMax, yMax);
    
        canvas.drawPath(
            path,
            Paint()
              ..color = Colors.white
              ..style = PaintingStyle.fill);
      }
    
      @override
      bool shouldRepaint(CurvedPainter oldDelegate) => oldDelegate.btnY != btnY;
    }