blob: d32c1ffd8bdb44423b03d3837ed0e27920edd880 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
// This tests whether the Opacity layer raster cache works with color filters.
// See https://github.com/flutter/flutter/issues/51975.
class ColorFilterAndFadePage extends StatefulWidget {
const ColorFilterAndFadePage({super.key});
@override
State<ColorFilterAndFadePage> createState() => _ColorFilterAndFadePageState();
}
class _ColorFilterAndFadePageState extends State<ColorFilterAndFadePage> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final Widget shadowWidget = _ShadowWidget(
width: 24,
height: 24,
useColorFilter: _useColorFilter,
shadow: const ui.Shadow(
color: Colors.black45,
offset: Offset(0.0, 2.0),
blurRadius: 4.0,
),
);
final Widget row = Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
shadowWidget,
const SizedBox(width: 12),
shadowWidget,
const SizedBox(width: 12),
shadowWidget,
const SizedBox(width: 12),
shadowWidget,
const SizedBox(width: 12),
shadowWidget,
const SizedBox(width: 12),
],
);
final Widget column = Column(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
row,
const SizedBox(height: 12),
row,
const SizedBox(height: 12),
row,
const SizedBox(height: 12),
row,
const SizedBox(height: 12),
],
);
final Widget fadeTransition = FadeTransition(
opacity: _opacityAnimation,
// This RepaintBoundary is necessary to not let the opacity change
// invalidate the layer raster cache below. This is necessary with
// or without the color filter.
child: RepaintBoundary(
child: column,
),
);
return Scaffold(
backgroundColor: Colors.lightBlue,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
fadeTransition,
Container(height: 20),
const Text('Use Color Filter:'),
Checkbox(
value: _useColorFilter,
onChanged: (bool? value) {
setState(() {
_useColorFilter = value ?? false;
});
},
),
],
),
),
);
}
// Create a looping fade-in fade-out animation for opacity.
void _initAnimation() {
_controller = AnimationController(duration: const Duration(seconds: 3), vsync: this);
_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
_opacityAnimation.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
@override
void initState() {
super.initState();
_initAnimation();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
late AnimationController _controller;
late Animation<double> _opacityAnimation;
bool _useColorFilter = true;
}
class _ShadowWidget extends StatelessWidget {
const _ShadowWidget({
required this.width,
required this.height,
required this.useColorFilter,
required this.shadow,
});
final double width;
final double height;
final bool useColorFilter;
final Shadow shadow;
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height,
child: CustomPaint(
painter: _ShadowPainter(
useColorFilter: useColorFilter,
shadow: shadow,
),
size: Size(width, height),
isComplex: true,
),
);
}
}
class _ShadowPainter extends CustomPainter {
const _ShadowPainter({required this.useColorFilter, required this.shadow});
final bool useColorFilter;
final Shadow shadow;
@override
void paint(Canvas canvas, Size size) {
final Rect rect = Offset.zero & size;
final Paint paint = Paint();
if (useColorFilter) {
paint.colorFilter = ColorFilter.mode(shadow.color, BlendMode.srcIn);
}
canvas.saveLayer(null, paint);
canvas.translate(shadow.offset.dx, shadow.offset.dy);
canvas.drawRect(rect, Paint());
canvas.drawRect(rect, Paint()..maskFilter = MaskFilter.blur(BlurStyle.normal, shadow.blurSigma));
canvas.restore();
canvas.drawRect(rect, Paint()..color = useColorFilter ? Colors.white : Colors.black);
}
@override
bool shouldRepaint(_ShadowPainter oldDelegate) => oldDelegate.useColorFilter != useColorFilter;
}