Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design a background from 2 images in flutter

I'd like to create a a new stateless widget class that is defined by 2 images(top, bottom) and a line(defined by a function, e.g. (x){x+500}, a width(can be 0, if it shouldn't be drawn), and a color) separating the two images.

For each pixel:

  • If the y position of a pixel is greater than the result of f(x) + width/2 a pixel of bottom shall be drawn
  • If it's smaller than f(x) - width / 2 a pixel of top shall be drawn
  • Else a pixel of the given line color shall be drawn

Below see an example of what mywidget({'top': A, 'bottom': B, 'f': (x){return sin(x)+500;}, 'width': 1, 'color': Color(0xFFFFFFFF)}); could look like:

(0,0)
+------+
|      |
|  A   |
| __   |
|/  \__|
|      |
|  B   |
+------+(e.g. 1920,1080)

Is there a line widget where the shape is defined by a (mathematic) function?

Is this the only way to do it? Or is there a container widget that already allows this? I have looked at the Stack widget but that's not quite solving the problem, as I'd need a structure to decide which pixel is rendered as described above. The function to decide which should happen should be supplyable by the user.

like image 568
jaaq Avatar asked Oct 17 '25 02:10

jaaq


1 Answers

ClipPath with CustomClipper<Path> can help you with it.
What you can get:
Result screenshot
Example source code:

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: ClippedPartsWidget(
          top: Container(
            color: Colors.red,
          ),
          bottom: Container(
            color: Colors.blue,
          ),
          splitFunction: (Size size, double x) {
            // normalizing x to make it exactly one wave
            final normalizedX = x / size.width * 2 * pi;
            final waveHeight = size.height / 15;
            final y = size.height / 2 - sin(normalizedX) * waveHeight;

            return y;
          },
        ),
      ),
    ),
  );
}

class ClippedPartsWidget extends StatelessWidget {
  final Widget top;
  final Widget bottom;
  final double Function(Size, double) splitFunction;

  ClippedPartsWidget({
    @required this.top,
    @required this.bottom,
    @required this.splitFunction,
  });

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        // I'm putting unmodified top widget to back. I wont cut it.
        // Instead I'll overlay it with clipped bottom widget.
        top,
        Align(
          alignment: Alignment.bottomCenter,
          child: ClipPath(
            clipper: FunctionClipper(splitFunction: splitFunction),
            child: bottom,
          ),
        ),
      ],
    );
  }
}

class FunctionClipper extends CustomClipper<Path> {
  final double Function(Size, double) splitFunction;

  FunctionClipper({@required this.splitFunction}) : super();

  Path getClip(Size size) {
    final path = Path();

    // move to split line starting point
    path.moveTo(0, splitFunction(size, 0));

    // draw split line
    for (double x = 1; x <= size.width; x++) {
      path.lineTo(x, splitFunction(size, x));
    }

    // close bottom part of screen
    path.lineTo(size.width, size.height);
    path.lineTo(0, size.height);

    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    // I'm returning fixed 'true' value here for simplicity, it's not the part of actual question
    // basically that means that clipping will be redrawn on any changes
    return true;
  }
}
like image 150
Mikhail Ponkin Avatar answered Oct 18 '25 19:10

Mikhail Ponkin