Overview
ClipRect is a widget that clips its child widget using a rectangular area.
This widget takes a clipper callback function that returns a Rect object, allowing you to clip the child widget to any desired rectangular area. Internally, it uses ClipPath to perform the clipping.
See: https://api.flutter.dev/flutter/widgets/ClipRect-class.html
When to use it?
- When you want to show only a portion of a widget
- When you need to hide overflowing content
- When defining boundaries for scrollable areas
- When displaying only specific regions of images or videos
- When implementing custom crop functionality
Basic Usage
// Full area clipping (default)
ClipRect({
clipper: (size) => Rect.fromLTWH({
left: 0,
top: 0,
width: size.width,
height: size.height
}),
child: Image({
src: 'https://example.com/large-image.jpg',
width: 400,
height: 400
})
})
// Clip only center portion
ClipRect({
clipper: (size) => Rect.fromCenter({
center: { x: size.width / 2, y: size.height / 2 },
width: size.width * 0.5,
height: size.height * 0.5
}),
child: Container({
width: 200,
height: 200,
color: 'blue'
})
})
// Show only top half
ClipRect({
clipper: (size) => Rect.fromLTRB({
left: 0,
top: 0,
right: size.width,
bottom: size.height / 2
}),
child: child
})
Props Details
clipper (required)
Value: (size: Size) => Rect
A callback function that defines the clipping area. It receives the widget’s size as a parameter and must return a Rect object.
// Various clipping pattern examples
ClipRect({
// Top-left quarter only
clipper: (size) => Rect.fromLTWH({
left: 0,
top: 0,
width: size.width / 2,
height: size.height / 2
}),
child: child
})
// Horizontal center strip
ClipRect({
clipper: (size) => Rect.fromLTRB({
left: 0,
top: size.height * 0.25,
right: size.width,
bottom: size.height * 0.75
}),
child: child
})
// Clipping with margins
ClipRect({
clipper: (size) => Rect.fromLTWH({
left: 20,
top: 20,
width: size.width - 40,
height: size.height - 40
}),
child: child
})
clipped
Value: boolean (default: true)
Enables/disables the clipping effect.
true
: Apply clippingfalse
: Disable clipping (child widget displays normally)
ClipRect({
clipped: isClippingEnabled,
clipper: (size) => Rect.fromLTWH({
left: 0,
top: 0,
width: size.width,
height: size.height
}),
child: content
})
child
Value: Widget
The child widget to be clipped.
Understanding Rect API
Rect Creation Methods
// Create from left, top, width, height
Rect.fromLTWH({
left: 10,
top: 10,
width: 100,
height: 100
});
// Create from left, top, right, bottom coordinates
Rect.fromLTRB({
left: 10,
top: 10,
right: 110,
bottom: 110
});
// Create from center point and size
Rect.fromCenter({
center: { x: 60, y: 60 },
width: 100,
height: 100
});
// Rectangle enclosing a circle
Rect.fromCircle({
center: { x: 50, y: 50 },
radius: 50
});
// Create from two points
Rect.fromPoints(
{ x: 10, y: 10 }, // First point
{ x: 110, y: 110 } // Second point
);
Practical Examples
Example 1: Image Crop Tool
class ImageCropper extends StatefulWidget {
imageUrl: string;
constructor({ imageUrl }: { imageUrl: string }) {
super();
this.imageUrl = imageUrl;
}
createState(): State<ImageCropper> {
return new ImageCropperState();
}
}
class ImageCropperState extends State<ImageCropper> {
cropRect = {
x: 0,
y: 0,
width: 200,
height: 200
};
build(): Widget {
return Container({
width: 400,
height: 400,
child: Stack({
children: [
// Original image (dimmed)
Opacity({
opacity: 0.3,
child: Image({
src: this.widget.imageUrl,
width: 400,
height: 400,
objectFit: 'cover'
})
}),
// Cropped area
Positioned({
left: this.cropRect.x,
top: this.cropRect.y,
child: ClipRect({
clipper: (size) => Rect.fromLTWH({
left: 0,
top: 0,
width: this.cropRect.width,
height: this.cropRect.height
}),
child: Transform.translate({
offset: { x: -this.cropRect.x, y: -this.cropRect.y },
child: Image({
src: this.widget.imageUrl,
width: 400,
height: 400,
objectFit: 'cover'
})
})
})
}),
// Crop area border
Positioned({
left: this.cropRect.x,
top: this.cropRect.y,
child: Container({
width: this.cropRect.width,
height: this.cropRect.height,
decoration: BoxDecoration({
border: Border.all({
color: 'white',
width: 2
})
})
})
})
]
})
});
}
}
Example 2: Text Overflow Handling
function TextPreview({ text, maxLines = 3, lineHeight = 24 }): Widget {
const maxHeight = maxLines * lineHeight;
return Container({
width: 300,
child: Stack({
children: [
ClipRect({
clipper: (size) => Rect.fromLTWH({
left: 0,
top: 0,
width: size.width,
height: Math.min(size.height, maxHeight)
}),
child: Text(text, {
style: TextStyle({
fontSize: 16,
lineHeight: lineHeight
})
})
}),
// Fade out effect
Positioned({
bottom: 0,
left: 0,
right: 0,
child: Container({
height: 30,
decoration: BoxDecoration({
gradient: LinearGradient({
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: ['rgba(255,255,255,0)', 'rgba(255,255,255,1)']
})
})
})
})
]
})
});
};
Example 3: Progress Indicator
function ProgressBar({ progress, height = 20 }): Widget {
return Container({
width: 300,
height: height,
decoration: BoxDecoration({
borderRadius: BorderRadius.circular(height / 2),
color: '#E0E0E0'
}),
child: ClipRRect({
borderRadius: BorderRadius.circular(height / 2),
child: Stack({
children: [
// Progress bar
ClipRect({
clipper: (size) => Rect.fromLTWH({
left: 0,
top: 0,
width: size.width * progress,
height: size.height
}),
child: Container({
decoration: BoxDecoration({
gradient: LinearGradient({
colors: ['#4CAF50', '#8BC34A'],
begin: Alignment.centerLeft,
end: Alignment.centerRight
})
})
})
}),
// Text
Center({
child: Text(`${Math.round(progress * 100)}%`, {
style: TextStyle({
color: progress > 0.5 ? 'white' : 'black',
fontWeight: 'bold',
fontSize: 12
})
})
})
]
})
})
});
};
Example 4: Viewport Simulation
function ViewportSimulator({ content, viewportSize, scrollOffset }): Widget {
return Container({
width: viewportSize.width,
height: viewportSize.height,
decoration: BoxDecoration({
border: Border.all({
color: '#333',
width: 2
})
}),
child: ClipRect({
clipper: (size) => Rect.fromLTWH({
left: 0,
top: 0,
width: size.width,
height: size.height
}),
child: Transform.translate({
offset: {
x: -scrollOffset.x,
y: -scrollOffset.y
},
child: content
})
})
});
};
// Usage example
ViewportSimulator({
viewportSize: { width: 300, height: 400 },
scrollOffset: { x: 0, y: 100 },
content: Container({
width: 300,
height: 1000,
child: Column({
children: Array.from({ length: 20 }, (_, i) =>
Container({
height: 50,
margin: EdgeInsets.all(5),
color: i % 2 === 0 ? '#E3F2FD' : '#BBDEFB',
child: Center({
child: Text(`Item ${i + 1}`)
})
})
)
})
})
});
Example 5: Image Comparison Slider
class ImageComparisonSlider extends StatefulWidget {
beforeImage: string;
afterImage: string;
constructor({ beforeImage, afterImage }: { beforeImage: string; afterImage: string }) {
super();
this.beforeImage = beforeImage;
this.afterImage = afterImage;
}
createState(): State<ImageComparisonSlider> {
return new ImageComparisonSliderState();
}
}
class ImageComparisonSliderState extends State<ImageComparisonSlider> {
sliderPosition = 0.5;
build(): Widget {
return GestureDetector({
onHorizontalDragUpdate: (details) => {
const newPosition = Math.max(0, Math.min(1,
details.localPosition.x / 400
));
this.setState(() => {
this.sliderPosition = newPosition;
});
},
child: Container({
width: 400,
height: 300,
child: Stack({
children: [
// After image (full)
Image({
src: this.widget.afterImage,
width: 400,
height: 300,
objectFit: 'cover'
}),
// Before image (clipped)
ClipRect({
clipper: (size) => Rect.fromLTWH({
left: 0,
top: 0,
width: size.width * this.sliderPosition,
height: size.height
}),
child: Image({
src: this.widget.beforeImage,
width: 400,
height: 300,
objectFit: 'cover'
})
}),
// Slider line
Positioned({
left: 400 * this.sliderPosition - 2,
top: 0,
bottom: 0,
child: Container({
width: 4,
color: 'white',
child: Center({
child: Container({
width: 40,
height: 40,
decoration: BoxDecoration({
color: 'white',
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow({
color: 'rgba(0,0,0,0.3)',
blurRadius: 4,
offset: { x: 0, y: 2 }
})
]
}),
child: Center({
child: Icon({
icon: Icons.dragHandle,
color: '#666'
})
})
})
})
})
})
]
})
})
});
}
}
Important Notes
- ClipRect can affect rendering performance, so use it only when necessary
- Touch events outside the clipped area are not detected
- The clipper function is called whenever the widget size changes, so avoid complex calculations
- Consider performance optimization when using with animations
- Nested clipping can cause performance issues, so use with caution
Related Widgets
- ClipOval: Clips with an oval
- ClipRRect: Clips with a rounded rectangle
- ClipPath: Clips with a custom path
- CustomClipper: Implements custom clipping logic
- Viewport: Defines scrollable areas