Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Portable Material: Flutter physical models 1.1

Portable Material: Flutter physical models 1.1

One of the most interesting new technologies to reach the mainstream eye in 2017 is undoubtedly Flutter, a novel cross-platform framework from Google that targets Android, iOS and the oh-so-elusive Fuchsia OS.

Whether you’ve heard of Flutter or not, you will be fascinated by the intriguing design that lurks under the Surface. We’ll dive in to how exactly your Flutter widgets get drawn on screen, from your code all the way down to Skia. Did you know that the UI toolkit in Flutter is based on physical models, and that Material is just one of those? Or that in Flutter the Material and Cupertino (iOS) toolkits share a common heritage in their roots?

---

Presented with Eugenio Marletti at Android Makers 2018, Paris.

---

— PDF copy and Keynote source available here: https://www.dropbox.com/sh/jtkl8a2lv3vwxdb/AAC5ADc5HDUJ7RDHzin5ODGya?dl=0

Sebastiano Poggi

April 24, 2018
Tweet

More Decks by Sebastiano Poggi

Other Decks in Technology

Transcript

  1. Android Forget about KitKat No ripples No rounded corners No

    elevation/shadows (efficiently) (efficiently)
  2. Views Background It’s a Drawable To be precise: ShapeDrawable GradientDrawable

    no, it doesn’t have a gradient all about the stroke naming! right?
  3. No drawing done so far Deferred drawing Views compile DisplayLists

    Display lists are sent down to the RenderThread
  4. What about the rest? Elevation shadow Lives in the view’s

    main RenderNode RenderNode has outline and elevation Outline can be customised ShadowTesselator …and so on
  5. 64 bit 32 bit WIP API 16+ x86 WIP On

    all supported platforms
  6. Platform thread UI thread GPU thread I/O thread ~ main

    thread ~ RenderThread ~ AsyncTask Threading
  7. import 'package:flutter/material.dart'; void main() => runApp( new Container( alignment: Alignment.center,

    color: const Color.fromARGB(255, 40, 185, 152), child: new SizedBox( width: 240.0, height: 120.0, child: new Card(), ), ), );
  8. import 'package:flutter/material.dart'; void main() => runApp(new Container(alignment: Alignment.center, color: const

    Color.fromARGB(255, 40, 185, 152), child: new SizedBox(width: 240.0, height: 120.0, child: new Card()))); Fits on one line!!1!
  9. Composition A build() function import 'package:flutter/material.dart'; @override Widget build(context) =>

    new Container( alignment: Alignment.center, color: const Color.fromARGB(255, 40, 185, 152), child: new SizedBox( width: 240.0, height: 120.0, child: new Card(), ), );
  10. import 'package:flutter/material.dart'; void main() => runApp( Container( alignment: Alignment.center, color:

    Colors.grey[50], child: SizedBox( width: 240.0, height: 120.0, child: Card( child: ), ), ), ); class _CardContentsPlaceholder extends SizedBox {} InkWell( onTap: () {}, ),
  11. import 'package:flutter/material.dart'; void main() => runApp( Container( alignment: Alignment.center, color:

    Colors.grey[50], child: SizedBox( width: 240.0, height: 120.0, child: Card( child: _CardContentsPlaceholder() ), ), ), ); _CardContentsPlaceholder() class _CardContentsPlaceholder extends SizedBox {} ,
  12. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Material ↳ _CardContentsPlac // packages/flutter/lib/src/material/card.dart class Card extends StatelessWidget { Card({ Key key, this.color, this.elevation: 2.0, this.child, }) : super(key: key); final Widget child; final Color color; final double elevation; @override Widget build(context) => Semantics( container: true, child: Container( margin: EdgeInsets.all(4.0), child: Material( color: color, type: MaterialType.card, elevation: elevation, child: child, ), ), ); }
  13. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _CardContentsPla // packages/flutter/lib/src/material/card.dart class Card extends StatelessWidget { Card({ Key key, this.color, this.elevation: 2.0, this.child, }) : super(key: key); final Widget child; final Color color; final double elevation; @override Widget build(context) => Semantics( container: true, child: Container( margin: EdgeInsets.all(4.0), child: Material( color: color, type: MaterialType.card, elevation: elevation, child: child, ), ), ); }
  14. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _CardContentsPla // flutter/packages/flutter/lib/src/material/material.dart class Material extends StatefulWidget { Material({ Key key, this.type: MaterialType.canvas, this.elevation: 0.0, this.color, this.shadowColor: Color(0xFF000000), this.textStyle, this.borderRadius, this.shape, this.animationDuration: kThemeChangeDuration, this.child, }) : assert(type != null), assert(elevation != null), assert(shadowColor != null), assert(!(shape != null && borderRadius != null)), assert(animationDuration != null), assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null))), super(key: key); // ... @override _MaterialState createState() => _MaterialState(); } class _MaterialState extends State<Material> with TickerProviderStateMixin { @override Widget build(context) { // ... } }
  15. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _CardContentsPlace // flutter/packages/flutter/lib/src/material/material.dart class Material extends StatefulWidget { Material({ Key key, this.type: MaterialType.canvas, this.elevation: 0.0, this.color, this.shadowColor: Color(0xFF000000), this.textStyle, this.borderRadius, this.shape, this.animationDuration: kThemeChangeDuration, this.child, }) : assert(type != null), assert(elevation != null), assert(shadowColor != null), assert(!(shape != null && borderRadius != null)), assert(animationDuration != null), assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null))), super(key: key); // ... @override _MaterialState createState() => _MaterialState(); class _MaterialState extends State<Material> with TickerProviderStateMixin { @override Widget build(context) { // ... } } }
  16. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _CardContentsPlace // flutter/packages/flutter/lib/src/material/material.dart class _MaterialState extends State<Material> with TickerProviderStateMixin { @override Widget build(context) { // ... } }
  17. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ AnimatedDefaultTextStyle ↳ _CardContentsPlaceholde // flutter/packages/flutter/lib/src/material/material.dart class _MaterialState extends State<Material> with TickerProviderStateMixin { @override Widget build(context) { // ... Widget contents = AnimatedDefaultTextStyle( style: widget.textStyle ?? Theme .of(context) .textTheme .body1, duration: widget.animationDuration, child: widget.child, ); // ... } }
  18. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ AnimatedDefaultTextStyle ↳ DefaultTextStyle ↳ _CardContentsPlacehold // flutter/packages/flutter/lib/src/material/material.dart class _MaterialState extends State<Material> with TickerProviderStateMixin { @override Widget build(context) { // ... Widget contents = AnimatedDefaultTextStyle( style: widget.textStyle ?? Theme .of(context) .textTheme .body1, duration: widget.animationDuration, child: widget.child, ); // ... } }
  19. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextStyl ↳ DefaultTextStyle ↳ _CardContentsPlac // flutter/packages/flutter/lib/src/material/material.dart class _MaterialState extends State<Material> with TickerProviderStateMixin { @override Widget build(context) { // ... Widget contents = AnimatedDefaultTextStyle( style: widget.textStyle ?? Theme.of(context).textTheme.body1, duration: widget.animationDuration, child: widget.child, ); final Color backgroundColor = _getBackgroundColor(context); contents = NotificationListener<LayoutChangedNotification>( onNotification: (notification) { _inkFeatureRenderer.currentContext .findRenderObject() ._didChangeLayout(); return true; }, child: _InkFeatures( key: _inkFeatureRenderer, color: backgroundColor, child: contents, vsync: this, ), ); // ... }
  20. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextSt ↳ DefaultTextStyle ↳ _CardContentsPl // flutter/packages/flutter/lib/src/material/material.dart class _MaterialState extends State<Material> with TickerProviderStateMixin { @override Widget build(context) { // ... Widget contents = AnimatedDefaultTextStyle( style: widget.textStyle ?? Theme.of(context).textTheme.body1, duration: widget.animationDuration, child: widget.child, ); final Color backgroundColor = _getBackgroundColor(context); contents = NotificationListener<LayoutChangedNotification>( onNotification: (notification) { _inkFeatureRenderer.currentContext .findRenderObject() ._didChangeLayout(); return true; }, child: _InkFeatures( key: _inkFeatureRenderer, color: backgroundColor, child: contents, vsync: this, ), ); // ... }
  21. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextSt ↳ DefaultTextStyle ↳ _CardContentsPl // flutter/packages/flutter/lib/src/material/material.dart /// The interior of non-transparent material. /// /// Animates [elevation], [shadowColor], and [shape]. class _MaterialInterior extends ImplicitlyAnimatedWidget { // ... extends StatefulWidget }
  22. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextSt ↳ DefaultTextStyle ↳ _CardContentsPl // flutter/packages/flutter/lib/src/material/material.dart /// The interior of non-transparent material. /// /// Animates [elevation], [shadowColor], and [shape]. class _MaterialInterior extends ImplicitlyAnimatedWidget { // ... extends StatefulWidget }
  23. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextSt ↳ DefaultTextStyle ↳ _CardContentsPl // flutter/packages/flutter/lib/src/material/material.dart /// The interior of non-transparent material. /// /// Animates [elevation], [shadowColor], and [shape]. class _MaterialInterior extends ImplicitlyAnimatedWidget { // ... @override _MaterialInteriorState createState() => _MaterialInteriorState(); extends StatefulWidget }
  24. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultText ↳ DefaultTextStyle ↳ _CardContents // flutter/packages/flutter/lib/src/material/material.dart /// The interior of non-transparent material. /// /// Animates [elevation], [shadowColor], and [shape]. class _MaterialInterior extends ImplicitlyAnimatedWidget { // ... @override _MaterialInteriorState createState() => _MaterialInteriorState(); }
  25. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTe ↳ DefaultTextSty ↳ _CardConten // flutter/packages/flutter/lib/src/material/material.dart /// The interior of non-transparent material. /// /// Animates [elevation], [shadowColor], and [shape]. class _MaterialInterior extends ImplicitlyAnimatedWidget { // ... @override _MaterialInteriorState createState() => _MaterialInteriorState(); }
  26. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextStyle↳ ↳ DefaultTextStyle ↳ _CardContentsPlaceholder↳
  27. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextStyle↳ ↳ DefaultTextStyle
  28. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextStyle↳ ↳ DefaultTextStyle
  29. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationLis ↳ _InkFeatures ↳ AnimatedD ↳ Defaul // flutter/packages/flutter/lib/src/widgets/framework.dart @immutable abstract class Widget extends DiagnosticableTree { // ... @protected Element createElement(); // ... }
  30. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationLis ↳ _InkFeatures ↳ AnimatedD ↳ Defaul // flutter/packages/flutter/lib/src/widgets/framework.dart abstract class Element extends DiagnosticableTree implements BuildContext { // ... }
  31. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationLis ↳ _InkFeatures ↳ AnimatedD ↳ Defaul void main() => runApp(MyApp()); class MyApp extends StatelessWidget { static const _buttonText = Text("Let there be Snackbar..."); static const _snackbarText = Text("...and there was Snackbar"); static const _snackbar = SnackBar(content: _snackbarText); @override Widget build(context) => MaterialApp( home: Scaffold( body: Center( child: RaisedButton( child: _buttonText, onPressed: () => Scaffold.of(context) .showSnackBar(_snackbar), ), ), ), ); }
  32. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationLis ↳ _InkFeatures ↳ AnimatedD ↳ Defaul void main() => runApp(MyApp()); class MyApp extends StatelessWidget { static const _buttonText = Text("Let there be Snackbar..."); static const _snackbarText = Text("...and there was Snackbar"); static const _snackbar = SnackBar(content: _snackbarText); @override Widget build(context) => MaterialApp( home: Scaffold( body: Center( child: Builder( builder: (context) => RaisedButton( child: _buttonText, onPressed: () => Scaffold.of(context).showSnackBar(_snackbar), ), ), ), ), ); }
  33. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationLis ↳ _InkFeatures ↳ AnimatedD ↳ Defaul // flutter/packages/flutter/lib/src/widgets/framework.dart abstract class Element extends DiagnosticableTree implements BuildContext { // ... }
  34. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationLis ↳ _InkFeatures ↳ AnimatedD ↳ Defaul // flutter/packages/flutter/lib/src/widgets/framework.dart abstract class Element extends DiagnosticableTree implements BuildContext { // ... } RenderObject get renderObject { // ... } // ...
  35. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationLis ↳ _InkFeatures ↳ AnimatedD ↳ Defaul // flutter/packages/flutter/lib/src/rendering/object.dart abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget { // ... }
  36. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationLis ↳ _InkFeatures ↳ AnimatedD ↳ Defaul // flutter/packages/flutter/lib/src/widgets/basic.dart /// Creates a physical model with an arbitrary shape clip. class PhysicalShape extends SingleChildRenderObjectWidget { // ... @override RenderPhysicalShape createRenderObject(BuildContext context) => RenderPhysicalShape( clipper: clipper, elevation: elevation, color: color, shadowColor: shadowColor, ); @override void updateRenderObject( BuildContext context, RenderPhysicalShape renderObject) { renderObject ..clipper = clipper ..elevation = elevation ..color = color ..shadowColor = shadowColor; } }
  37. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextStyle↳ ↳ DefaultTextStyle
  38. ↳ Container ↳ DecoratedBox ↳ Align ↳ SizedBox ↳ Card

    ↳ Semantics ↳ Container ↳ Padding ↳ Material ↳ _MaterialInterior ↳ PhysicalShape ↳ _ShapeBorderPaint ↳ CustomPaint ↳ NotificationListener ↳ _InkFeatures ↳ AnimatedDefaultTextStyle↳ ↳ DefaultTextStyle ↳ ↳ RenderDecoratedBox ↳ RenderPositionedBox ↳ RenderConstrainedBox ↳ ↳ RenderSemanticsAnnotations ↳ ↳ RenderPadding ↳ ↳ ↳ RenderPhysicalShape ↳ RenderCustomPaint ↳ ↳ ↳ _RenderInkFeatures↳
  39. // flutter/packages/flutter/lib/src/rendering/proxy_box.dart class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> { // ... @override

    void paint(PaintingContext context, Offset offset) { // ... if (elevation != 0.0) { canvas.drawShadow( offsetPath, shadowColor, elevation, color.alpha != 0xFF, ); } // ... } // ... }
  40. // flutter/bin/cache/pkg/sky_engine/lib/ui/painting.dart class Canvas extends NativeFieldWrapperClass2 { // ... ///

    Draws a shadow for a [Path] representing the given /// material elevation. void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) { assert(path != null); assert(color != null); assert(transparentOccluder != null); _drawShadow(path, color.value, elevation, transparentOccluder); } void _drawShadow(Path path, int color, double elevation, bool transparentOccluder) native 'Canvas_drawShadow'; }
  41. // engine/lib/ui/painting/canvas.cc namespace blink { // ... void Canvas::drawShadow(const CanvasPath*

    path, SkColor color, double elevation, bool transparentOccluder) { SkScalar dpr = UIDartState::Current()->window()-> viewport_metrics().device_pixel_ratio; flow::PhysicalShapeLayer::DrawShadow( canvas_, path->path(), color, elevation, transparentOccluder, dpr ); } // ... }
  42. // engine/flow/layers/physical_shape_layer.cc #include "third_party/skia/include/utils/SkShadowUtils.h" namespace flow { //... void PhysicalShapeLayer::DrawShadow(

    SkCanvas* canvas, const SkPath& path, SkColor color, float elevation, bool transparentOccluder, SkScalar dpr ) { // ... SkShadowFlags flags = // ... const SkRect& bounds = path.getBounds(); SkScalar shadow_x = (bounds.left() + bounds.right()) / 2; SkScalar shadow_y = bounds.top() - 600.0f; SkColor inAmbient = SkColorSetA(color, kAmbientAlpha * SkColorGetA(color)); SkColor inSpot = SkColorSetA(color, kSpotAlpha * SkColorGetA(color)); SkColor ambientColor, spotColor; SkShadowUtils::ComputeTonalColors(inAmbient, inSpot, &ambientColor, &spotColor); SkShadowUtils::DrawShadow(canvas, path, SkPoint3::Make(0, 0, dpr * elevation), SkPoint3::Make(shadow_x, shadow_y, dpr * kLightHeight), dpr * kLightRadius, ambientColor, spotColor, flags); } // ... }