Design a background from 2 images in flutter
ClipPath
with CustomClipper<Path>
can help you with it.
What you can get:
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;
}
}
jaaq
Updated on December 13, 2022Comments
-
jaaq over 1 year
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
Blow 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.
- If the y position of a pixel is greater than the result of
-
jaaq almost 5 yearsWow, really nice! Thank you very much. Just one more thing about optionally drawing the path in a fixed color(previously ommited from example constructor call, sorry), Could I just draw the path using
Canvas.drawPath
? If so would you draw the path within the getClip function or instead save the path as a member of FunctionClipper so you can draw it within the build function of ClippedPartsWidget? Whats the way to go here? I'd like to only draw the splitFunction if width is given andwidth>0
. -
Mikhail Ponkin almost 5 years@jaaq yes, you can use resulting path with
Canvas.drawPath
, but it will require using another set of widgets (CustomPaint
withCustomPainter
). About storing path: I would store it in FunctionClipper (or CustomPainter) for encapsulation if other widgets does not work with it -
jaaq almost 5 yearsAwesome thanks. I'll award you the much earned bounty, once stackoverflow lets me :)
-
jaaq almost 5 yearscould you maybe help me again please? I tried wrapping the
ClippedPartsWidget
with aCustomPaint
widget, where I draw the same path again using the same function(which is inefficient, but good enough for now). However, if I draw the path with saywidth=3
it either doesn't show if the paint is passed to as a painter property or it overwrites the entire bottom(the clipped widget) with the color I give it. Could you maybe explain in more detail or show me how to finalize this widget so it can optionally render the line defined by splitFunction? -
Mikhail Ponkin almost 5 yearsOh, you want to draw a line? Then I misunderstood you. Then another path is required, containing only the splitting line. There is also an option to set
Paint.style = PaintingStyle.stroke
but in this case it will draw outline of whole path. -
jaaq almost 5 yearsYes that was exactly my issue, it didn't occur to me that fill-in could be the default. Again, very nice implementation, it works flawlessly now!
-
TryHard about 4 years@MikhailPonkin Do you have an idea how I can tell my application that it should not recalculate everything when I open my keyboard? I asked the question here: stackoverflow.com/questions/61551166/…