Beskärning, blandningslägen och lagerkomposition
Använd beskärningsbanor och blandningslägen för att komponera avancerade visuella lagereffekter.
Beskärning, blandningslägen och lagerkomposition är en gratis lektion i Mobilutveckling med Flutter på CoddyKit. Detta är lektion 3 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Mobilutveckling med Flutter, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Mobilutveckling med Flutter innehåller totalt 4 lektioner.
Varför komposition är viktig
Avancerade visuella effekter i Flutter skapas sällan av en enda form. Glödande ringar, frostade överlägg, maskerade miniatyrbilder och brickor med "utskurna" hål byggs genom att lagra flera ritningar ovanpå varandra och styra hur varje nytt lager kombineras med det som redan finns på canvasen.
Tre verktyg driver detta på en Canvas:
- Klippning — begränsar var ritningen får hamna (
clipPath,clipRect,clipRRect). - Blandningslägen — styr beräkningen som sammanfogar ett nytt lager med pixlarna under det (
BlendModepå enPaint). - Lager —
saveLayerskapar en mellanlagrad buffert så att ett blandningsläge tillämpas på en hel grupp, inte bara på en form.
I den här lektionen kopplas alla tre ihop.
Klipp canvasen
Inuti CustomPainter.paint begränsar en klippning det område där du får rita. Allt du ritar efter klippningen maskeras till det området fram till nästa restore().
canvas.clipRect(rect)— rektangulär klippning.canvas.clipRRect(rrect)— klippning med en rundad rektangel, utmärkt för miniatyrbilder på kort.canvas.clipPath(path)— klippning med en godtycklig form.
Para alltid ihop klippning med save() / restore() så att klippningen inte påverkar senare ritningar.
class ClipPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = const Color(0xFF2196F3);
canvas.save();
final rrect = RRect.fromRectAndRadius(
Offset.zero & size,
const Radius.circular(24),
);
canvas.clipRRect(rrect);
// Fills the whole canvas, but only the rounded area shows.
canvas.drawRect(Offset.zero & size, paint);
canvas.restore();
}
@override
bool shouldRepaint(covariant ClipPainter oldDelegate) => false;
}Klippning med en godtycklig bana
För masker som inte är rektangulära, till exempel biljetter, vågor och brickor, skapar du en Path och skickar den till clipPath. Den klassiska biljettformen använder två konkava bågar på sidorna.
Ange doAntiAlias: true (standardvärdet) för jämna kanter. För animerade klippningar som ändras i varje bildruta kan doAntiAlias: false vara något snabbare.
Path ticketPath(Size size, double notch) {
final r = notch;
return Path()
..moveTo(0, 0)
..lineTo(size.width, 0)
..lineTo(size.width, size.height / 2 - r)
..arcToPoint(
Offset(size.width, size.height / 2 + r),
radius: Radius.circular(r),
clockwise: false,
)
..lineTo(size.width, size.height)
..lineTo(0, size.height)
..lineTo(0, size.height / 2 + r)
..arcToPoint(
Offset(0, size.height / 2 - r),
radius: Radius.circular(r),
clockwise: false,
)
..close();
}Vad ett blandningsläge faktiskt gör
Ett BlendMode definierar formeln som kombinerar källan (det du ritar nu) med målet (pixlarna som redan finns där). Standardvärdet är BlendMode.srcOver — normal alfakomposition.
Användbara grupper:
- Porter-Duff (geometri/maskning):
srcIn,dstIn,srcOut,dstATop,clear. - Separerbar färgmatematik:
multiply,screen,overlay,plus(additiv glöd),difference.
Du tilldelar det på Paint: paint.blendMode = BlendMode.multiply.
Blandningslägen behöver ett lager
Här är den viktiga fallgropen: många blandningslägen (srcIn, dstIn, multiply över transparens och så vidare) fungerar bara korrekt när det finns ett uttryckligt lager att använda som mål.
canvas.saveLayer(bounds, paint) allokerar en buffert utanför skärmen. Ritningar mellan saveLayer och restore samlas i den bufferten; vid restore komponeras bufferten tillbaka med hjälp av lagerfärgens blandningsläge.
Utan saveLayer skulle en srcIn-ritning behandla hela skärmen som mål — och vanligtvis radera mer än du avsåg.
void paint(Canvas canvas, Size size) {
final bounds = Offset.zero & size;
// 1. Open an offscreen layer.
canvas.saveLayer(bounds, Paint());
// 2. Destination: a circle.
canvas.drawCircle(
size.center(Offset.zero),
size.shortestSide / 2,
Paint()..color = const Color(0xFFFFFFFF),
);
// 3. Source with srcIn keeps only the overlap with the circle.
canvas.drawRect(
bounds,
Paint()
..shader = const LinearGradient(
colors: [Color(0xFFFF0080), Color(0xFF7928CA)],
).createShader(bounds)
..blendMode = BlendMode.srcIn,
);
// 4. Composite the layer back to the canvas.
canvas.restore();
}Maskera en gradient i en form
Mönstret från föregående scen är det vedertagna sättet att skapa gradientfyllda ikoner eller textmasker:
- Rita den ogenomskinliga maskformen först (den blir målet).
- Rita gradienten med
BlendMode.srcIn— den finns bara kvar där masken är ogenomskinlig.
Byt rollerna med dstIn: rita gradienten först och därefter en form med dstIn för att bara behålla gradienten under formen. Välj metod utifrån vilket lager du vill rita sist.
class GradientTextMask extends CustomPainter {
GradientTextMask(this.label);
final String label;
@override
void paint(Canvas canvas, Size size) {
final bounds = Offset.zero & size;
canvas.saveLayer(bounds, Paint());
final tp = TextPainter(
text: TextSpan(
text: label,
style: const TextStyle(
fontSize: 64, fontWeight: FontWeight.bold,
color: Color(0xFFFFFFFF),
),
),
textDirection: TextDirection.ltr,
)..layout();
tp.paint(canvas, Offset.zero); // destination = glyphs
canvas.drawRect(
bounds,
Paint()
..shader = const LinearGradient(
colors: [Color(0xFF00C6FF), Color(0xFF0072FF)],
).createShader(bounds)
..blendMode = BlendMode.srcIn, // gradient fills glyphs
);
canvas.restore();
}
@override
bool shouldRepaint(covariant GradientTextMask old) => old.label != label;
}Additiv glöd med BlendMode.plus
BlendMode.plus adderar källans och målets färgkanaler, begränsade till 1,0. Överlappande ljusa former blir ljusare — perfekt för neonglöd, gnistor och ljussken.
Kombinera det med en MaskFilter.blur för att mjuka upp varje form till en halo. Eftersom plus ackumuleras skapar flera lager av suddiga cirklar en övertygande glöd utan någon bildresurs.
void drawGlow(Canvas canvas, Offset c, double r, Color color) {
final glow = Paint()
..color = color
..blendMode = BlendMode.plus
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12);
canvas.drawCircle(c, r, glow);
// A second, tighter core makes the center read brighter.
canvas.drawCircle(c, r * 0.5,
Paint()..color = color..blendMode = BlendMode.plus);
}Skapa hål med BlendMode.clear
Om du vill skära ut ett genomskinligt hål i ett lager som redan har ritats, ritar du en form med BlendMode.clear. Den raderar målpixlar, det vill säga sätter alfavärdet till 0, där källan täcker dem.
Detta måste ske inuti ett saveLayer — annars skulle clear skära igenom allt bakom canvasen, inklusive widgets under den. Effekten med en spotlight eller ett överlägg med ett utskuret hål är det typiska användningsområdet.
void paint(Canvas canvas, Size size) {
final bounds = Offset.zero & size;
canvas.saveLayer(bounds, Paint());
// Dim scrim covering everything.
canvas.drawRect(bounds, Paint()..color = const Color(0xCC000000));
// Cut a clear circular "spotlight" through the scrim.
canvas.drawCircle(
size.center(Offset.zero),
size.shortestSide / 4,
Paint()..blendMode = BlendMode.clear,
);
canvas.restore();
}Lagerfärg kontra färg per form
Det finns två olika färger i spel, och att blanda ihop dem är ett vanligt fel:
- Färgen per form (som skickas till
drawCircle,drawRect…) styr hur just den formen blandas med innehållet i det aktuella lagret. - Lagerfärgen (som skickas till
saveLayer) styr hur hela det färdiga lagret blandas tillbaka när du anroparrestore(), och kan tillämpa en gruppsopacityellercolorFilter.
Vill du använda ett reglage för att tona ned en hel sammansatt grupp jämnt? Lägg opaciteten på lagerfärgen, inte på varje form — opacitet per form skulle göra att överlappningar syns genom varandra.
void paint(Canvas canvas, Size size) {
final bounds = Offset.zero & size;
// Group opacity: whole layer fades together.
final layerPaint = Paint()..color = const Color(0x80FFFFFF);
canvas.saveLayer(bounds, layerPaint);
final p = Paint()..color = const Color(0xFFE91E63);
canvas.drawCircle(const Offset(60, 60), 50, p);
canvas.drawCircle(const Offset(100, 60), 50, p); // overlap stays solid
canvas.restore(); // entire pair composited at 50% alpha
}Klippning + blandning + lager tillsammans
En frostig, rundad miniatyrbild med en gradientglans kombinerar alla tre verktygen i följande ordning:
- Klipp till en rundad rektangel så att inget hamnar utanför kortet.
saveLayerså att glansen kan blandas med fotot som en grupp.- Rita fotot och därefter en mjuk markering med
BlendMode.softLightelleroverlay.
Ordningen är avgörande: klipp först, öppna lagret, rita målet, rita den blandade källan, återställ och återställ sedan klippningen.
void paint(Canvas canvas, Size size, ui.Image photo) {
final bounds = Offset.zero & size;
canvas.save();
canvas.clipRRect(
RRect.fromRectAndRadius(bounds, const Radius.circular(20)),
);
canvas.saveLayer(bounds, Paint());
canvas.drawImageRect(
photo,
Offset.zero & Size(photo.width.toDouble(), photo.height.toDouble()),
bounds,
Paint(),
);
canvas.drawRect(
bounds,
Paint()
..shader = const LinearGradient(
begin: Alignment.topLeft, end: Alignment.bottomRight,
colors: [Color(0x88FFFFFF), Color(0x11000000)],
).createShader(bounds)
..blendMode = BlendMode.softLight,
);
canvas.restore(); // close layer
canvas.restore(); // close clip
}Prestanda: lager är inte gratis
Varje saveLayer allokerar en textur utanför skärmen och tvingar fram ett extra kompositionssteg — det överlägset mest kostsamma du kan göra per bildruta i en målare. Använd det som ett verktyg du tar till medvetet, inte som standard.
- Ange så snäva gränser som möjligt till
saveLayer; ett lager över hela skärmen är mycket dyrare än ett litet. - Föredra vanlig
clipRRect+ direkta ritningar när du faktiskt inte behöver blanda grupper. - Cachelagra statiska sammansättningar, till exempel genom
ui.PictureRecordertill enui.Image, i stället för att bygga om lagret i varje bildruta. - Återanvänd
Paint-objekt och undvik allokeringar i den hetapaint-sökvägen.
Snabbkontroll: välj rätt verktyg
Du vill fylla tecknen i fet text med en linjär gradient inuti en CustomPainter, och inget utanför tecknen ska färgas. Vilken metod är korrekt?
Sammanfattning
Du kan nu skapa avancerade lagereffekter på en Canvas:
- Klippning (
clipRect/clipRRect/clipPath) begränsar var ritningar hamnar — omslut den alltid medsave()/restore(). - Blandningslägen anger hur en källa blandas med målet:
srcIn/dstInför maskning,plusför additiv glöd,clearför utskurna hål ochsoftLight/overlayför glans. - saveLayer ger blandningslägen en riktig målbuffer och låter dig tillämpa gruppopacitet eller färgfilter via lagerfärgen.
- Skilj på lagerfärg och färg per form, rita målet före källan och håll lagren små och sällsynta för bättre prestanda.
Använd lager medvetet, klipp när du bara behöver en mask och cachelagra statiska sammansättningar.
Lär dig Dart med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 22
- Lektioner
- 88
Vanliga frågor
Är lektionen ”Beskärning, blandningslägen och lagerkomposition” gratis?
Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Mobilutveckling med Flutter, inklusive ”Beskärning, blandningslägen och lagerkomposition”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Mobilutveckling med Flutter innehåller totalt 4 lektioner.
Vad lär jag mig i ”Beskärning, blandningslägen och lagerkomposition”?
Använd beskärningsbanor och blandningslägen för att komponera avancerade visuella lagereffekter. Ni övar på Mobilutveckling med Flutter med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Mobilutveckling med Flutter?
Du behöver inga förkunskaper. Utbildningen i Mobilutveckling med Flutter på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.
Hur lång tid tar lektionen ”Beskärning, blandningslägen och lagerkomposition”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Mobilutveckling med Flutter-lektionen?
Ja. Varje Mobilutveckling med Flutter-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Canvas API: banor, penslar och shaders
- Bygg en interaktiv anpassad diagramwidget
- Beskärning, blandningslägen och lagerkomposition
- Träffdetektering och optimering av ommålning