Posted by Yarden Eitan, Software Engineer
I am Yarden, an iOS engineer for Material Design—Google's open-source system for designing and building excellent user interfaces. I help build and maintain our iOS components, but I'm also the engineering lead for Material's shape system.
You can't have a UI without shape. Cards, buttons, sheets, text fields—and just about everything else you see on a screen—are often displayed within some kind of "surface" or "container." For most of computing's history, that's meant rectangles. Lots of rectangles.
But the Material team knew there was potential in giving designers and developers the ability to systematically apply unique shapes across all of our Material Design UI components. Rounded corners! Angular cuts! For designers, this means being able to create beautiful interfaces that are even better at directing attention, expressing brand, and supporting interactions. For developers, having consistent shape support across all major platforms means we can easily apply and customize shape across apps.
My role as engineering lead was truly exciting—I got to collaborate with our design leads to scope the project and find the best way to create this complex new system. Compared to systems for typography and color (which have clear structures and precedents like the web's H1-H6 type hierarchy, or the idea of primary/secondary colors) shape is the Wild West. It's a relatively unexplored terrain with rules and best practices still waiting to be defined. To meet this challenge, I got to work with all the different Material Design engineering platforms to identify possible blockers, scope the effort, and build it!
When building out the system, we had two high level goals:
From an engineering perspective, adding shape support held the bulk of the work and complexities, whereas theming had more design-driven challenges. In this post, I'll mostly focus on the engineering work and how we added shape support to our components.
Here's a rundown of what I'll cover here:
Our first task was to scope out two questions: 1) What is shape support? and 2) What functionality should it provide? Initially our goals were somewhat ambitious. The original proposal suggested an API to customize components by edges and corners, with full flexibility on how these edges and corners look. We even thought about receiving a custom .png file with a path and converting it to a shaped component in each respective platform.
We soon found that having no restrictions would make it extremely hard to define such a system. More flexibility doesn't necessarily mean a better result. For example, it'd be quite a feat to define a flexible and easy API that lets you make a snake-shaped FAB and train-shaped cards. But those elements would almost certainly contradict the clear and straightforward approach championed by Material Design guidance.
This truck-shaped FAB is a definite "don't" in Material Design guidance.
We had to weigh the expense of time and resources against the added value for each functionality we could provide.
To solve these open questions we decided to conduct a full weeklong workshop including team members from design, engineering, and tooling. It proved to be extremely effective. Even though there were a lot of inputs, we were able to hone down what features were feasible and most impactful for our users. Our final proposal was to make the initial system support three types of shapes: square, rounded, and cut. These shapes can be achieved through an API customizing a component's corners.
Anyone who's built for multiple platforms knows that consistency is key. But during our workshop, we realized how difficult it would be to provide the exact same functionality for all our platforms: Android, Flutter, iOS, and the web. Our biggest blocker? Getting cut corners to work on the web.
Unlike sharp or rounded corners, cut corners do not have a built-in native solution on the web.
Our web team looked at a range of solutions—we even considered the idea of adding background-colored squares over each corner to mask it and make it appear cut. Of course, the drawbacks there are obvious: Shadows are masked and the squares themselves need to act as chameleons when the background isn't static or has more than one color.
We then investigated the Houdini (paint worklet) API along with polyfill which initially seemed like a viable solution that would actually work. However, adding this support would require additional effort:
Even if we'd decided to add more engineering effort and go down the Houdini path, the question of value vs cost still remained, especially with Houdini still being "not ready" across the web ecosystem.
Based on our research and weighing the cost of the effort, we ultimately decided to move forward without supporting cut corners for web UIs (at least for now). But the good news was that we have spec-ed out the requirements and could start building!
After honing down the feature set, it was up to the engineers of each platform to go and start building. I helped build out shape support for iOS. Here's how we did it:
In iOS, the basic building block of user interfaces is based on instances of the UIView class. Each UIView is backed by a CALayer instance to manage and display its visual content. By modifying the CALayer's properties, you can modify various properties of its visual appearance, like color, border, shadow, and also the geometry.
UIView
CALayer
When we refer to a CALayer's geometry, we always talk about it in the form of a rectangle.
Its frame is built from an (x, y) pair for position and a (width, height) pair for size. The main API for manipulating the layer's rectangular shape is by setting its cornerRadius, which receives a radius value, and in turn sets its four corners to be rounded by that value. The notion of a rectangular backing and an easy API for rounded corners exists pretty much across the board for Android, Flutter, and the web. But things like cut corners and custom edges are usually not as straightforward. To be able to offer these features we built a shape library that provides a generator for creating CALayers with specific, well-defined shape attributes.
cornerRadius
Thankfully, Apple provides us with the class CAShapeLayer, which subclasses CALayer and has a customPath property. Assigning this property to a custom CGPath allows us to create any shape we want.
CAShapeLayer
customPath
CGPath
With the path capabilities in mind, we then built a class that leverages the CGPath APIs and provides properties that our users will care about when shaping their components. Here is the API:
/** An MDCShapeGenerating for creating shaped rectangular CGPaths. By default MDCRectangleShapeGenerator creates rectangular CGPaths. Set the corner and edge treatments to shape parts of the generated path. */ @interface MDCRectangleShapeGenerator : NSObject <MDCShapeGenerating> /** The corner treatments to apply to each corner. */ @property(nonatomic, strong) MDCCornerTreatment *topLeftCorner; @property(nonatomic, strong) MDCCornerTreatment *topRightCorner; @property(nonatomic, strong) MDCCornerTreatment *bottomLeftCorner; @property(nonatomic, strong) MDCCornerTreatment *bottomRightCorner; /** The offsets to apply to each corner. */ @property(nonatomic, assign) CGPoint topLeftCornerOffset; @property(nonatomic, assign) CGPoint topRightCornerOffset; @property(nonatomic, assign) CGPoint bottomLeftCornerOffset; @property(nonatomic, assign) CGPoint bottomRightCornerOffset; /** The edge treatments to apply to each edge. */ @property(nonatomic, strong) MDCEdgeTreatment *topEdge; @property(nonatomic, strong) MDCEdgeTreatment *rightEdge; @property(nonatomic, strong) MDCEdgeTreatment *bottomEdge; @property(nonatomic, strong) MDCEdgeTreatment *leftEdge; /** Convenience to set all corners to the same MDCCornerTreatment instance. */ - (void)setCorners:(MDCCornerTreatment *)cornerShape; /** Convenience to set all edge treatments to the same MDCEdgeTreatment instance. */ - (void)setEdges:(MDCEdgeTreatment *)edgeShape;
By providing such an API, a user can generate a path for only a corner or an edge, and the MDCRectangleShapeGenerator class above will create a shape with those properties in mind. For this initial implementation of our initial shape system, we used only the corner properties.
MDCRectangleShapeGenerator
As you can see, the corners themselves are made of the class MDCCornerTreatment, which encapsulates three pieces of important information:
MDCCornerTreatment
To make things even simpler, we didn't want our users to have to build the custom corner by calculating the corner path, so we provided 3 convenient subclasses for our MDCCornerTreatment that generate a rounded, curved, and cut corner.
As an example, our cut corner treatment receives a value called a "cut"—which defines the angle and size of the cut based on the number of UI points starting from the edge of the corner, and going an equal distance on the X axis and the Y axis. If the shape is a square with a size of 100x100, and we have all its corners set with MDCCutCornerTreatment and a cut value of 50, then the final result will be a diamond with a size of 50x50.
MDCCutCornerTreatment
Here's how the cut corner treatment implements the path generator:
- (MDCPathGenerator *)pathGeneratorForCornerWithAngle:(CGFloat)angle andCut:(CGFloat)cut { MDCPathGenerator *path = [MDCPathGenerator pathGeneratorWithStartPoint:CGPointMake(0, cut)]; [path addLineToPoint:CGPointMake(MDCSin(angle) * cut, MDCCos(angle) * cut)]; return path; }
The cut corner's path only cares about the 2 points (one on each edge of the corner) that dictate the cut. The points are (0, cut) and (sin(angle) * cut, cos(angle) * cut). In our case—because we are talking only about rectangles where their corner is 90 degrees—the latter point is equivalent to (cut, 0) where sin(90) = 1 and cos(90) = 0
Here's how the rounded corner treatment implements the path generator:
- (MDCPathGenerator *)pathGeneratorForCornerWithAngle:(CGFloat)angle andRadius:(CGFloat)radius { MDCPathGenerator *path = [MDCPathGenerator pathGeneratorWithStartPoint:CGPointMake(0, radius)]; [path addArcWithTangentPoint:CGPointZero toPoint:CGPointMake(MDCSin(angle) * radius, MDCCos(angle) * radius) radius:radius]; return path; }
From the starting point of (0, radius) we draw an arc of a circle to the point (sin(angle) * radius, cos(angle) * radius) which—similarly to the cut example—translates to (radius, 0). Lastly, the radius value is the radius of the arc.
After providing an MDCRectangleShapeGenerator with the convenient APIs for setting the corners and edges, we then needed to add a property for each of our components to receive the shape generator and apply the shape to the component.
Each supported component now has a shapeGenerator property in its API that can receive an MDCShapeGenerator or any different shape generator that implements the pathForSize method: Given the width and height of the component, it returns a CGPath of the shape. We also needed to make sure that the path generated is then applied to the underlying CALayer of the component's UIView for it to be displayed.
shapeGenerator
MDCShapeGenerator
pathForSize
By applying the shape generator's path on the component, we had to keep a couple things in mind:
Adding proper shadow, border, and background color support
Because the shadows, borders, and background colors are part of the default UIView API and don't necessarily take into account custom CALayer paths (they follow the default rectangular bounds), we needed to provide additional support. So we implemented MDCShapedShadowLayer to be the view's main CALayer. What this class does is take the shape generator path, and then passes that path to be the layer's shadow path—so the shadow will follow the custom shape. It also provides different APIs for setting the background color and border color/width by explicitly setting the values on the CALayer that holds the custom path, rather than invoking the top level UIView APIs. As an example, when setting the background color to black (instead of invoking UIView's backgroundColor) we invoke CALayer's fillColor.
MDCShapedShadowLayer
backgroundColor
fillColor
Being conscious of setting layer's properties such as shadowPath and cornerRadius
Because the shape's layer is set up differently than the view's default layer, we need to be conscious of places where we set our layer's properties in our existing component code. As an example, setting the cornerRadius of a component—which is the default way to set rounded corners using Apple's API—will actually not be applicable if you also set a custom shape.
Supporting touch events
Receiving touch also applies only on the original rectangular bounds of the view. With a custom shape, we'll have cases where there are places in the rectangular bounds where the layer isn't drawn, or places outside the bounds where the layer is drawn. So we needed a way to support proper touch that corresponds to where the shape is and isn't, and act accordingly.
To achieve this, we override the hitTest method of our UIView. The hitTest method is responsible for returning the view supposed to receive the touch. In our case, we implemented it so it returns the custom shape's view if the touch event is contained inside the generated shape path:
hitTest
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (self.layer.shapeGenerator) { if (CGPathContainsPoint(self.layer.shapeLayer.path, nil, point, true)) { return self; } else { return nil; } } return [super hitTest:point withEvent:event]; }
Ink Ripple Support
As with the other properties, our ink ripple (which provides a ripple effect to the user as touch feedback) is also built on top of the default rectangular bounds. For ink, there are two things we update: 1) the maxRippleRadius and 2) the masking to bounds. The maxRippleRadius must be updated in cases where the shape is either smaller or bigger than the bounds. In these cases we can't rely on the bounds because for smaller shapes the ink will ripple too fast, and for bigger shapes the ripple won't cover the entire shape. The ink layer's maskToBounds needs to also be set to NO so we can allow the ink to spread outside of the bounds when the custom shape is bigger than the default bounds.
maxRippleRadius
maskToBounds
- (void)updateInkForShape { CGRect boundingBox = CGPathGetBoundingBox(self.layer.shapeLayer.path); self.inkView.maxRippleRadius = (CGFloat)(MDCHypot(CGRectGetHeight(boundingBox), CGRectGetWidth(boundingBox)) / 2 + 10.f); self.inkView.layer.masksToBounds = NO; }
With all the implementation complete, here are per-platform examples of how to provide cut corners to a Material Button component:
Android:
Kotlin
button.background as? MaterialShapeDrawable?.let { it.shapeAppearanceModel.apply { cornerFamily = CutCornerTreatment(cornerSize) } }
XML:
<com.google.android.material.button.MaterialButton android:layout_width="wrap_content" android:layout_height="wrap_content" app:shapeAppearanceOverlay="@style/MyShapeAppearanceOverlay"/> <style name="MyShapeAppearanceOverlay"> <item name="cornerFamily">cut</item> <item name="cornerSize">4dp</item> <style>
Flutter:
FlatButton( shape: BeveledRectangleBorder( // Despite referencing circles and radii, this means "make all corners 4.0". borderRadius: BorderRadius.all(Radius.circular(4.0)), ),
iOS:
MDCButton *button = [[MDCButton alloc] init]; MDCRectangleShapeGenerator *rectShape = [[MDCRectangleShapeGenerator alloc] init]; [rectShape setCorners:[MDCCutCornerTreatment alloc] initWithCut:4]]]; button.shapeGenerator = rectShape;
Web (rounded corners):
.my-button { @include mdc-button-shape-radius(4px); }
I'm really excited to have tackled this problem and have it be part of the Material Design system. I'm particularly happy to have worked so collaboratively with design. As an engineer, I tend to tackle problems more or less from similar angles, and also think about problems very similarly to other engineers. But when solving problems together with designers, it feels like the challenge is actually looked at from all the right angles (pun intended), and the solution often turns out to be better and more thoughtful.
We're in good shape to continue growing the Material shape system and offering even more support for things like edge treatments and more complicated shapes. One day (when Houdini is ready) we'll even be able to support cut corners on the web.
Please check our code out on GitHub across the different platforms: Android, Flutter, iOS, Web. And check out our newly updated Material Design guidance on shape.
Material Components lets you build easily for Android, iOS, and the web using open-source code for Material Design, a shared set of principles uniting style, brand, interaction, and motion.
These components are regularly updated by a team of engineers and designers to follow the latest Material Design guidelines, ensuring well-crafted implementations that meet development standards such as internationalization and accessibility support.
Pixel-perfect components for Android, iOS, and the web
Maintained by Google engineers and designers, using the latest APIs and features.
The code on GitHub is available for you to contribute or simply use elements as needed
Also used in Google's products, these components meet industry standards, such as internationalization and accessibility
Material Components are maintained by a core team of Android, iOS, and web engineers and UX designers at Google. We strive to support the best of each platform by:
With these components, your team can easily develop rich user experiences using Material Design. We'll be continually updating the components to match the latest Material Design guidelines, and we're looking forward to you and your team contributing to the project. To get the latest news and chat with us directly, please check out our GitHub repos, follow us on Twitter (@materialdesign), and visit us at https://material.io/components/.
Posted by Matias Duarte, VP, Material Design at Google
As a child I was surrounded by the commercial design and pop-cultural art of Japan. I played with Transformer robot toys, pumped quarters into Pac-Man and Donkey Kong, watched Star Blazers (Yamato) and Robotech (Macross) cartoons after school, and listened to mix-tapes on my Sony Walkman.
In art school, I became fascinated by the creative dialog of the late 1800s, where western artists drew inspiration from their peers in Asia. The flattening of perspective and embrace of the two-dimensional graphic qualities of the image were revolutionary at the time, and would lay the foundation for the great modernist movements of painting and design in the West.
These influences can be found in Material Design, our comprehensive system for visual, motion, and interaction design across all platforms and all devices. Material Design continues to evolve but, at its core, it relies on the foundations of good graphic and print design – clear typography, systematic layout, thoughtful application of scale, intentional use of color and white space, and foreground imagery. Working together these elements do far more than please the eye. They create hierarchy, meaning, and focus attention on content.
Yesterday, I was in Tokyo to host SPAN, our annual design event that engages the many ways design and technology shape our everyday lives. SPAN Tokyo provides us with an opportunity to honor the influence of Japanese art and culture on Material Design, as we highlight the most inspiring local designers and broader creative community. In anticipation of the event, we have translated our Material Design Guidelines into Japanese which we are please to announce are available for download starting today (material.google.com/jp). This document is a first step towards making Material Design a conversation that includes our Japanese friends. Furthermore, it is a symbol of our commitment to continuing this dialog of design throughout Asia.
With that in mind, we’ve organized SPAN Tokyo to feature a broad range of practitioners contributing to the contemporary visual cultural happening in Japan today—from art generated through machine learning and neural networks, to start-up culture, ikebana, type design, and much more. We’re honored to have been joined by this esteemed group of speakers, including London Design Museum director Deyan Sudjic, illustrator Mariya Suzuki, artist Keiichi Tanaami, and many more.
To recap yesterday's conference and stay up to date on future events, follow us on Twitter, Google+, Facebook, and sign up for the Google Design Newsletter.
Posted by Addy Osmani, Staff Developer Platform Engineer
Back in 2014, Google published the material design specification with a goal to provide guidelines for good design and beautiful UI across all device form factors. Today we are releasing our first effort to bring this to websites using vanilla CSS, HTML and JavaScript. We’re calling it Material Design Lite (MDL).
MDL makes it easy to add a material design look and feel to your websites. The “Lite” part of MDL comes from several key design goals: MDL has few dependencies, making it easy to install and use. It is framework-agnostic, meaning MDL can be used with any of the rapidly changing landscape of front-end tool chains. MDL has a low overhead in terms of code size (~27KB gzipped), and a narrow focus—enabling material design styling for websites.
Get started now and give it a spin or try one of our examples on CodePen.
MDL is a complimentary implementation to the Paper elements built with Polymer. The Paper elements are fully encapsulated components that can be used individually or composed together to create a material design-style site, and support more advanced user interaction. That said, MDL can be used alongside the Polymer element counterparts.
MDL optimises for websites heavy on content such as marketing pages, text articles and blogs. We've built responsive templates to show the broadness of sites that can be created using MDL that can be downloaded from our Templates page. We hope these inspire you to build great looking sites.
Blogs:
Text-heavy content sites:
Dashboards:
Standalone articles:
and more.
MDL includes a rich set of components, including material design buttons, text-fields, tooltips, spinners and many more. It also include a responsive grid and breakpoints that adhere to the new material design adaptive UI guidelines.
The MDL sources are written in Sass using BEM. While we hope you'll use our theme customizer or pre-built CSS, you can also download the MDL sources from GitHub and build your own version. The easiest way to use MDL is by referencing our CDN, but you can also download the CSS or import MDL via npm or Bower.
The complete MDL experience works in all modern evergreen browsers (Chrome, Firefox, Opera, Edge) and Safari, but gracefully degrades to CSS-only in browsers like IE9 that don’t pass our Cutting-the-mustard test. Our browser compatibility matrix has the most up to date information on the browsers MDL officially supports.
We've been working with the designers evolving material design to build in additional thinking for the web. This includes working on solutions for responsive templates, high-performance typography and missing components like badges. MDL is spec compliant for today and provides guidance on aspects of the spec that are still being evolved. As with the material design spec itself, your feedback and questions will help us evolve MDL, and in turn, how material design works on the web.
We’re sure you have plenty of questions and we have tried to cover some of them in our FAQ. Feel free to hit us up on GitHub or Stack Overflow if you have more. :)
MDL is built on the core technologies of the web you already know and use every day—CSS, HTML and JS. By adopting MDL into your projects, you gain access to an authoritative and highly curated implementation of material design for the web. We can’t wait to see the beautiful, modern, responsive websites you’re going to build with Material Design Lite.
Posted by Monica Bagagem, Developer Marketing
Google I/O 2015 starts tomorrow, and, like last year, we’ve got an exciting lineup of design-focused content for both developers and designers to experience in-person and online. Just a year ago, we announced material design - a system for cross-platform visual, motion, and interaction design. This year at I/O, we’ll see how material has been adopted and implemented by the community, and our approach on design across our platforms.
At 4PM PDT on Thursday, May 28, join Matias Duarte’s “Material Now” session to recap where we’ve been and get a sneak peek of where we’re going with design at Google. We’ll be recognizing some of the phenomenal material design work from across the community, so definitely tune in if you’re an Android developer or designer. For more details, check Matias’ post on Google+.
The session will be live streamed so you can follow along in real-time even if you’re not at Moscone. Recordings will also be available shortly after on the I/O website.
Add Design Sessions to your I/O schedule
We’ve dedicated an entire section of Moscone West to design-related programming, including one-on-one and group UX reviews with members of the material design team. Appointments will be on a first-come, first-serve basis, but we'll also have Google designers on hand for more casual questions.
Add Material Design Reviews to your I/O schedule
Google designers and engineers will host several deep-dive, 20 minute tech talks in a breakout area within the Design Sandbox on Level 2. The space has been designed to facilitate conversation and discussion with small audiences, so come prepared with questions! We’ll be covering a range of topics such as cross-platform and responsive design, designing for platforms like Google Cast and Android Auto, and how to adapt material design to your brand. As an added bonus, most Sandbox Talks will take place twice throughout the conference giving you more flexibility to adjust your schedule.
Add Design Sandbox Talks to your I/O schedule
Explore the full Google I/O schedule here.
Be sure to follow +GoogleDesign and @GoogleDesign, where we’ll be posting design-related announcements throughout the conference. You can also follow and join the general conversation about I/O at #io15. See you tomorrow!
When we started building for the first mobile devices, mobile meant less: less screen space, slower connection, fewer features. A mobile experience was often a lesser experience. But mobile devices have evolved—they have become more powerful, faster, and more intuitive—so must our approach to design.
And as Google, including the Android platform, expands into new form factors, we’re introducing one consistent design that spans devices across mobile, desktop, and beyond. Today at Google I/O, we introduced material design, which uses tactile surfaces, bold graphic design, and fluid motion to create beautiful, intuitive experiences.
In material design, surface and shadow establish a physical structure to explain what can be touched and what can move. Content is front and center, using principles of modern print design. Motion is meaningful, clarifying relationships and teaching with delightful details.
We needed something that felt at home on the smallest watch, the largest TV, and every screen in between. We used it for Android Wear, our project to extend Android wearables, as well as Android TV, and Android Auto. So as you create applications and services for this expansive new range of devices, we’ve created one unified set of style guidelines that works across any platform. We’re releasing the first version of these guidelines as part of our preview today. You can find them on google.com/design.
Bringing material design to Android is a big part of the L-Release of Android, the version we previewed today. We’ve added the new Material theme, which you can apply to your apps for a new style: it lets you easily infuse your own color palette into your app, and offers new system widgets, screen transitions and animated touch feedback. We’ve also added the ability to specify a view’s elevation, allowing you to raise UI elements and cast dynamic, real-time shadows in your apps.
Last year at I/O we announced Polymer, an ambitious UI toolkit for the web. As a developer, you’ll now have access to all the capabilities of material design via Polymer, bringing tangibility, bold graphics, and smooth animations to your applications on the web.
If you’d like to learn more about material design, please take a look at our guidelines. Join us as we continue to design and iterate at +Google Design.