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 clipping
  • false: 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
  • 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