Skip to content

Instantly share code, notes, and snippets.

@av
Created January 13, 2020 18:44
Show Gist options
  • Save av/014f3ae1b2842d5ac6e559781ef4e380 to your computer and use it in GitHub Desktop.
Save av/014f3ae1b2842d5ac6e559781ef4e380 to your computer and use it in GitHub Desktop.
Flutter: neu
import 'package:flutter/material.dart';
void main() => runApp(NeumorphicApp());
class NeumorphicApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Neumorphic App',
theme: ThemeData(
primarySwatch: Colors.blue,
backgroundColor: Colors.blueGrey.shade200,
scaffoldBackgroundColor: Colors.blueGrey.shade200,
dialogBackgroundColor: Colors.blueGrey.shade200,
),
home: Scaffold(
body: Center(
child: NeumorphicContainer(
child: Text(
'Space',
style: Typography.blackCupertino.display2,
),
),
),
),
);
}
}
class NeumorphicContainer extends StatefulWidget {
final Widget child;
final double bevel;
final Offset blurOffset;
final Color color;
NeumorphicContainer({
Key key,
this.child,
this.bevel = 10.0,
this.color,
}) : this.blurOffset = Offset(bevel / 2, bevel / 2),
super(key: key);
@override
_NeumorphicContainerState createState() => _NeumorphicContainerState();
}
class _NeumorphicContainerState extends State<NeumorphicContainer> {
bool _isPressed = false;
void _onPointerDown(PointerDownEvent event) {
setState(() {
_isPressed = true;
});
}
void _onPointerUp(PointerUpEvent event) {
setState(() {
_isPressed = false;
});
}
@override
Widget build(BuildContext context) {
final color = this.widget.color ?? Theme.of(context).backgroundColor;
return Listener(
onPointerDown: _onPointerDown,
onPointerUp: _onPointerUp,
child: AnimatedContainer(
duration: const Duration(milliseconds: 150),
padding: const EdgeInsets.all(24.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(widget.bevel * 10),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
_isPressed ? color : color.mix(Colors.black, .1),
_isPressed ? color.mix(Colors.black, .05) : color,
_isPressed ? color.mix(Colors.black, .05) : color,
color.mix(Colors.white, _isPressed ? .2 : .5),
],
stops: [
0.0,
.3,
.6,
1.0,
]),
boxShadow: _isPressed ? null : [
BoxShadow(
blurRadius: widget.bevel,
offset: -widget.blurOffset,
color: color.mix(Colors.white, .6),
),
BoxShadow(
blurRadius: widget.bevel,
offset: widget.blurOffset,
color: color.mix(Colors.black, .3),
)
],
),
child: widget.child,
),
);
}
}
extension ColorUtils on Color {
Color mix(Color another, double amount) {
return Color.lerp(this, another, amount);
}
}
@tieorange
Copy link

Example without extension methods
For those who don't use Flutter's master channel yet

import 'package:flutter/material.dart';

class NeumorphicContainer extends StatefulWidget {
  final Widget child;
  final double bevel;
  final Offset blurOffset;
  final Color color;

  NeumorphicContainer({
    Key key,
    this.child,
    this.bevel = 10.0,
    this.color,
  })  : this.blurOffset = Offset(bevel / 2, bevel / 2),
        super(key: key);

  @override
  _NeumorphicContainerState createState() => _NeumorphicContainerState();
}

class _NeumorphicContainerState extends State<NeumorphicContainer> {
  bool _isPressed = false;

  void _onPointerDown(PointerDownEvent event) {
    setState(() {
      _isPressed = true;
    });
  }

  void _onPointerUp(PointerUpEvent event) {
    setState(() {
      _isPressed = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    final color = this.widget.color ?? Theme.of(context).backgroundColor;

    return Listener(
      onPointerDown: _onPointerDown,
      onPointerUp: _onPointerUp,
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 150),
        padding: const EdgeInsets.all(24.0),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(widget.bevel * 10),
          gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [
            _isPressed ? color : mix(Colors.black, .1, color),
            _isPressed ? mix(Colors.black, .05, color) : color,
            _isPressed ? mix(Colors.black, .05, color) : color,
            mix(Colors.white, _isPressed ? .2 : .5, color),
          ], stops: [
            0.0,
            .3,
            .6,
            1.0,
          ]),
          boxShadow: _isPressed
              ? null
              : [
                  BoxShadow(
                    blurRadius: widget.bevel,
                    offset: -widget.blurOffset,
                    color: mix(Colors.white, .6, color),
                  ),
                  BoxShadow(
                    blurRadius: widget.bevel,
                    offset: widget.blurOffset,
                    color: mix(Colors.black, .3, color),
                  )
                ],
        ),
        child: widget.child,
      ),
    );
  }
}

Color mix(Color another, double amount, Color currentColor) {
  return Color.lerp(currentColor, another, amount);
}

@av
Copy link
Author

av commented Feb 10, 2020

Thanks for sharing this @tieorange!
Will be useful for people still having SDK constrained to versions <=2.5!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment