preface
In mobile development, no matter ordinary buttons or cool animations can’t do without drawing. Each platform has its own drawing system, system drawing tools and various excellent open source frameworks accumulated over time. In daily development, not only do we need to be able to use these wheels, often to better meet the design requirements, but we also need to tinker with them and upgrade them. At this point, we need to have a deeper understanding of drawing. As a mobile developer, we may have some knowledge of Drawing on Android/iOS.
Drawing API
The code is based on Flutter 2.2
Dart API
Drawing elements
Painting in life is inseparable from pen, ink and paper, and so is drawing in the program.
- Canvas, Canvas
- Paint brush, Paint
- Path: the Path
- Color: Color
Let’s take a look at these draw elements (we don’t need to remember them all, just look for them when we use them) :
Canvas: Canvas
An interface for recording graphical operations.
Canvas objects are used to create Picture objects, which themselves can be used with SceneBuilder to build scenes. In normal use, however, this is all handled by the framework.
The canvas has the current transformation matrix applied to all operations. Initially, the transformation matrix is an identity transformation. It can be modified using the Translate, Scale, rotate, Skew, and Transform methods.
The canvas also has a current clipping area that applies to all operations. Initially, the clipping area was infinite. You can modify it using the clipRect, clipRRect, and clipPath methods.
The current transforms and clips can be saved and restored using stacks managed by the Save, saveLayer, and Restore methods.
Canvas clipping:
-
void clipPath(Path path, {bool doAntiAlias = true})
Shrinks the clip area to the intersection of the current clip and the given Path.
-
void clipRect(Rect rect, {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true})
Shrinks the clipping region to the intersection of the current clip and the given rectangle.
-
void clipRRect(RRect rrect, {bool doAntiAlias = true})
Shrinks the clipping region to the intersection of the current clipping and the given rounded rectangle.
Canvas drawing:
-
void drawPoints(PointMode pointMode, List<Offset> points, Paint paint)
Draws a series of points based on the given PointMode.
-
void drawLine(Offset p1, Offset p2, Paint paint)
Draw a line between the given points using the given pigment. The line is stroked, and this call ignores the value of paint.style.
-
void drawRect(Rect rect, Paint paint)
Draws a rectangle with the given Paint. Whether the rectangle is filled or stroked (or both) is controlled by paint.style.
-
void drawOval(Rect rect, Paint paint)
Draws an axially aligned ellipse and fills the given axially aligned rectangle with the given Paint. Whether the ellipse is filled or stroked (or both) is controlled by paint.style.
-
void drawCircle(Offset c, double radius, Paint paint)
Draw a circle centered around the point given by the first argument, whose radius is given by the second argument, and Paint by the third argument. Whether the circle is filled or stroked (or both) is controlled by paint.style.
-
void drawImage(Image image, Offset p, Paint paint)
Draws the given Image onto the canvas with its upper-left corner at the given Offset. Compose the image into the canvas using the given Paint.
-
void drawParagraph(Paragraph paragraph, Offset offset)
Draw the text from the given Paragraph to this canvas at the given Offset.
-
void drawColor(Color color, BlendMode blendMode)
Draws the given Color onto the canvas, applies the given BlendMode, the given Color is the source, and the background is the target.
-
void drawPath(Path path, Paint paint)
Draws the given Path with the given Paint.
-
void drawPaint(Paint paint)
Fills the canvas with the given Paint.
-
void drawShadow(Path path, Color color, double elevation, bool transparentOccluder)
Draws a shadow for the Path representing the given material elevation.
-
void drawPicture(Picture picture)
Draws the given picture onto the canvas. To create a picture, see PictureRecorder.
Canvas transformation:
-
void skew(double sx, double sy)
Adds an axial alignment skew to the current transform, with the first parameter being a horizontal skew rotated clockwise around the origin and the second parameter being a vertical skew rotated clockwise around the origin.
-
void rotate(double radians)
Adds rotation to the current transform in clockwise radians.
-
void scale(double sx, [double sy])
Adds an axial alignment scale to the current transform, scaling by the first parameter in the horizontal direction and the second parameter in the vertical direction.
-
void translate(double dx, double dy)
Adds a translation to the current transformation, moving the coordinate space horizontally with the first argument and vertically with the second argument.
-
void transform(Float64List matrix4)
Multiply the current transformation by the specified 4 e e 4 transformation matrix of the list of values specified as the priority order of the columns.
Canvas state:
-
void save()
Saves a copy of the current transform and clip in the save stack.
-
void saveLayer(Rect bounds, Paint paint)
Save a copy of the current transform and clip on the save stack, and then create a new group that subsequent calls will be part of. When the save stack pops up later, the group is flattened to a layer and the paint. ColorFilter and paint. BlendMode for the given paint are applied.
-
void restore()
If there is anything to pop up, pop up the current save stack. Otherwise, do nothing.
-
int getSaveCount()
Returns the number of items on the save stack, including the initial state. This means that it returns 1 for the clean canvas, and increases it with each call to Save and saveLayer, and decreases it with each matching restore call.
2. Paint
A description of the style used when drawing on a Canvas.
Most of the Canvas apis use Paint objects to describe the styles used for this operation.
Paint properties:
- BlendMode – blendMode to apply when drawing shapes or compositing layers
- Color – color
- ColorFilter — colorFilter
- FilterQuality – The quality controller for filters
- ImageFilter – imageFilter
- InvertColors — Whether the color is inverted
- IsAntiAlias – Whether to apply anti-aliasing to lines and images drawn on canvas
- MaskFilter — maskFilter (e.g., blur)
- Shader — shader
- StrokeCap — Line cap type, type of finish placed at the end of drawn lines
- StrokeJoin — Line type, type of finishes placed on the connections between segments
- StrokeMiterLimit — Limits for drawing mitts on segments
- StrokeWidth – line width
- Style — brush style
- EnableDithering – Whether to dither the output while drawing an image
3. Path: Path
A complex one-dimensional subset of the plane.
A path consists of many subpaths and a current point.
Subpaths consist of various types of line segments, such as lines, arcs, or Bezier curves. Subpaths can be open or closed and self-intersecting.
The (possibly discontinuous) region enclosing a plane according to the current fillType.
At the current point it was originally at the origin. After each operation that adds a segment to a subpath, the current point is updated to the end of that segment.
Paths can be drawn on the Canvas using Canvas.drawPath, and clipping regions can be created using Canvas.clipPath.
Common methods:
-
void addPath(Path path, Offset offset, {Float64List? matrix4})
Add a new subpath offset with the given path offset.
-
void addRect ( Rect rect )
Adds a new subpath consisting of four rows that outline the given rectangle.
-
void addOval(Rect oval)
Adds a new subpath consisting of a curve that forms an ellipse filling the given rectangle.
-
void moveTo(double x, double y)
Starts a new subpath at the given coordinates.
-
void lineTo(double x, double y)
Adds a straight line segment from the current point to the given point.
-
void conicTo(double x1, double y1, double x2, double y2, double w)
Using control points (x1,y1) and weights (w), add a Bessel curve segment that bends from the current point to a given point (x2,y2). If the weight is greater than 1, the curve is hyperbolic; If the weight is equal to 1, it is a parabola; If it’s less than 1, it’s an ellipse.
-
void relativeMoveTo(double dx, double dy)
Starts a new subpath at a given offset from the current point.
-
void relativeLineTo(double dx, double dy)
Adds a line segment from the current point to a point at a given offset from the current point.
-
void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2)
Using the control point offset from the current point (x1,y1), add a quadratic Bezier curve segment that bends from the current point to the point offset from the current point (x2,y2).
-
void relativeConicTo(double x1, double y1, double x2, double y2, double w)
Add a Bessel curve segment that bends from the current point to the point offset from the current point (x2,y2), using the control point offset from the current point (x1,y1) and the weight W. If the weight is greater than 1, the curve is hyperbolic; If the weight is equal to 1, it is a parabola; If it’s less than 1, it’s an ellipse.
-
void close()
Closing the last subpath is like drawing a straight line from the current point to the first point of the subpath.
-
void reset()
Clears Path objects for all child paths, returning them to the same state as when they were created. Reset to the origin at the current point.
4. What Color is it
Immutable 32-bit color values in ARGB format.
Consider the light blue-green color of the Flutter logo. It is completely opaque, with a red channel value of 0x42 (66), a green channel value of 0xA5 (165), and a blue channel value of 0xF5 (245). In common hash syntax for color values, it would be described as #42A5F5.
Here are some ways it can be built:
Color c = const Color(0xFF42A5F5); Color c = const Color.fromARGB(0xFF, 0x42, 0xA5, 0xF5); Color c = const Color.fromARGB(255, 66, 165, 245); Color c = const color.fromrgbo (66, 165, 245, 1.0);Copy the code
If you are having a problem with Color is that your Color does not seem to be colored, please check to ensure that you specified the full eight hexadecimal digits. If only six digits are specified, the first two digits are assumed to be zero, which means full transparency:
Color c1 = const Color(0xFFFFFF); // fully transparent white (invisible) Color c2 = const Color(0xFFFFFFFF); // fully opaque white (visible) Copy the code
Also check out:
Colors, which defines the Colors in the Material Design specification.
The most common use of the Color class is to create a Color value directly using the constructor, but there are more complex applications. Such as: channel transformation, application of mixed mode, shader, filter and other advanced usage, and then introduced when used.
Simple application
Points and lines
class Paper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
alignment: Alignment.center,
child: CustomPaint(
// Use the CustomPaint background palettepainter: MyPainter(), ), ); }}class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// Create a brush
finalPaint paintLine = Paint() .. color = Colors.black .. strokeWidth =3;
/ / draw the line
canvas.drawLine(Offset(- 100..0), Offset(100.0), paintLine);
canvas.drawLine(Offset(0.100), Offset(0.- 100.), paintLine);
// Create a brush
finalPaint paintPoint = Paint().. color = Colors.red;// Draw the dots
canvas.drawCircle(Offset(0.0), 10, paintPoint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Copy the code
Effect:
Draw a five-pointed star using Path
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
finalPaint paint = Paint() .. color = Colors.red .. strokeWidth =3
..style = PaintingStyle.stroke;
Path path = Path();
// The path moves to the starting point
path.moveTo(0.- 100.);
// Connect the endpoints
path.lineTo(59.81);
path.lineTo(- 95..- 31);
path.lineTo(95.- 31);
path.lineTo(59.81);
path.lineTo(0.- 100.);
// Draw the path
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Copy the code
Create a Text stroke by adding a Paint to the Text Forground
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
alignment: Alignment.center,
child: Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
// Border text
Text(
'BillionBottle',
style: TextStyle(
fontSize: 100,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
// Use Paint to stroke textforeground: Paint() .. style = PaintingStyle.stroke .. strokeWidth =6
..color = Colors.blue[700],),),// Fill the text with two layers of overlay to complete the stroke effect
Text(
'BillionBottle',
style: TextStyle(
fontSize: 100,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
color: Colors.white,
),
),
],
),
),
);
}
Copy the code
Effect:
Add an effect to the stroke using the ImageShader ImageShader
Image shaders are powerful. It allows the brush to draw with its own image, much like a brush in Photoshop. The constructor calls for the UI.Image object, the horizontal and vertical arrangement modes, and the transformation matrix Float64List.
class Paper extends StatefulWidget {
@override
_PaperState createState() => _PaperState();
}
class _PaperState extends State<Paper> with SingleTickerProviderStateMixin {
ui.Image _image;
@override
void initState() {
super.initState();
// Resource images need to be loaded in advance
loadImage().then((value) {
setState(() {
_image = value;
});
});
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
alignment: Alignment.center,
child: Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
// Border text
Text(
'BillionBottle',
style: TextStyle(
fontSize: 120, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, foreground: Paint() .. style = PaintingStyle.stroke .. strokeWidth =14
..color = Colors.black
// Use the image shader to draw the resource image to the foreground color of the border text
..shader = ImageShader(
_image,
TileMode.repeated,
TileMode.repeated,
Float64List.fromList([
1.0.0.0.0.1.0.0.0.0.1.0.0.0.0.1,]),),),// Fill the text with two layers of overlay to complete the stroke effect
Text(
'BillionBottle',
style: TextStyle(
fontSize: 120, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, color: Colors.white, ), ), ], ), ), ); }}// Read an image from assets
Future<ui.Image> loadImage() async {
ByteData byteData = await rootBundle.load('assets/images/test.jpg');
ui.Codec codec = await ui.instantiateImageCodec(byteData.buffer.asUint8List());
ui.FrameInfo frameInfo = await codec.getNextFrame();
return frameInfo.image;
}
Copy the code
Resources:
Effect:
Implement a wave effect using drawing knowledge
Wave loading effects are common in daily development, such as:
There are many ways to achieve a wave effect on the client side. In addition to loading GIF, animation, etc., you can also draw bezier curves to achieve this.
I’m sure many of you know the term bezier curve, which we see a lot in many places. However, not everyone knows exactly what the “Bezier curve” is and what makes it so famous.
The mathematical basis of Bessel curves is known as the Bernstein polynomials as early as 1912. But it was not until 1959 that Paul de Casteljau, then a French mathematician working for Citroen, began to attempt to apply it graphically and proposed a numerically stable de Casteljau algorithm. The Bezier curve, however, got its name from the publicity of another French engineer, Pierre Bezier, who worked for Renault in 1962. He used this method to generate complex smooth curves with very few control points to aid in the industrial design of car bodies.
Bessel curve has been widely used in the field of industrial design because of its simple control but strong descriptive ability. Bessel curves also play an important role in the field of computer graphics, especially vector graphics. Some of our most common vector drawing software today, such as Flash, Illustrator, CorelDraw, etc., all provide Bessel curve drawing capability without exception. Even bitmap editing software like Photoshop includes bezier curves as the only vector drawing tool (the pen tool).
The specific formula of The Bezier curve will not be explained here. We will directly implement a simple Bezier curve in Flutter:
Path path = Path();
// Path object
Paint paint = Paint()// Brush object
..color = Colors.orange/ / color
..style = PaintingStyle.stroke/ / line
..strokeWidth = 2;/ / line width
// set path to a quadratic bezier curve consisting of three points: current point (0,0) control point (0,80) end point (100,100)
path.quadraticBezierTo(0.80.100.100);
/ / to draw
canvas.drawPath(path, paint);
Copy the code
Effect (the background has a coordinate system drawn for easy viewing of points) :
Draw the waves
/ / wavelength
final double waveWidth = 80;
/ / the amplitude
final double waveHeight = 40;
@override
void paint(Canvas canvas, Size size) {
// Move the canvas starting point to the center of the screen
canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
// Set the brushPaint paint = Paint() .. color = Colors.red .. style = PaintingStyle.fill// Change this to fill mode instead of lines
..strokeWidth = 2;
/ / path
Path path = Path();
// Draw bezier curve relative displacement method
// Using the control point offset from the current point (x1,y1), add the quadratic Bezier curve segment from the current point to the point offset from the current point (x2,y2).
// the corresponding point is the starting point (0,0), the control point (40, -80) and the end point (80, 0).
path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
// the second curve corresponds to three points: the starting point (80,0), the control point (40,80) and the end point (160,0)
path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
/ / to draw
canvas.drawPath(path, paint);
}
Copy the code
Effect:
Draws a continuous wave passing through the specified border:
// Set the brushPaint paint = Paint() .. color = Colors.red .. style = PaintingStyle.fill .. strokeWidth =2;
// Save the canvas state
canvas.save();
Path path = Path();
// The first wave
path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
// The second wave
path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
// The current end point (x,y) is connected to (x+0,y+100)
path.relativeLineTo(0.100);
// The current end point (x,y) is connected to (x-80*2*2,y)
path.relativeLineTo(-waveWidth*2 * 2.0.0);
// The path is closed
path.close();
/ / to draw
canvas.drawPath(path, paint);
// Restore the canvas
canvas.restore();
// Rectangle border
Path pathRect = Path();
pathRect.addRect(Rect.fromLTWH(160.- 100..160.200));
// Set the rectangle border brushPaint paintCircle = Paint() .. color = Colors.black .. style = PaintingStyle.stroke .. strokeWidth =2;
// Draw a rectangle
canvas.drawPath(pathRect, paintCircle);
Copy the code
Effect:
The next step is to show the initial wave effect by adding animations and clipping:
The essence of animation in Flutter is to redraw the canvas as the data changes, creating a continuous visual effect. According to this principle, we create the effect of motion by repeatedly moving the wave X coordinate to the right and then redrawing it.
Here we mainly use the AnimationController class
The TickerProvider object needs to be passed in the AnimationController constructor
State object can be mixed with SingleTickerProviderStateMixin to become the object
Duration indicates the duration of movement, from lowerBound to upperBound.
class _PaperState extends State<Paper2> with SingleTickerProviderStateMixin {
// Animate the X-axis of the controller
AnimationController _controllerX;
@override
void initState() {
super.initState();
// Set the parameters of the animation controller, and the motion duration is 600ms
_controllerX = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
)..repeat();// Repeat the pattern
}
@override
void dispose() {
_controllerX.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
returnContainer( color: Colors.white, child: CustomPaint( painter: MyPainter(_controllerX), ), ); }}class MyPainter extends CustomPainter {
/ / wavelength
final double waveWidth = 80;
/ / the amplitude
final double waveHeight = 40;
/ / x animation
final Animation<double> repaintX;
const MyPainter(this.repaintX):super(repaint: repaintX);
@override
void paint(Canvas canvas, Size size) {
// The canvas moves to the center of the screen
canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
// The canvas saves the state
canvas.save();
// Change the position of the X-axis of the canvas according to the progress, and then redraw continuously into the implementation of action effects
canvas.translate(2 * waveWidth * repaintX.value, 0);
// Save the canvas state
Path path = Path();
// The first wave
path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
// The second wave
path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
// The current end point (x,y) is connected to (x+0,y+60)
path.relativeLineTo(0.60);
// The current end point (x,y) is connected to (x-80*2*2,y)
path.relativeLineTo(-waveWidth*2 * 2.0.0);
// The path is closed
path.close();
/ / to draw
canvas.drawPath(path, paint);
// Restore the canvas
canvas.restore();
// Rectangle border
Path pathRect = Path();
pathRect.addRect(Rect.fromLTWH(160.- 140..160.200));
// Set the rectangle border brushPaint paintCircle = Paint() .. color = Colors.black .. style = PaintingStyle.stroke .. strokeWidth =2;
// Draw a rectangle
canvas.drawPath(pathRect, paintCircle);
}
// Uncompleted animation progress to redraw
@override
boolshouldRepaint(MyPainter oldDelegate) => oldDelegate.repaintX ! = repaintX; }Copy the code
Effect:
Add clipping so that the wave appears only in the specified area.
// Add clipping
canvas.clipRect((Rect.fromCenter(center: Offset( waveWidth*3.- 60),width: waveWidth*2,height: 200.0)));
Copy the code
Effect:
If we want the y direction to move as well, we can create a new animation controller with the y ring
class _PaperState extends State<Paper2> with SingleTickerProviderStateMixin {
// Animate the X-axis of the controller
AnimationController _controllerX;
// Animation controller y axis
AnimationController _controllerY;
@override
void initState() {
super.initState();
// Set the X-axis animation controller parameters. The motion duration is 600ms
_controllerX = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
)..repeat();// Repeat the pattern
// Set the parameters of the Y-axis animation controller. The motion duration is 5000ms
_controllerY = AnimationController(
duration: const Duration(milliseconds: 5000),
vsync: this,
)..repeat();// Repeat the pattern
}
@override
void dispose() {
_controllerX.dispose();
_controllerY.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
returnContainer( color: Colors.white, child: CustomPaint( painter: MyPainter(_controllerX,_controllerY), ), ); }}class MyPainter extends CustomPainter {
/ / wavelength
final double waveWidth = 80;
/ / the amplitude
final double waveHeight = 40;
// X-axis animation
final Animation<double> repaintX;
// Y-axis animation
final Animation<double> repaintY;
const MyPainter(this.repaintX,this.repaintY);
@override
void paint(Canvas canvas, Size size) {
...
// Change the position of the X-axis and Y-axis of the canvas according to the progress, and then continuously redraw into the action effect implementation
canvas.translate(2 * waveWidth * repaintX.value, - 200.*repaintY.value); . }@override
bool shouldRepaint(MyPainter oldDelegate) => true;
}
Copy the code
The runtime encounters an error at this point
Mean when you state with SingleTickerProviderStateMixin can only use a single animation controller, under the condition of the principle of specific TickerProviderStateMixin here not delve into, we do some modification according to the prompt, And perfect the wave effect.
class Paper2 extends StatefulWidget {
@override
_PaperState createState() => _PaperState();
}
class _PaperState extends State<Paper2> with TickerProviderStateMixin {
// Animate the X-axis of the controller
AnimationController _controllerX;
// Animation controller y axis
AnimationController _controllerY;
@override
void initState() {
super.initState();
_controllerX = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
)..repeat();
_controllerY = AnimationController(
duration: const Duration(milliseconds: 5000),
vsync: this,
)..repeat();
}
@override
void dispose() {
super.dispose();
_controllerX.dispose();
_controllerY.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: CustomPaint(
// Use the CurveTween animation curve to control the X-axis movement ratepainter: MyPainter(CurveTween(curve: Curves.linear).animate(_controllerX), _controllerY), ), ); }}class MyPainter extends CustomPainter {
/ / wavelength
final double waveWidth = 80;
/ / the amplitude
final double waveHeight = 10;
final Animation<double> repaintX;
final Animation<double> repaintY;
const MyPainter(this.repaintX, this.repaintY) : super(repaint: repaintY);
@override
void paint(Canvas canvas, Size size) {
// Adjust the canvas to start at the center of the screen
canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
// Add clipping
canvas.clipRect(
(Rect.fromCenter(center: Offset(waveWidth, - 60), width: waveWidth * 2, height: 200.0)));
// Set the brushPaint paint = Paint() .. style = PaintingStyle.fill .. strokeWidth =2;
Path path = Path();
// Add 3 wave paths
path.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0);
path.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0);
// The current end point (x,y) is connected to (x+0,y+300)
path.relativeLineTo(0.300);
// The current end point (x,y) is connected to (x-80*3*2,y)
path.relativeLineTo(-waveWidth * 3 * 2.0.0);
canvas.save();
// Draw the background wave twice as fast as the foreground to make the wave more dynamic
canvas.translate(4 - * waveWidth * repaintX.value, - 220.* repaintY.value); canvas.drawPath(path, paint.. color = Colors.orange.withAlpha(88));
canvas.restore();
canvas.save();
// Draw foreground waves
canvas.translate(2 - * waveWidth * repaintX.value, - 220.* repaintY.value); canvas.drawPath(path, paint.. color = Colors.red); canvas.restore();// Rectangle border
Path pathRect = Path();
pathRect.addRect(Rect.fromLTWH(0.- 160..160.200));
// Set the rectangle border brushPaint paintCircle = Paint() .. color = Colors.black .. style = PaintingStyle.stroke .. strokeWidth =2;
// Draw a rectangle
canvas.drawPath(pathRect, paintCircle);
}
// Continue to redraw the Y-axis animation before it finishes
@override
boolshouldRepaint(MyPainter oldDelegate) => oldDelegate.repaintY ! = repaintY; }Copy the code
Effect:
This is more consistent with the wave loading effect in our impression. We can put it into different shapes to achieve different loading loading animations, and then we will apply it into our Logo.
@override
void paint(Canvas canvas, Size size) {
// Adjust the position
canvas.translate(0, ScreenUtils.screenHeight / 2 + 100);
// Save the canvas state
canvas.save();
// Adjust the position
canvas.translate(0.- 320.);
// Draw red text to place the bottom layer
_drawTextWithParagraph(canvas, TextAlign.center, Colors.red);
// Restore the canvas state
canvas.restore();
// Rectangle border
Path pathRect = Path();
pathRect.addRect((Rect.fromCenter(
center: Offset(waveWidth * 5.- 100.), width: waveWidth * 8.5, height: 140.0)));
// Set the brushPaint paint = Paint() .. style = PaintingStyle.fill .. strokeWidth =2;
Path path = Path();
// Make the background wave path twice as fast as the foreground to make the wave more dynamic
path.moveTo(4 - * waveWidth * repaintX.value, - 220. * repaintY.value);
// Add wave path
for (double i = -waveWidth; i < ScreenUtils.screenWidth; i += waveWidth) {
path.relativeQuadraticBezierTo(waveWidth / 4, -waveHeight, waveWidth / 2.0);
path.relativeQuadraticBezierTo(waveWidth / 4, waveHeight, waveWidth / 2.0);
}
path.relativeLineTo(0.500);
path.relativeLineTo(-waveWidth * 6 * 2.0.0);
// Merge paths, take the intersection of wave and rectangle borders
var combine = Path.combine(PathOperation.intersect, pathRect, path);
/ / to drawcanvas.drawPath(combine, paint.. color = Colors.orange.withAlpha(88));
// Path restore
path.reset();
// Make foreground wave path
path.moveTo(2 - * waveWidth * repaintX.value, - 220. * repaintY.value);
// Add wave path
for (double i = -waveWidth; i < ScreenUtils.screenWidth; i += waveWidth) {
path.relativeQuadraticBezierTo(waveWidth / 4, -waveHeight, waveWidth / 2.0);
path.relativeQuadraticBezierTo(waveWidth / 4, waveHeight, waveWidth / 2.0);
}
path.relativeLineTo(0.500);
path.relativeLineTo(-waveWidth * 6 * 2.0.0);
path.close();
// Merge paths, take the intersection of wave and rectangle borders
var combine1 = Path.combine(PathOperation.intersect, pathRect, path);
/ / to drawcanvas.drawPath(combine1, paint.. color = Colors.red);// Canvas clipping
canvas.clipPath(combine1);
// Adjust the position
canvas.translate(0.- 320.);
// Draw the white text that intersects the wave
_drawTextWithParagraph(canvas, TextAlign.center, Colors.white);
}
// Draw text
void _drawTextWithParagraph(Canvas canvas, TextAlign textAlign, Color colors) {
var builder = ui.ParagraphBuilder(ui.ParagraphStyle(
textAlign: textAlign,
fontSize: 40,
textDirection: TextDirection.ltr,
maxLines: 1)); builder.pushStyle( ui.TextStyle( fontSize:120,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
color: colors,
),
);
builder.addText("BillionBottle");
ui.Paragraph paragraph = builder.build();
paragraph.layout(ui.ParagraphConstraints(width: 800));
canvas.drawParagraph(paragraph, Offset(0.150));
}
Copy the code
Effect:
Conclusion:
This paper mainly introduces the simple effect of Flutter drawing by using the elements of brush, brush and path through displacement, transformation, cropping and filter effect.
In addition to the simple presentation in this article, Flutter drawing can create more possibilities through deeper understanding and learning. While there are plenty of well-developed open source components available in various communities, building a wheel by hand gives us a clearer picture of what we know. A lot of cool effect is from the first point, the first line drawing began, nine layers of Taiwan, from the tired soil, with you.