[go: nahoru, domu]

Adds ColorPalette interface and surrounding infrastructure

This makes some things such as animating color values easy to implement,
and is also early preparation for future light/dark theme support.

The default backing implementation is a to-be-memoized class containing
observable color properties, so that when values in this are changed we
only recompose composables that directly consume these colors.

Unary plus syntax is temporary, and should be going away soon after
compiler changes.

Bug: b/138714596
Test: ColorPaletteTest / ColorPaletteBenchmark
Change-Id: I63d53f99413d3eb156abb35b70bc06fc6797ed58
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/ColorPaletteBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/ColorPaletteBenchmark.kt
new file mode 100644
index 0000000..2887eaf
--- /dev/null
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/ColorPaletteBenchmark.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.benchmark.test
+
+import androidx.test.filters.LargeTest
+import androidx.ui.benchmark.ComposeBenchmarkRule
+import androidx.ui.benchmark.toggleStateBenchmarkRecompose
+import androidx.ui.material.ColorPalette
+import androidx.ui.test.assertNoPendingChanges
+import androidx.ui.test.cases.ImmutableColorPaletteTestCase
+import androidx.ui.test.cases.ObservableColorPaletteTestCase
+import androidx.ui.test.doFramesUntilNoChangesPending
+import androidx.ui.test.recomposeAssertHadChanges
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Benchmark to compare performance of the default observable [ColorPalette] that will be memoized
+ * and mutated when incoming values change, compared to a simple immutable [ColorPalette] that
+ * will cause all consumers to be recomposed whenever its value changes.
+ */
+@LargeTest
+@RunWith(JUnit4::class)
+class ColorPaletteBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun observablePalette_recompose() {
+        benchmarkRule
+            .toggleStateBenchmarkRecomposeForObservableTestCase(ObservableColorPaletteTestCase())
+    }
+
+    @Test
+    fun immutablePalette_recompose() {
+        benchmarkRule.toggleStateBenchmarkRecompose(ImmutableColorPaletteTestCase())
+    }
+}
+
+/**
+ *  Measures recomposition time for [ObservableColorPaletteTestCase]
+ *
+ * TODO: b/143769776
+ * Currently changing a @Model value that causes another @Model value to change results in two
+ * recompositions - so this method recomposes once for the initial @Model change, so we can
+ * accurately benchmark the second recomposition which is the actual colour change we care about -
+ * the initial recompose should be very small in scope as we are just changing local state to
+ * trigger the colour change.
+ */
+private fun ComposeBenchmarkRule.toggleStateBenchmarkRecomposeForObservableTestCase(
+    testCase: ObservableColorPaletteTestCase
+) {
+    runBenchmarkFor(testCase) {
+        doFramesUntilNoChangesPending()
+
+        measureRepeated {
+            runWithTimingDisabled {
+                testCase.toggleState()
+                recomposeAssertHadChanges()
+            }
+            recomposeAssertHadChanges()
+            assertNoPendingChanges()
+        }
+    }
+}
diff --git a/ui/integration-tests/test/src/androidTest/java/androidx/ui/test/ColorPaletteTest.kt b/ui/integration-tests/test/src/androidTest/java/androidx/ui/test/ColorPaletteTest.kt
new file mode 100644
index 0000000..c49815fd
--- /dev/null
+++ b/ui/integration-tests/test/src/androidTest/java/androidx/ui/test/ColorPaletteTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.test.filters.MediumTest
+import androidx.ui.test.cases.ImmutableColorPaletteTestCase
+import androidx.ui.test.cases.ObservableColorPaletteTestCase
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Test to ensure correctness of [ObservableColorPaletteTestCase] and
+ * [ImmutableColorPaletteTestCase].
+ */
+@MediumTest
+@RunWith(JUnit4::class)
+class ColorPaletteTest {
+    @get:Rule
+    val composeTestRule = createComposeRule(disableTransitions = true)
+
+    @Test
+    fun testObservablePalette() {
+        val testCase = ObservableColorPaletteTestCase()
+        composeTestRule
+            .forGivenTestCase(testCase)
+            .performTestWithEventsControl {
+                doFrame()
+                assertNoPendingChanges()
+
+                Assert.assertEquals(2, testCase.primaryCompositions)
+                Assert.assertEquals(1, testCase.secondaryCompositions)
+
+                doFrame()
+                assertNoPendingChanges()
+
+                testCase.toggleState()
+
+                // TODO: b/143769776
+                // Currently changing a @Model value that causes another @Model value to change
+                // results in two recompositions - so we need to pump two frames here.
+                doFrame()
+                assertLastRecomposeHadChanges()
+                doFrame()
+                assertLastRecomposeHadChanges()
+
+                Assert.assertEquals(4, testCase.primaryCompositions)
+                Assert.assertEquals(1, testCase.secondaryCompositions)
+            }
+    }
+
+    @Test
+    fun testImmutablePalette() {
+        val testCase = ImmutableColorPaletteTestCase()
+        composeTestRule
+            .forGivenTestCase(testCase)
+            .performTestWithEventsControl {
+                doFrame()
+                assertNoPendingChanges()
+
+                Assert.assertEquals(2, testCase.primaryCompositions)
+                Assert.assertEquals(1, testCase.secondaryCompositions)
+
+                doFrame()
+                assertNoPendingChanges()
+
+                testCase.toggleState()
+
+                doFrame()
+                assertLastRecomposeHadChanges()
+
+                Assert.assertEquals(4, testCase.primaryCompositions)
+                Assert.assertEquals(2, testCase.secondaryCompositions)
+            }
+    }
+}
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt
new file mode 100644
index 0000000..907e572
--- /dev/null
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test.cases
+
+import androidx.compose.Composable
+import androidx.compose.Immutable
+import androidx.compose.State
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.graphics.Color
+import androidx.ui.material.ColorPalette
+import androidx.ui.material.MaterialTheme
+import androidx.ui.test.ComposeTestCase
+import androidx.ui.test.ToggleableTestCase
+
+/**
+ * Generic test case for asserting / benchmarking recomposition performance when reading values
+ * from a [ColorPalette] provided through [MaterialTheme].
+ */
+sealed class ColorPaletteTestCase : ComposeTestCase, ToggleableTestCase {
+    private var primaryState: State<Color>? = null
+
+    private val primaryTracker = CompositionTracker()
+    private val secondaryTracker = CompositionTracker()
+
+    @Composable
+    override fun emitContent() {
+        val primary = +state { Color.Red }
+        primaryState = primary
+
+        val palette = createPalette(primary.value)
+
+        App(palette, primaryTracker = primaryTracker, secondaryTracker = secondaryTracker)
+    }
+
+    override fun toggleState() {
+        with(primaryState!!) {
+            value = if (value == Color.Blue) Color.Red else Color.Blue
+        }
+    }
+
+    abstract fun createPalette(primary: Color): ColorPalette
+
+    val primaryCompositions get() = primaryTracker.compositions
+    val secondaryCompositions get() = secondaryTracker.compositions
+}
+
+/**
+ * Test case using the default observable [ColorPalette] that will be memoized and mutated when
+ * incoming values change, causing only functions consuming the specific changed color to recompose.
+ */
+class ObservableColorPaletteTestCase : ColorPaletteTestCase() {
+    override fun createPalette(primary: Color): ColorPalette {
+        return ColorPalette(primary = primary)
+    }
+}
+
+/**
+ * Test case using an immutable [ColorPalette], that will cause a new value to be assigned to the
+ * ambient every time we change this object, causing everything consuming this ambient to recompose.
+ */
+class ImmutableColorPaletteTestCase : ColorPaletteTestCase() {
+    override fun createPalette(primary: Color): ColorPalette =
+        ImmutableColorPalette(primary = primary)
+
+    private class ImmutableColorPalette(override val primary: Color) : ColorPalette {
+        override val secondary = Color.Black
+        override val primaryVariant = Color.Black
+        override val secondaryVariant = Color.Black
+        override val background = Color.Black
+        override val surface = Color.Black
+        override val error = Color.Black
+        override val >
+        override val >
+        override val >
+        override val >
+        override val >
+    }
+}
+
+@Composable
+private fun App(
+    colorPalette: ColorPalette,
+    primaryTracker: CompositionTracker,
+    secondaryTracker: CompositionTracker
+) {
+    MaterialTheme(colorPalette) {
+        CheapPrimaryColorConsumer(primaryTracker)
+        ExpensiveSecondaryColorConsumer(secondaryTracker)
+        CheapPrimaryColorConsumer(primaryTracker)
+    }
+}
+
+@Composable
+private fun CheapPrimaryColorConsumer(compositionTracker: CompositionTracker) {
+    val primary = (+MaterialTheme.colors()).primary
+    // Consume color variable to avoid any optimizations
+    println("Color $primary")
+    compositionTracker.compositions++
+}
+
+@Composable
+private fun ExpensiveSecondaryColorConsumer(compositionTracker: CompositionTracker) {
+    val secondary = (+MaterialTheme.colors()).secondary
+    // simulate some (relatively) expensive work
+    Thread.sleep(1)
+    // Consume color variable to avoid any optimizations
+    println("Color $secondary")
+    compositionTracker.compositions++
+}
+
+/**
+ * Immutable as we want to ensure that we always skip recomposition unless the ambient value
+ * inside the function changes.
+ */
+@Immutable
+private class CompositionTracker(var compositions: Int = 0)
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/ColoredRect.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/ColoredRect.kt
index 9bdb8e0..586b22d 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/ColoredRect.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/ColoredRect.kt
@@ -17,13 +17,12 @@
 package androidx.ui.foundation
 
 import androidx.compose.Composable
-import androidx.compose.trace
 import androidx.ui.core.Dp
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.shape.DrawShape
 import androidx.ui.foundation.shape.RectangleShape
-import androidx.ui.graphics.Color
 import androidx.ui.graphics.Brush
+import androidx.ui.graphics.Color
 import androidx.ui.graphics.SolidColor
 import androidx.ui.layout.Container
 
diff --git a/ui/ui-material/api/api_lint.ignore b/ui/ui-material/api/api_lint.ignore
index ce47776..1d81c43 100644
--- a/ui/ui-material/api/api_lint.ignore
+++ b/ui/ui-material/api/api_lint.ignore
@@ -13,6 +13,8 @@
 
 MissingNullability: androidx.ui.material.BottomAppBar#INSTANCE:
     Missing nullability on field `INSTANCE` in class `class androidx.ui.material.BottomAppBar`
+MissingNullability: androidx.ui.material.MaterialTheme#INSTANCE:
+    Missing nullability on field `INSTANCE` in class `class androidx.ui.material.MaterialTheme`
 MissingNullability: androidx.ui.material.TabRow#INSTANCE:
     Missing nullability on field `INSTANCE` in class `class androidx.ui.material.TabRow`
 MissingNullability: androidx.ui.material.ripple.DefaultRippleEffectFactory#INSTANCE:
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/AndroidManifest.xml b/ui/ui-material/integration-tests/material-demos/src/main/AndroidManifest.xml
index b4a315f..8971e34 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-material/integration-tests/material-demos/src/main/AndroidManifest.xml
@@ -121,6 +121,7 @@
 
         <activity
             android:name=".AppBarActivity"
+            android:theme="@android:style/Theme.Material.Light.NoActionBar"
             android:configChanges="orientation|screenSize"
             android:label="Material/App Bar">
             <intent-filter>
@@ -139,6 +140,7 @@
         </activity>
 
         <activity android:name=".TabActivity"
+            android:theme="@android:style/Theme.Material.Light.NoActionBar"
             android:configChanges="orientation|screenSize"
             android:label="Material/Tabs">
             <intent-filter>
@@ -187,5 +189,25 @@
                 <category android:name="androidx.ui.demos.SAMPLE_CODE" />
             </intent-filter>
         </activity>
+
+        <activity android:name=".MaterialThemeActivity"
+            android:theme="@android:style/Theme.Material.Light.NoActionBar"
+            android:configChanges="orientation|screenSize"
+            android:label="Material/Material Theme">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".DynamicThemeActivity"
+            android:theme="@android:style/Theme.Material.Light.NoActionBar"
+            android:configChanges="orientation|screenSize"
+            android:label="Material/Dynamic Theme">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonActivity.kt
index a5e251a..dafa6d8 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonActivity.kt
@@ -42,11 +42,11 @@
 import androidx.ui.layout.MainAxisAlignment
 import androidx.ui.material.Button
 import androidx.ui.material.ContainedButtonStyle
+import androidx.ui.material.MaterialTheme
 import androidx.ui.material.samples.ButtonWithTextSample
 import androidx.ui.material.samples.ContainedButtonSample
 import androidx.ui.material.samples.OutlinedButtonSample
 import androidx.ui.material.samples.TextButtonSample
-import androidx.ui.material.themeColor
 
 class ButtonActivity : MaterialDemoActivity() {
 
@@ -65,13 +65,16 @@
 
                 TextButtonSample(onClick)
 
-                Button("SECONDARY COLOR", onClick, ContainedButtonStyle(+themeColor { secondary }))
+                Button(
+                    "SECONDARY COLOR",
+                    onClick,
+                    ContainedButtonStyle((+MaterialTheme.colors()).secondary))
 
                 ButtonWithTextSample(onClick)
 
                 // TODO(Andrey): Disabled button has wrong bg and text color for now.
                 // Need to figure out where will we store their styling. Not a part of
-                // MaterialColors right now and specs are not clear about this.
+                // ColorPalette right now and specs are not clear about this.
                 Button("DISABLED. TODO")
             }
         }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/CustomShapeActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/CustomShapeActivity.kt
index cce7840..c2a8906 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/CustomShapeActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/CustomShapeActivity.kt
@@ -21,13 +21,12 @@
 import androidx.compose.Composable
 import androidx.ui.core.Alignment
 import androidx.ui.core.dp
-import androidx.ui.foundation.shape.border.Border
 import androidx.ui.foundation.shape.GenericShape
+import androidx.ui.foundation.shape.border.Border
 import androidx.ui.graphics.Color
 import androidx.ui.layout.FixedSpacer
 import androidx.ui.layout.Wrap
 import androidx.ui.material.Button
-import androidx.ui.material.MaterialTheme
 import androidx.ui.material.OutlinedButtonStyle
 
 class CustomShapeActivity : MaterialDemoActivity() {
@@ -39,13 +38,6 @@
 
     @Composable
     override fun materialContent() {
-        CustomShapeDemo()
-    }
-}
-
-@Composable
-fun CustomShapeDemo() {
-    MaterialTheme {
         Wrap(Alignment.Center) {
             Button(
                 >
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
new file mode 100644
index 0000000..7348013
--- /dev/null
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.material.demos
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.animation.FastOutSlowInEasing
+import androidx.compose.Composable
+import androidx.compose.Model
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.Alignment
+import androidx.ui.core.Px
+import androidx.ui.core.Text
+import androidx.ui.core.dp
+import androidx.ui.core.setContent
+import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.corner.CircleShape
+import androidx.ui.foundation.shape.corner.RoundedCornerShape
+import androidx.ui.graphics.Color
+import androidx.ui.graphics.lerp
+import androidx.ui.graphics.toArgb
+import androidx.ui.layout.Column
+import androidx.ui.layout.Container
+import androidx.ui.layout.Expanded
+import androidx.ui.layout.ExpandedWidth
+import androidx.ui.layout.FlexColumn
+import androidx.ui.layout.HeightSpacer
+import androidx.ui.layout.Padding
+import androidx.ui.layout.Stack
+import androidx.ui.material.BottomAppBar
+import androidx.ui.material.BottomAppBar.FabConfiguration
+import androidx.ui.material.ColorPalette
+import androidx.ui.material.FloatingActionButton
+import androidx.ui.material.MaterialTheme
+import androidx.ui.material.TopAppBar
+import androidx.ui.material.surface.Surface
+import androidx.ui.material.themeTextStyle
+import androidx.ui.text.TextStyle
+import kotlin.math.round
+
+/**
+ * Demo activity that animates the primary, secondary, and background colours in the [MaterialTheme]
+ * as the user scrolls. This has the effect of going from a 'light' theme to a 'dark' theme.
+ */
+class DynamicThemeActivity : Activity() {
+    private val scrollFraction = ScrollFraction()
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            val palette = interpolateTheme(scrollFraction.fraction)
+            val darkenedPrimary = palette.darkenedPrimary
+            window.statusBarColor = darkenedPrimary
+            window.navigationBarColor = darkenedPrimary
+
+            DynamicThemeApp(scrollFraction, palette)
+        }
+    }
+
+    private val ColorPalette.darkenedPrimary: Int
+        get() {
+            return with(primary) {
+                copy(
+                    red = red * 0.75f,
+                    green = green * 0.75f,
+                    blue = blue * 0.75f
+                )
+            }.toArgb()
+        }
+}
+
+@Model
+private class ScrollFraction(var fraction: Float = 0f)
+
+@Composable
+private fun DynamicThemeApp(scrollFraction: ScrollFraction, palette: ColorPalette) {
+    MaterialTheme(palette) {
+        Stack(Expanded) {
+            expanded {
+                FlexColumn {
+                    inflexible {
+                        TopBar()
+                    }
+                    expanded(1f) {
+                        val background = (+MaterialTheme.colors()).background
+                        Surface(color = background) {
+                            ScrollingContent(scrollFraction)
+                        }
+                    }
+                }
+            }
+            aligned(Alignment.BottomCenter) {
+                BottomBar(scrollFraction)
+            }
+        }
+    }
+}
+
+@Composable
+private fun TopBar() {
+    TopAppBar({ Text("Scroll down!") })
+}
+
+@Composable
+private fun BottomBar(scrollFraction: ScrollFraction) {
+    val secondary = (+MaterialTheme.colors()).secondary
+    val fabText = emojiForScrollFraction(scrollFraction.fraction)
+    BottomAppBar<Any>(fabConfiguration = FabConfiguration(fab = {
+        FloatingActionButton(
+            text = fabText,
+            textStyle = +themeTextStyle { h5 },
+            color = secondary,
+            >
+        )
+    }, cutoutShape = CircleShape))
+}
+
+@Composable
+private fun ScrollingContent(scrollFraction: ScrollFraction) {
+    val scrollerPosition = +memo { ScrollerPosition() }
+    val fraction = round((scrollerPosition.value / scrollerPosition.maxPosition) * 100) / 100
+    +memo(fraction) { scrollFraction.fraction = fraction }
+    VerticalScroller(scrollerPosition) {
+        Cards()
+    }
+}
+
+@Composable
+private fun Cards() {
+    Column {
+        repeat(20) { index ->
+            val shapeColor = lerp(Color(0xFF303030.toInt()), Color.White, index / 19f)
+            val textColor = lerp(Color.White, Color(0xFF303030.toInt()), index / 19f)
+            Padding(25.dp) {
+                Container(ExpandedWidth, height = 150.dp) {
+                    // TODO: ideally this would be a Card but currently Surface consumes every
+                    // colour from the Material theme to work out text colour, so we end up doing a
+                    // large amount of work here when the top level theme changes
+                    DrawShape(RoundedCornerShape(10.dp), shapeColor)
+                    Text("Card ${index + 1}", style = TextStyle(color = textColor))
+                }
+            }
+        }
+        HeightSpacer(100.dp)
+    }
+}
+
+private fun interpolateTheme(fraction: Float): ColorPalette {
+    val interpolatedFraction = FastOutSlowInEasing(fraction)
+
+    val primary = lerp(Color(0xFF6200EE.toInt()), Color(0xFF303030.toInt()), interpolatedFraction)
+    val secondary = lerp(Color(0xFF03DAC6.toInt()), Color(0xFFBB86FC.toInt()), interpolatedFraction)
+    val background = lerp(Color.White, Color(0xFF121212.toInt()), interpolatedFraction)
+
+    return ColorPalette(
+        primary = primary,
+        secondary = secondary,
+        background = background
+    )
+}
+
+/**
+ * 'Animate' the emoji in the FAB from 'sun' to 'moon' as we darken the theme
+ */
+private fun emojiForScrollFraction(fraction: Float): String {
+    return when {
+        // Sun
+        fraction < 1 / 7f -> "\u2600"
+        // Sun behind small cloud
+        fraction < 2 / 7f -> "\uD83C\uDF24"
+        // Sun behind cloud
+        fraction < 3 / 7f -> "\uD83C\uDF25"
+        // Cloud
+        fraction < 4 / 7f -> "\u2601"
+        // Cloud with rain
+        fraction < 5 / 7f -> "\uD83C\uDF27"
+        // Cloud with lightning
+        fraction < 6 / 7f -> "\uD83C\uDF29"
+        // Moon
+        else -> "\uD83C\uDF15"
+    }
+}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
index 72b6adfa..89680ea 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
@@ -32,15 +32,15 @@
 import androidx.ui.core.setContent
 import androidx.ui.graphics.Color
 import androidx.ui.graphics.toArgb
-import androidx.ui.material.MaterialColors
+import androidx.ui.material.ColorPalette
 import androidx.ui.material.MaterialTheme
+import androidx.ui.material.demos.MaterialSettingsActivity.SettingsFragment
 import kotlin.random.Random
 import kotlin.reflect.full.memberProperties
-import kotlin.reflect.full.primaryConstructor
 
 @Model
-class CurrentMaterialColors {
-    var colors = MaterialColors()
+class CurrentColorPalette {
+    var colors: ColorPalette = ColorPalette()
 }
 
 /**
@@ -49,7 +49,7 @@
  */
 abstract class MaterialDemoActivity : Activity() {
 
-    private val currentColors = CurrentMaterialColors()
+    private val currentColors = CurrentColorPalette()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -81,7 +81,7 @@
         when (item.itemId) {
             SETTINGS -> startActivity(Intent(this, MaterialSettingsActivity::class.java))
             SHUFFLE -> {
-                val colors = generateMaterialColors()
+                val colors = generateColorPalette(currentColors.colors)
                 colors.saveColors()
                 currentColors.colors = colors
             }
@@ -91,11 +91,19 @@
                 val newOnPrimary = currentColors.colors.primary
                 val newSecondary = currentColors.colors.onSecondary
                 val newOnSecondary = currentColors.colors.secondary
-                val colors = currentColors.colors.copy(
+                val colors = ColorPalette(
                     primary = newPrimary,
-                    >
+                    primaryVariant = currentColors.colors.primaryVariant,
                     secondary = newSecondary,
-                    >
+                    secondaryVariant = currentColors.colors.secondaryVariant,
+                    background = currentColors.colors.background,
+                    surface = currentColors.colors.surface,
+                    error = currentColors.colors.error,
+                    >
+                    >
+                    >
+                    >
+                    >
                 )
                 colors.saveColors()
                 currentColors.colors = colors
@@ -110,14 +118,14 @@
     }
 
     /**
-     * Returns [MaterialColors] from the values saved to [SharedPreferences]. If a given color is
-     * not present in the [SharedPreferences], its default value as defined in [MaterialColors]
+     * Returns [ColorPalette] from the values saved to [SharedPreferences]. If a given color is
+     * not present in the [SharedPreferences], its default value as defined in [ColorPalette]
      * will be returned.
      */
-    private fun getColorsFromSharedPreferences(): MaterialColors {
+    private fun getColorsFromSharedPreferences(): ColorPalette {
         val sharedPreferences = getDefaultSharedPreferences(this)
-        val constructor = MaterialColors::class.primaryConstructor!!
-        val parametersToSet = constructor.parameters.mapNotNull { parameter ->
+        val function = ::ColorPalette
+        val parametersToSet = function.parameters.mapNotNull { parameter ->
             val savedValue = sharedPreferences.getString(parameter.name, "")
             if (savedValue.isNullOrBlank()) {
                 null
@@ -126,13 +134,14 @@
                 parameter to parsedColor
             }
         }.toMap()
-        return MaterialColors::class.primaryConstructor!!.callBy(parametersToSet)
+        if (parametersToSet.isEmpty()) return ColorPalette()
+        return ::ColorPalette.callBy(parametersToSet)
     }
 
     /**
-     * Persists the current [MaterialColors] to [SharedPreferences].
+     * Persists the current [ColorPalette] to [SharedPreferences].
      */
-    private fun MaterialColors.saveColors() {
+    private fun ColorPalette.saveColors() {
         forEachColorProperty { name, color ->
             getDefaultSharedPreferences(this@MaterialDemoActivity)
                 .edit()
@@ -142,18 +151,26 @@
     }
 
     /**
-     * Generates random colors for [MaterialColors.primary], [MaterialColors.onPrimary],
-     * [MaterialColors.secondary] and [MaterialColors.onSecondary] as dark-on-light or light-on-dark
+     * Generates random colors for [ColorPalette.primary], [ColorPalette.onPrimary],
+     * [ColorPalette.secondary] and [ColorPalette.onSecondary] as dark-on-light or light-on-dark
      * pairs.
      */
-    private fun generateMaterialColors(): MaterialColors {
+    private fun generateColorPalette(currentColors: ColorPalette): ColorPalette {
         val (primary, onPrimary) = generateColorPair()
         val (secondary, onSecondary) = generateColorPair()
-        return MaterialColors(
+        return ColorPalette(
             primary = primary,
-            >
+            primaryVariant = currentColors.primaryVariant,
             secondary = secondary,
-            >
+            secondaryVariant = currentColors.secondaryVariant,
+            background = currentColors.background,
+            surface = currentColors.surface,
+            error = currentColors.error,
+            >
+            >
+            >
+            >
+            >
         )
     }
 
@@ -448,12 +465,12 @@
         override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
             val context = preferenceManager.context
             val screen = preferenceManager.createPreferenceScreen(context)
-            // Create new MaterialColors to resolve defaults
-            MaterialColors().forEachColorProperty { name, color ->
+            // Create new ColorPalette to resolve defaults
+            ColorPalette().forEachColorProperty { name, color ->
                 val preference = EditTextPreference(context)
                 preference.key = name
                 preference.title = name
-                // set the default value to be the default for MaterialColors
+                // set the default value to be the default for ColorPalette
                 preference.setDefaultValue(Integer.toHexString(color.toArgb()))
                 preference.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
                 screen.addPreference(preference)
@@ -464,14 +481,14 @@
 }
 
 /**
- * Iterates over each color present in a given [MaterialColors].
+ * Iterates over each color present in a given [ColorPalette].
  *
- * @param action the action to take on each property, where [name] is the name of the property,
- * such as 'primary' for [MaterialColors.primary], and [color] is the resolved [Color] of the
+ * @param action the action to take on each property, where name is the name of the property,
+ * such as 'primary' for [ColorPalette.primary], and color is the resolved [Color] of the
  * property.
  */
-private fun MaterialColors.forEachColorProperty(action: (name: String, color: Color) -> Unit) {
-    MaterialColors::class.memberProperties.forEach { property ->
+private fun ColorPalette.forEachColorProperty(action: (name: String, color: Color) -> Unit) {
+    ColorPalette::class.memberProperties.forEach { property ->
         val name = property.name
         val color = property.get(this) as Color
         action(name, color)
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialThemeActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialThemeActivity.kt
new file mode 100644
index 0000000..bf435bd
--- /dev/null
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialThemeActivity.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.material.demos
+
+import androidx.compose.Composable
+import androidx.ui.layout.Center
+import androidx.ui.layout.FlexColumn
+import androidx.ui.material.samples.MaterialThemeSample
+import androidx.ui.material.samples.ThemeColorSample
+import androidx.ui.material.samples.ThemeTextStyleSample
+
+class MaterialThemeActivity : MaterialDemoActivity() {
+    @Composable
+    override fun materialContent() {
+        FlexColumn {
+            expanded(1f) {
+                Center {
+                    MaterialThemeSample()
+                }
+                Center {
+                    ThemeColorSample()
+                }
+                Center {
+                    ThemeTextStyleSample()
+                }
+            }
+        }
+    }
+}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorActivity.kt
index 016d434..23bc943 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorActivity.kt
@@ -17,18 +17,19 @@
 package androidx.ui.material.demos
 
 import android.os.Handler
-import androidx.ui.layout.FlexColumn
-import androidx.ui.layout.MainAxisAlignment.SpaceEvenly
-import androidx.ui.layout.Row
-import androidx.ui.material.CircularProgressIndicator
-import androidx.ui.material.LinearProgressIndicator
-import androidx.ui.graphics.Color
 import androidx.compose.Composable
 import androidx.compose.Model
 import androidx.compose.onActive
 import androidx.compose.onDispose
 import androidx.compose.unaryPlus
+import androidx.ui.graphics.Color
 import androidx.ui.layout.ExpandedWidth
+import androidx.ui.layout.FlexColumn
+import androidx.ui.layout.MainAxisAlignment
+import androidx.ui.layout.MainAxisAlignment.SpaceEvenly
+import androidx.ui.layout.Row
+import androidx.ui.material.CircularProgressIndicator
+import androidx.ui.material.LinearProgressIndicator
 
 class ProgressIndicatorActivity : MaterialDemoActivity() {
 
@@ -84,7 +85,7 @@
     +onActive { state.start() }
     +onDispose { state.stop() }
 
-    FlexColumn {
+    FlexColumn(mainAxisAlignment = MainAxisAlignment.Center) {
         expanded(flex = 1f) {
             Row(
                 ExpandedWidth,
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
index b45ece0..dded9a9 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
@@ -18,9 +18,9 @@
 
 import androidx.ui.layout.FlexColumn
 import androidx.ui.material.surface.Surface
-import androidx.ui.material.themeColor
 import androidx.compose.Composable
 import androidx.compose.unaryPlus
+import androidx.ui.material.MaterialTheme
 
 /**
  * This file contains Material components that are needed to build the Rally app and not
@@ -35,7 +35,7 @@
             appBar()
         }
         expanded(flex = 1.0f) {
-            Surface(color = +themeColor { background }) {
+            Surface(color = (+MaterialTheme.colors()).surface) {
                 children()
             }
         }
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyAlertDialog.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyAlertDialog.kt
index c6da7ca5..3ee7de7 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyAlertDialog.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyAlertDialog.kt
@@ -28,8 +28,8 @@
 import androidx.ui.material.AlertDialog
 import androidx.ui.material.Button
 import androidx.ui.material.Divider
+import androidx.ui.material.MaterialTheme
 import androidx.ui.material.TextButtonStyle
-import androidx.ui.material.themeColor
 
 @Composable
 fun RallyAlertDialog(
@@ -46,7 +46,7 @@
                 Column(crossAxisAlignment = CrossAxisAlignment.Stretch) {
                     Divider(
                         Spacing(left = 12.dp, right = 12.dp),
-                        color = (+themeColor { onSurface }).copy(alpha = .2f)
+                        color = (+MaterialTheme.colors()).onSurface.copy(alpha = 0.2f)
                     )
                     Button(text = buttonText,  style = style)
                 }
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyCards.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyCards.kt
index 9b60e8ec..362c128 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyCards.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyCards.kt
@@ -40,10 +40,10 @@
 import androidx.ui.layout.WidthSpacer
 import androidx.ui.material.Button
 import androidx.ui.material.Divider
+import androidx.ui.material.MaterialTheme
 import androidx.ui.material.TextButtonStyle
 import androidx.ui.material.ripple.Ripple
 import androidx.ui.material.surface.Card
-import androidx.ui.material.themeColor
 import androidx.ui.material.themeTextStyle
 import java.util.Locale
 
@@ -81,7 +81,7 @@
             }
             Divider(
                 Spacing(left = 12.dp, right = 12.dp),
-                color = +themeColor { background },
+                color = (+MaterialTheme.colors()).background,
                 height = 2.dp
             )
             Ripple(bounded = true) {
@@ -235,7 +235,7 @@
 
 /**
  * The Bills card within the Rally Overview screen.
-*/
+ */
 @Composable
 fun RallyBillsOverviewCard() {
     Card {
@@ -285,7 +285,8 @@
                 aligned(alignment = Alignment.Center) {
                     val accountsProportion = listOf(0.65f, 0.25f, 0.03f, 0.05f)
                     val colors = listOf(0xFF1EB980, 0xFF005D57, 0xFF04B97F, 0xFF37EFBA).map {
-                        Color(it) }
+                        Color(it)
+                    }
                     Container(height = 300.dp, expanded = true) {
                         DrawAnimatedCircle(accountsProportion, colors)
                     }
@@ -333,4 +334,4 @@
 }
 
 @Composable
-fun RallyDivider() = Divider(color = +themeColor { background }, height = 2.dp)
+fun RallyDivider() = Divider(color = (+MaterialTheme.colors()).background, height = 2.dp)
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
index ad16cb4..c8cc995 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
@@ -22,8 +22,7 @@
 import androidx.ui.core.em
 import androidx.ui.core.sp
 import androidx.ui.graphics.Color
-import androidx.ui.material.Colors
-import androidx.ui.material.MaterialColors
+import androidx.ui.material.ColorPalette
 import androidx.ui.material.MaterialTheme
 import androidx.ui.material.MaterialTypography
 import androidx.ui.material.Typography
@@ -40,9 +39,9 @@
 
 @Composable
 fun RallyTheme(children: @Composable() () -> Unit) {
-    val colors = MaterialColors(
+    val colors = ColorPalette(
         primary = rallyGreen,
-        surface = Color(0xFF33333D),
+        surface = Color(0xFF26282F.toInt()),
         >
         background = Color(0xFF26282F),
         >
@@ -94,7 +93,7 @@
 
 @Composable
 fun RallyDialogThemeOverlay(children: @Composable() () -> Unit) {
-    val dialogColors = (+ambient(Colors)).copy(
+    val dialogColors = ColorPalette(
         primary = Color.White,
         surface = Color(0xFF1E1E1E),
         >
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt
index 092deca6..e649a64 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt
@@ -32,7 +32,7 @@
 import androidx.ui.material.TopAppBar
 import androidx.ui.graphics.Image
 import androidx.ui.foundation.shape.corner.CutCornerShape
-import androidx.ui.material.themeColor
+import androidx.ui.material.MaterialTheme
 import androidx.ui.text.TextStyle
 import kotlin.math.abs
 import kotlin.math.roundToInt
@@ -105,7 +105,7 @@
         navigationIcon = navigationIcon,
         fabConfiguration = FabConfiguration {
             FloatingActionButton(
-                color = +themeColor { secondary },
+                color = (+MaterialTheme.colors()).secondary,
                 icon = someActionImage,
                  /** doSomething() */ })
         },
@@ -124,7 +124,7 @@
     BottomAppBar(
         fabConfiguration = FabConfiguration(fabPosition = BottomAppBar.FabPosition.End) {
             FloatingActionButton(
-                color = +themeColor { secondary },
+                color = (+MaterialTheme.colors()).secondary,
                 icon = someActionImage,
                  /** doSomething() */ })
         },
@@ -152,7 +152,7 @@
         navigationIcon = navigationIcon,
         fabConfiguration = FabConfiguration(cutoutShape = CircleShape) {
             FloatingActionButton(
-                color = +themeColor { secondary },
+                color = (+MaterialTheme.colors()).secondary,
                 icon = someActionImage,
                  /** doSomething() */ })
         },
@@ -180,7 +180,7 @@
         navigationIcon = navigationIcon,
         fabConfiguration = FabConfiguration(cutoutShape = CircleShape) {
             FloatingActionButton(
-                color = +themeColor { secondary },
+                color = (+MaterialTheme.colors()).secondary,
                 text = "Extended FAB",
                 textStyle = TextStyle(color = Color.White),
                  /** doSomething() */ })
@@ -235,7 +235,7 @@
         navigationIcon = navigationIcon,
         fabConfiguration = FabConfiguration(cutoutShape = fabShape) {
             FloatingActionButton(
-                color = +themeColor { secondary },
+                color = (+MaterialTheme.colors()).secondary,
                 icon = someActionImage,
                 >
                 shape = fabShape
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
index 51fab7f..d94ef22 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
@@ -23,45 +23,49 @@
 import androidx.ui.core.sp
 import androidx.ui.foundation.ColoredRect
 import androidx.ui.graphics.Color
-import androidx.ui.material.MaterialColors
+import androidx.ui.material.ColorPalette
+import androidx.ui.material.FloatingActionButton
 import androidx.ui.material.MaterialTheme
 import androidx.ui.material.MaterialTypography
-import androidx.ui.material.themeColor
 import androidx.ui.material.themeTextStyle
 import androidx.ui.text.TextStyle
 import androidx.ui.text.font.FontFamily
 import androidx.ui.text.font.FontWeight
 
+// TODO: add shape theme
 @Sampled
 @Composable
 fun MaterialThemeSample() {
-    val colors = MaterialColors(
+    val colors = ColorPalette(
         primary = Color(0xFF1EB980.toInt()),
         surface = Color(0xFF26282F.toInt()),
         >
     )
+
     val typography = MaterialTypography(
         h1 = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
             fontWeight = FontWeight.W100,
             fontSize = 96.sp),
-        h2 = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
-            fontWeight = FontWeight.W100,
-            fontSize = 60.sp)
+        button = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
+            fontWeight = FontWeight.W600,
+            fontSize = 14.sp)
 
     )
+
     MaterialTheme(colors = colors, typography = typography) {
-        // Your app content goes here
+        FloatingActionButton("FAB with text style and color from theme", >
     }
 }
 
 @Sampled
 @Composable
 fun ThemeColorSample() {
-    ColoredRect(color = +themeColor { primary })
+    val colors = +MaterialTheme.colors()
+    ColoredRect(color = colors.primary)
 }
 
 @Sampled
 @Composable
 fun ThemeTextStyleSample() {
-    Text(text = "Styled text", style = +themeTextStyle { h1 })
-}
\ No newline at end of file
+    Text(text = "H4 styled text", style = +themeTextStyle { h4 })
+}
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/ButtonTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/ButtonTest.kt
index 6f3c2ee..48800b0 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/ButtonTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/ButtonTest.kt
@@ -200,7 +200,9 @@
         composeTestRule.setMaterialContent {
             Button( style = ContainedButtonStyle()) {
                 Truth.assertThat(+currentTextStyle())
-                    .isEqualTo(+themeTextStyle { button.copy(color = +themeColor { onPrimary }) })
+                    .isEqualTo(+themeTextStyle {
+                        button.copy(color = (+MaterialTheme.colors()).onPrimary)
+                    })
             }
         }
     }
@@ -210,7 +212,9 @@
         composeTestRule.setMaterialContent {
             Button( style = OutlinedButtonStyle()) {
                 Truth.assertThat(+currentTextStyle())
-                    .isEqualTo(+themeTextStyle { button.copy(color = +themeColor { primary }) })
+                    .isEqualTo(+themeTextStyle {
+                        button.copy(color = (+MaterialTheme.colors()).primary)
+                    })
             }
         }
     }
@@ -220,7 +224,9 @@
         composeTestRule.setMaterialContent {
             Button( style = OutlinedButtonStyle()) {
                 Truth.assertThat(+currentTextStyle())
-                    .isEqualTo(+themeTextStyle { button.copy(color = +themeColor { primary }) })
+                    .isEqualTo(+themeTextStyle {
+                        button.copy(color = (+MaterialTheme.colors()).primary)
+                    })
             }
         }
     }
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
index 667fe8e..b973eea 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
@@ -36,7 +36,7 @@
 
     @Test
     fun textColorForBackgroundUsesCorrectValues() {
-        val colors = MaterialColors(
+        val colors = ColorPalette(
             primary = Color(0),
             >
             secondary = Color(2),
@@ -51,20 +51,20 @@
         composeTestRule.setContent {
             MaterialTheme(colors = colors) {
                 assertEquals(
-                    +textColorForBackground(+themeColor { primary }),
-                    +themeColor { onPrimary }
+                    +textColorForBackground((+MaterialTheme.colors()).primary),
+                    (+MaterialTheme.colors()).onPrimary
                 )
                 assertEquals(
-                    +textColorForBackground(+themeColor { secondary }),
-                    +themeColor { onSecondary }
+                    +textColorForBackground((+MaterialTheme.colors()).secondary),
+                    (+MaterialTheme.colors()).onSecondary
                 )
                 assertEquals(
-                    +textColorForBackground(+themeColor { background }),
-                    +themeColor { onBackground }
+                    +textColorForBackground((+MaterialTheme.colors()).background),
+                    (+MaterialTheme.colors()).onBackground
                 )
                 assertEquals(
-                    +textColorForBackground(+themeColor { surface }),
-                    +themeColor { onSurface }
+                    +textColorForBackground((+MaterialTheme.colors()).surface),
+                    (+MaterialTheme.colors()).onSurface
                 )
                 assertNull(+textColorForBackground(Color(100)))
             }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/AlertDialog.kt b/ui/ui-material/src/main/java/androidx/ui/material/AlertDialog.kt
index 63b9dd9..a9828b6 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/AlertDialog.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/AlertDialog.kt
@@ -111,7 +111,7 @@
     buttons: @Composable() () -> Unit
 ) {
     // TODO: Find a cleaner way to pass the properties of the MaterialTheme
-    val currentColors = +ambient(Colors)
+    val currentColors: ColorPalette = +MaterialTheme.colors()
     val currentTypography = +ambient(Typography)
     Dialog( {
         MaterialTheme(colors = currentColors, typography = currentTypography) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt b/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
index 963c320..9eb06eaef 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
@@ -77,14 +77,13 @@
  * @sample androidx.ui.material.samples.SimpleTopAppBarNavIcon
  *
  * @param title The title to be displayed in the center of the TopAppBar
- * @param color An optional color for the TopAppBar. By default [MaterialColors.primary] will be
- * used.
+ * @param color An optional color for the TopAppBar
  * @param navigationIcon The navigation icon displayed at the start of the TopAppBar
  */
 @Composable
 fun TopAppBar(
     title: @Composable() () -> Unit,
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     navigationIcon: @Composable() (() -> Unit)? = null
 ) {
     BaseTopAppBar(
@@ -108,8 +107,7 @@
  * @sample androidx.ui.material.samples.SimpleTopAppBarNavIconWithActions
  *
  * @param title The title to be displayed in the center of the TopAppBar
- * @param color An optional color for the TopAppBar. By default [MaterialColors.primary] will be
- * used.
+ * @param color An optional color for the TopAppBar
  * @param navigationIcon The navigation icon displayed at the start of the TopAppBar
  * @param actionData A list of data representing the actions to be displayed at the end of
  * the TopAppBar. Any remaining actions that do not fit on the TopAppBar should typically be
@@ -126,7 +124,7 @@
 fun <T> TopAppBar(
     title: @Composable() () -> Unit,
     actionData: List<T>,
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     navigationIcon: @Composable() (() -> Unit)? = null,
     action: @Composable() (T) -> Unit
     // TODO: support overflow menu here with the remainder of the list
@@ -144,7 +142,7 @@
 
 @Composable
 private fun BaseTopAppBar(
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     startContent: @Composable() (() -> Unit)?,
     title: @Composable() () -> Unit,
     endContent: @Composable() (() -> Unit)?
@@ -261,8 +259,7 @@
  *
  * For examples using a cutout FAB, see [FabConfiguration], which controls the shape of the cutout.
  *
- * @param color An optional color for the BottomAppBar. By default [MaterialColors.primary]
- * will be used.
+ * @param color An optional color for the BottomAppBar
  * @param navigationIcon The navigation icon displayed in the BottomAppBar. Note that if
  * [fabConfiguration] is [FabPosition.End] aligned, this parameter must be null / not set.
  * @param fabConfiguration The [FabConfiguration] that controls how / where
@@ -280,7 +277,7 @@
  */
 @Composable
 fun <T> BottomAppBar(
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     navigationIcon: @Composable() (() -> Unit)? = null,
     fabConfiguration: FabConfiguration? = null,
     actionData: List<T> = emptyList(),
@@ -604,7 +601,7 @@
 
 @Composable
 private fun BaseBottomAppBar(
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     startContent: @Composable() (() -> Unit)?,
     fabContainer: @Composable() (() -> Unit)?,
     shape: Shape = RectangleShape,
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Button.kt b/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
index 83f21e6..73603bc 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
@@ -84,7 +84,7 @@
  * If null is provided the color will be calculated by [RippleTheme.defaultColor].
  */
 fun ContainedButtonStyle(
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     shape: Shape = +themeShape { button },
     elevation: Dp = 2.dp,
     rippleColor: Color? = null
@@ -118,11 +118,14 @@
  * @param contentColor The color used by text and Ripple.
  */
 fun OutlinedButtonStyle(
-    border: Border = Border(+themeColor { onSurface.copy(alpha = OutlinedStrokeOpacity) }, 1.dp),
-    color: Color = +themeColor { surface },
+    border: Border = Border(
+        (+MaterialTheme.colors()).onSurface.copy(alpha = OutlinedStrokeOpacity),
+        1.dp
+    ),
+    color: Color = (+MaterialTheme.colors()).surface,
     shape: Shape = +themeShape { button },
     elevation: Dp = 0.dp,
-    contentColor: Color? = +themeColor { primary }
+    contentColor: Color? = (+MaterialTheme.colors()).primary
 ) = ButtonStyle(
     color = color,
     shape = shape,
@@ -149,7 +152,7 @@
  */
 fun TextButtonStyle(
     shape: Shape = +themeShape { button },
-    contentColor: Color? = +themeColor { primary }
+    contentColor: Color? = (+MaterialTheme.colors()).primary
 ) = ButtonStyle(
     color = Color.Transparent,
     shape = shape,
@@ -166,7 +169,7 @@
  *
  * The default text style for internal [Text] components will be set to [MaterialTypography.button]. Text color will
  * try to match the correlated color for the background color. For example if the background color is set to
- * [MaterialColors.primary] then the text will by default use [MaterialColors.onPrimary].
+ * [ColorPalette.primary] then the text will by default use [ColorPalette.onPrimary].
  *
  * @sample androidx.ui.material.samples.ButtonSample
  *
@@ -207,7 +210,7 @@
  *
  * The default text style for internal [Text] components will be set to [MaterialTypography.button]. Text color will
  * try to match the correlated color for the background color. For example if the background color is set to
- * [MaterialColors.primary] then the text will by default use [MaterialColors.onPrimary].
+ * [ColorPalette.primary] then the text will by default use [ColorPalette.onPrimary].
  *
  * @sample androidx.ui.material.samples.ButtonWithTextSample
  *
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt b/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
index a0a715e..7dcd8bc 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
@@ -53,13 +53,13 @@
  * @param onCheckedChange callback to be invoked when checkbox is being clicked,
  * therefore the change of checked state in requested.
  * If `null`, Checkbox will appears in the [checked] state and remains disabled
- * @param color custom color for checkbox. By default [MaterialColors.secondary] will be used
+ * @param color custom color for checkbox
  */
 @Composable
 fun Checkbox(
     checked: Boolean,
     onCheckedChange: ((Boolean) -> Unit)?,
-    color: Color = +themeColor { secondary }
+    color: Color = (+MaterialTheme.colors()).secondary
 ) {
     TriStateCheckbox(
         value = ToggleableState(checked),
@@ -83,13 +83,13 @@
  * @param onClick callback to be invoked when checkbox is being clicked,
  * therefore the change of ToggleableState state is requested.
  * If `null`, TriStateCheckbox appears in the [value] state and remains disabled
- * @param color custom color for checkbox. By default [MaterialColors.secondary] will be used
+ * @param color custom color for checkbox
  */
 @Composable
 fun TriStateCheckbox(
     value: ToggleableState,
     onClick: (() -> Unit)?,
-    color: Color = +themeColor { secondary }
+    color: Color = (+MaterialTheme.colors()).secondary
 ) {
     Wrap {
         Ripple(bounded = false) {
@@ -106,7 +106,7 @@
 
 @Composable
 private fun DrawCheckbox(value: ToggleableState, activeColor: Color) {
-    val unselectedColor = (+themeColor { onSurface }).copy(alpha = UncheckedBoxOpacity)
+    val unselectedColor = (+MaterialTheme.colors()).onSurface.copy(alpha = UncheckedBoxOpacity)
     val definition = +memo(activeColor, unselectedColor) {
         generateTransitionDefinition(activeColor, unselectedColor)
     }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Color.kt b/ui/ui-material/src/main/java/androidx/ui/material/Color.kt
new file mode 100644
index 0000000..63c98ae
--- /dev/null
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Color.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.material
+
+import androidx.compose.Ambient
+import androidx.compose.Composable
+import androidx.compose.Model
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.graphics.Color
+import kotlin.reflect.KProperty
+
+/**
+ * Collection of colors in the [Material color specification]
+ * [https://material.io/design/color/the-color-system.html#color-theme-creation].
+ */
+interface ColorPalette {
+    /**
+     * The primary color is the color displayed most frequently across your app’s screens and
+     * components.
+     */
+    val primary: Color
+    /**
+     * The primary variant color is used to distinguish two elements of the app using the primary
+     * color, such as the top app bar and the system bar.
+     */
+    val primaryVariant: Color
+    /**
+     * The secondary color provides more ways to accent and distinguish your product.
+     * Secondary colors are best for:
+     * - Floating action buttons
+     * - Selection controls, like sliders and switches
+     * - Highlighting selected text
+     * - Progress bars
+     * - Links and headlines
+     */
+    val secondary: Color
+    /**
+     * The secondary variant color is used to distinguish two elements of the app using the
+     * secondary color.
+     */
+    val secondaryVariant: Color
+    /**
+     * The background color appears behind scrollable content.
+     */
+    val background: Color
+    /**
+     * The surface color is used on surfaces of components, such as cards, sheets and menus.
+     */
+    val surface: Color
+    /**
+     * The error color is used to indicate error within components, such as text fields.
+     */
+    val error: Color
+    /**
+     * Color used for text and icons displayed on top of the primary color.
+     */
+    val onPrimary: Color
+    /**
+     * Color used for text and icons displayed on top of the secondary color.
+     */
+    val onSecondary: Color
+    /**
+     * Color used for text and icons displayed on top of the background color.
+     */
+    val onBackground: Color
+    /**
+     * Color used for text and icons displayed on top of the surface color.
+     */
+    val onSurface: Color
+    /**
+     * Color used for text and icons displayed on top of the error color.
+     */
+    val onError: Color
+}
+
+/**
+ * Creates a complete color definition for the [Material color specification]
+ * [https://material.io/design/color/the-color-system.html#color-theme-creation].
+ */
+fun ColorPalette(
+    primary: Color = Color(0xFF6200EE.toInt()),
+    primaryVariant: Color = Color(0xFF3700B3.toInt()),
+    secondary: Color = Color(0xFF03DAC6.toInt()),
+    secondaryVariant: Color = Color(0xFF018786.toInt()),
+    background: Color = Color.White,
+    surface: Color = Color.White,
+    error: Color = Color(0xFFB00020.toInt()),
+    onPrimary: Color = Color.White,
+    onSecondary: Color = Color.Black,
+    onBackground: Color = Color.Black,
+    onSurface: Color = Color.Black,
+    onError: Color = Color.White
+): ColorPalette = ObservableColorPalette(
+    primary,
+    primaryVariant,
+    secondary,
+    secondaryVariant,
+    background,
+    surface,
+    error,
+    onPrimary,
+    onSecondary,
+    onBackground,
+    onSurface,
+    onError
+)
+
+/**
+ * Default observable backing implementation for [ColorPalette].
+ *
+ * Typically we would just change the value of the [Colors] ambient when the theme changes, but
+ * in the case of wide-reaching data such as colors in the [MaterialTheme], this will cause almost
+ * every UI component on a screen to be recomposed. In reality, we only want to recompose
+ * components that consume the specific color(s) that have been changed - so this default
+ * implementation is intended to be memoized in the ambient, and then when a new immutable
+ * [ColorPalette] is provided, we can simply diff and update any values that need to be changed.
+ * Because the internal values are provided by an @Model delegate class, components consuming the
+ * specific color will be recomposed, while everything else will remain the same. This allows for
+ * large performance improvements when the theme is being changed, especially if it is being
+ * animated.
+ */
+private class ObservableColorPalette(
+    primary: Color,
+    primaryVariant: Color,
+    secondary: Color,
+    secondaryVariant: Color,
+    background: Color,
+    surface: Color,
+    error: Color,
+    onPrimary: Color,
+    onSecondary: Color,
+    onBackground: Color,
+    onSurface: Color,
+    onError: Color
+) : ColorPalette {
+
+    constructor(colorPalette: ColorPalette) : this(
+        primary = colorPalette.primary,
+        primaryVariant = colorPalette.primaryVariant,
+        secondary = colorPalette.secondary,
+        secondaryVariant = colorPalette.secondaryVariant,
+        background = colorPalette.background,
+        surface = colorPalette.surface,
+        error = colorPalette.error,
+        >
+        >
+        >
+        >
+        >
+    )
+
+    override var primary by ObservableColor(primary)
+    override var primaryVariant by ObservableColor(primaryVariant)
+    override var secondary by ObservableColor(secondary)
+    override var secondaryVariant by ObservableColor(secondaryVariant)
+    override var background by ObservableColor(background)
+    override var surface by ObservableColor(surface)
+    override var error by ObservableColor(error)
+    override var onPrimary by ObservableColor(onPrimary)
+    override var onSecondary by ObservableColor(onSecondary)
+    override var onBackground by ObservableColor(onBackground)
+    override var onSurface by ObservableColor(onSurface)
+    override var onError by ObservableColor(onError)
+}
+
+@Model
+private class ObservableColor(var color: Color) {
+    operator fun getValue(thisObj: Any?, property: KProperty<*>) = color
+
+    operator fun setValue(thisObj: Any?, property: KProperty<*>, next: Color) {
+        if (color != next) color = next
+    }
+}
+
+/**
+ * Updates the internal values of the given [ObservableColorPalette] with values from the [other]
+ * [ColorPalette].
+ */
+private fun ObservableColorPalette.updateColorsFrom(other: ColorPalette): ObservableColorPalette {
+    primary = other.primary
+    primaryVariant = other.primaryVariant
+    secondary = other.secondary
+    secondaryVariant = other.secondaryVariant
+    background = other.background
+    surface = other.surface
+    error = other.error
+    >
+    >
+    >
+    >
+    >
+    return this
+}
+
+/**
+ * Memoizes and mutates the given [colorPalette] if it is an [ObservableColorPalette], otherwise
+ * just provides the given palette in the [Colors] [Ambient].
+ */
+@Composable
+internal fun ProvideColorPalette(colorPalette: ColorPalette, children: @Composable() () -> Unit) {
+    val palette = when (colorPalette) {
+        is ObservableColorPalette -> {
+            (+memo<ObservableColorPalette> {
+                ObservableColorPalette(colorPalette)
+            }).updateColorsFrom(colorPalette)
+        }
+        else -> colorPalette
+    }
+    Colors.Provider(palette, children)
+}
+
+/**
+ * Ambient used to pass [ColorPalette] down the tree.
+ *
+ * To retrieve the current value of this ambient, use [MaterialTheme.colors].
+ */
+internal val Colors = Ambient.of { ColorPalette() }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/DataTable.kt b/ui/ui-material/src/main/java/androidx/ui/material/DataTable.kt
index a0b34ec..5fa07d9 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/DataTable.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/DataTable.kt
@@ -317,7 +317,7 @@
     headerRowHeight: Dp = HeaderRowHeight,
     cellSpacing: EdgeInsets = CellSpacing,
     border: Border = Border(color = BorderColor, width = BorderWidth),
-    selectedColor: Color = +themeColor { primary.copy(alpha = 0.08f) },
+    selectedColor: Color = (+MaterialTheme.colors()).primary.copy(alpha = 0.08f),
     pagination: DataTablePagination? = null,
     sorting: DataTableSorting? = null,
     block: DataTableChildren.() -> Unit
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Divider.kt b/ui/ui-material/src/main/java/androidx/ui/material/Divider.kt
index bf6662d..1534067 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Divider.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Divider.kt
@@ -28,14 +28,14 @@
 /**
  * A divider is a thin line that groups content in lists and layouts
  *
- * @param color color of the divider line, [MaterialColors.onSurface] will be used by default
+ * @param color color of the divider line
  * @param height height of the divider line, 1 dp is used by default
  * @param indent left offset of this line, no offset by default
  */
 @Composable
 fun Divider(
     modifier: Modifier = Modifier.None,
-    color: Color = +themeColor { onSurface },
+    color: Color = (+MaterialTheme.colors()).surface,
     height: Dp = 1.dp,
     indent: Dp = 0.dp
 ) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
index e9e56e9..155d9b8 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
@@ -278,7 +278,7 @@
     val scrimContent = @Composable {
         Container(expanded = true) {
             val paint = +memo { Paint().apply { style = PaintingStyle.fill } }
-            val color = +themeColor { onSurface }
+            val color = (+MaterialTheme.colors()).onSurface
             Draw { canvas, parentSize ->
                 val scrimAlpha = fraction() * ScrimDefaultOpacity
                 paint.color = color.copy(alpha = scrimAlpha)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt b/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
index 922367c..b1d925f 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
@@ -51,7 +51,7 @@
  * @param minSize Minimum size of the button. Defaults to [FabSize]
  * @param shape Defines the Button's shape as well its shadow. When null is provided it uses
  *  the [Shapes.button] from [CurrentShapeAmbient].
- * @param color The background color. [MaterialColors.primary] is used by default.
+ * @param color The background color
  * @param elevation The z-coordinate at which to place this button. This controls the size
  *  of the shadow below the button.
  */
@@ -60,7 +60,7 @@
     onClick: (() -> Unit)? = null,
     minSize: Dp = FabSize,
     shape: Shape = CircleShape,
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     elevation: Dp = 6.dp,
     children: @Composable() () -> Unit
 ) {
@@ -87,7 +87,7 @@
  * @param icon Image to draw in the center.
  * @param onClick will be called when user clicked on the button. The button will be disabled
  *  when it is null.
- * @param color The background color. [MaterialColors.primary] is used by default.
+ * @param color The background color
  * @param elevation The z-coordinate at which to place this button. This controls the size
  *  of the shadow below the button.
  */
@@ -96,7 +96,7 @@
     icon: Image,
     onClick: (() -> Unit)? = null,
     shape: Shape = CircleShape,
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     elevation: Dp = 6.dp
 ) {
     FloatingActionButton( shape = shape, color = color, elevation = elevation) {
@@ -116,7 +116,7 @@
  * @param textStyle Optional [TextStyle] to apply for a [text]
  * @param onClick will be called when user clicked on the button. The button will be disabled
  *  when it is null.
- * @param color The background color. [MaterialColors.primary] is used by default.
+ * @param color The background color
  * @param elevation The z-coordinate at which to place this button. This controls the size
  *  of the shadow below the button.
  */
@@ -126,7 +126,7 @@
     icon: Image? = null,
     textStyle: TextStyle? = null,
     onClick: (() -> Unit)? = null,
-    color: Color = +themeColor { primary },
+    color: Color = (+MaterialTheme.colors()).primary,
     elevation: Dp = 6.dp
 ) {
     FloatingActionButton(
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ListItem.kt b/ui/ui-material/src/main/java/androidx/ui/material/ListItem.kt
index 634cc7888..aa9b60a 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ListItem.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ListItem.kt
@@ -162,7 +162,7 @@
     }
 
     if (onClick != null) {
-        val rippleColor = (+themeColor { onSurface }).copy(alpha = RippleOpacity)
+        val rippleColor = (+MaterialTheme.colors()).onSurface.copy(alpha = RippleOpacity)
         Ripple(bounded = true, color = rippleColor) {
             Clickable( children = item)
         }
@@ -470,7 +470,7 @@
 
 private data class ListItemTextStyle(
     val style: MaterialTypography.() -> TextStyle,
-    val color: MaterialColors.() -> Color,
+    val color: ColorPalette.() -> Color,
     val opacity: Float
 )
 
@@ -480,7 +480,8 @@
 ): @Composable() (() -> Unit)? {
     if (children == null) return null
     return {
-        val textColor = (+themeColor(textStyle.color)).copy(alpha = textStyle.opacity)
+        val colors = +MaterialTheme.colors()
+        val textColor = textStyle.color(colors).copy(alpha = textStyle.opacity)
         val appliedTextStyle = (+themeTextStyle(textStyle.style)).copy(color = textColor)
         CurrentTextStyleProvider(appliedTextStyle, children)
     }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
index fc6fa8e..fc52852 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -36,7 +36,6 @@
 import androidx.ui.material.ripple.CurrentRippleTheme
 import androidx.ui.material.ripple.DefaultRippleEffectFactory
 import androidx.ui.material.ripple.RippleTheme
-import androidx.ui.material.surface.Card
 import androidx.ui.material.surface.CurrentBackground
 import androidx.ui.text.font.FontWeight
 import androidx.ui.text.font.FontFamily
@@ -52,19 +51,22 @@
  * It defines colors as specified in the [Material Color theme creation spec](https://material.io/design/color/the-color-system.html#color-theme-creation)
  * and the typography defined in the [Material Type Scale spec](https://material.io/design/typography/the-type-system.html#type-scale).
  *
- * All values may be set by providing this component with the [colors][MaterialColors] and
+ * All values may be set by providing this component with the [colors][ColorPalette] and
  * [typography][MaterialTypography] attributes. Use this to configure the overall theme of your
  * application.
  *
  * @sample androidx.ui.material.samples.MaterialThemeSample
+ *
+ * @param colors A complete definition of the Material Color theme for this hierarchy
+ * @param typography A set of text styles to be used as this hierarchy's typography system
  */
 @Composable
 fun MaterialTheme(
-    colors: MaterialColors = MaterialColors(),
+    colors: ColorPalette = ColorPalette(),
     typography: MaterialTypography = MaterialTypography(),
     children: @Composable() () -> Unit
 ) {
-    Colors.Provider(value = colors) {
+    ProvideColorPalette(colors) {
         Typography.Provider(value = typography) {
             CurrentTextStyleProvider(value = typography.body1) {
                 MaterialRippleTheme {
@@ -75,15 +77,14 @@
     }
 }
 
-/**
- * This Ambient holds on to the current definition of colors for this application as described
- * by the Material spec. You can read the values in it when creating custom components that want
- * to use Material colors, as well as override the values when you want to re-style a part of your
- * hierarchy.
- *
- * To access values within this ambient, use [themeColor].
- */
-val Colors = Ambient.of<MaterialColors> { error("No colors found!") }
+object MaterialTheme {
+    /**
+     * Retrieves the current [ColorPalette] at the call site's position in the hierarchy.
+     *
+     * @sample androidx.ui.material.samples.ThemeColorSample
+     */
+    fun colors() = ambient(Colors)
+}
 
 /**
  * This Ambient holds on to the current definition of typography for this application as described
@@ -97,69 +98,6 @@
 val Typography = Ambient.of<MaterialTypography> { error("No typography found!") }
 
 /**
- * Data class holding color values as defined by the [Material color specification](https://material.io/design/color/the-color-system.html#color-theme-creation).
- */
-data class MaterialColors(
-    /**
-     * The primary color is the color displayed most frequently across your app’s screens and
-     * components.
-     */
-    val primary: Color = Color(0xFF6200EE),
-    /**
-     * The primary variant color is used to distinguish two elements of the app using the primary
-     * color, such as the top app bar and the system bar.
-     */
-    val primaryVariant: Color = Color(0xFF3700B3),
-    /**
-     * The secondary color provides more ways to accent and distinguish your product.
-     * Secondary colors are best for:
-     * - Floating action buttons
-     * - Selection controls, like sliders and switches
-     * - Highlighting selected text
-     * - Progress bars
-     * - Links and headlines
-     */
-    val secondary: Color = Color(0xFF03DAC6),
-    /**
-     * The secondary variant color is used to distinguish two elements of the app using the
-     * secondary color.
-     */
-    val secondaryVariant: Color = Color(0xFF018786),
-    /**
-     * The background color appears behind scrollable content.
-     */
-    val background: Color = Color.White,
-    /**
-     * The surface color is used on surfaces of components, such as cards, sheets and menus.
-     */
-    val surface: Color = Color.White,
-    /**
-     * The error color is used to indicate error within components, such as text fields.
-     */
-    val error: Color = Color(0xFFB00020),
-    /**
-     * Color used for text and icons displayed on top of the primary color.
-     */
-    val onPrimary: Color = Color.White,
-    /**
-     * Color used for text and icons displayed on top of the secondary color.
-     */
-    val onSecondary: Color = Color.Black,
-    /**
-     * Color used for text and icons displayed on top of the background color.
-     */
-    val onBackground: Color = Color.Black,
-    /**
-     * Color used for text and icons displayed on top of the surface color.
-     */
-    val onSurface: Color = Color.Black,
-    /**
-     * Color used for text and icons displayed on top of the error color.
-     */
-    val onError: Color = Color.White
-)
-
-/**
  * Data class holding typography definitions as defined by the [Material typography specification](https://material.io/design/typography/the-type-system.html#type-scale).
  */
 data class MaterialTypography(
@@ -240,7 +178,7 @@
                 }
             },
             opacity = effectOf {
-                val isDarkTheme = (+themeColor { surface }).luminance() < 0.5f
+                val isDarkTheme = (+MaterialTheme.colors()).surface.luminance() < 0.5f
                 if (isDarkTheme) 0.24f else 0.12f
             }
         )
@@ -249,16 +187,6 @@
 }
 
 /**
- * Helper effect that resolves [Color]s from the [Colors] ambient by applying [choosingBlock].
- *
- * @sample androidx.ui.material.samples.ThemeColorSample
- */
-@CheckResult(suggest = "+")
-fun themeColor(
-    choosingBlock: MaterialColors.() -> Color
-) = effectOf<Color> { (+ambient(Colors)).choosingBlock() }
-
-/**
  * Helper effect that resolves [TextStyle]s from the [Typography] ambient by applying
  * [choosingBlock].
  *
@@ -284,7 +212,7 @@
      */
     val button: Shape,
     /**
-     * Shape used for [Card]
+     * Shape used for [androidx.ui.material.surface.Card]
      */
     val card: Shape
     // TODO(Andrey): Add shapes for other surfaces? will see what we need.
@@ -320,4 +248,4 @@
 @CheckResult(suggest = "+")
 fun themeShape(
     choosingBlock: Shapes.() -> Shape
-) = effectOf<Shape> { (+ambient(CurrentShapeAmbient)).choosingBlock() }
+) = effectOf<Shape> { (+ambient(CurrentShapeAmbient)).choosingBlock() }
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt b/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
index 0d9a42a..65bda9a 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
@@ -54,13 +54,12 @@
  *
  * @param progress The progress of this progress indicator, where 0.0 represents no progress and 1.0
  * represents full progress
- * @param color The color of the progress indicator. [MaterialColors.primary] is used when no color
- * is provided
+ * @param color The color of the progress indicator.
  */
 @Composable
 fun LinearProgressIndicator(
     @FloatRange(from = 0.0, to = 1.0) progress: Float,
-    color: Color = +themeColor { primary }
+    color: Color = (+MaterialTheme.colors()).primary
 ) {
     DeterminateProgressIndicator(progress = progress) {
         Wrap {
@@ -83,11 +82,10 @@
  * An indeterminate linear progress indicator that represents continual progress without a defined
  * start or end point.
  *
- * @param color The color of the progress indicator. [MaterialColors.primary] is used when no color
- * is provided
+ * @param color The color of the progress indicator.
  */
 @Composable
-fun LinearProgressIndicator(color: Color = +themeColor { primary }) {
+fun LinearProgressIndicator(color: Color = (+MaterialTheme.colors()).primary) {
     Wrap {
         Container(width = LinearIndicatorWidth, height = StrokeWidth) {
             val paint = +paint(color, StrokeCap.butt)
@@ -158,13 +156,12 @@
  *
  * @param progress The progress of this progress indicator, where 0.0 represents no progress and 1.0
  * represents full progress
- * @param color The color of the progress indicator. [MaterialColors.primary] is used when no color
- * is provided
+ * @param color The color of the progress indicator.
  */
 @Composable
 fun CircularProgressIndicator(
     @FloatRange(from = 0.0, to = 1.0) progress: Float,
-    color: Color = +themeColor { primary }
+    color: Color = (+MaterialTheme.colors()).primary
 ) {
     DeterminateProgressIndicator(progress = progress) {
         CircularIndicatorContainer {
@@ -189,11 +186,10 @@
  * An indeterminate circular progress indicator that represents continual progress without a defined
  * start or end point.
  *
- * @param color The color of the progress indicator. [MaterialColors.primary] is used when no color
- * is provided
+ * @param color The color of the progress indicator.
  */
 @Composable
-fun CircularProgressIndicator(color: Color = +themeColor { primary }) {
+fun CircularProgressIndicator(color: Color = (+MaterialTheme.colors()).primary) {
     CircularIndicatorContainer {
         val paint = +paint(color, StrokeCap.square)
         Transition(definition = CircularIndeterminateTransition, toState = 1) { state ->
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt b/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
index 51e0143..069f7ba 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
@@ -85,7 +85,6 @@
  * @param onSelectedChange callback to be invoked when RadioButton is clicked,
  * therefore the selection of this item is requested
  * @param radioColor color for RadioButtons when selected.
- * [MaterialColors.secondary] is used by default
  * @param textStyle parameters for text customization
  */
 @Composable
@@ -93,7 +92,7 @@
     options: List<String>,
     selectedOption: String?,
     onSelectedChange: (String) -> Unit,
-    radioColor: Color = +themeColor { secondary },
+    radioColor: Color = (+MaterialTheme.colors()).secondary,
     textStyle: TextStyle? = null
 ) {
     RadioGroup {
@@ -146,11 +145,10 @@
      * Places [RadioButton] and [Text] inside the [Row].
      *
      * @param selected whether or not radio button in this item is selected
-     * @param onSelect callback to be invoked when your item is being clicked,
-     * therefore the selection of this item is requested. Not invoked if item is already selected
+     * @param onSelect callback to be invoked when your item is being clicked, therefore the
+     * selection of this item is requested. Not invoked if item is already selected
      * @param text to put as a label description of this item
-     * @param radioColor color for RadioButtons when selected.
-     * [MaterialColors.secondary] is used by default
+     * @param radioColor color for RadioButtons when selected
      * @param textStyle parameters for text customization
      */
     @Composable
@@ -158,7 +156,7 @@
         selected: Boolean,
         onSelect: () -> Unit,
         text: String,
-        radioColor: Color = +themeColor { secondary },
+        radioColor: Color = (+MaterialTheme.colors()).secondary,
         textStyle: TextStyle? = null
     ) {
         RadioGroupItem(selected = selected,  {
@@ -186,13 +184,13 @@
  * @param selected boolean state for this button: either it is selected or not
  * @param onSelect callback to be invoked when RadioButton is being clicked,
  * therefore the selection of this item is requested. Not invoked if item is already selected
- * @param color optional color. [MaterialColors.secondary] is used by default
+ * @param color color of the RadioButton
  */
 @Composable
 fun RadioButton(
     selected: Boolean,
     onSelect: (() -> Unit)?,
-    color: Color = +themeColor { secondary }
+    color: Color = (+MaterialTheme.colors()).secondary
 ) {
     Wrap {
         Ripple(bounded = false) {
@@ -202,7 +200,7 @@
                 Padding(padding = RadioButtonPadding) {
                     Container(width = RadioButtonSize, height = RadioButtonSize) {
                         val unselectedColor =
-                            (+themeColor { onSurface }).copy(alpha = UnselectedOpacity)
+                            (+MaterialTheme.colors()).onSurface.copy(alpha = UnselectedOpacity)
                         val definition = +memo(color, unselectedColor) {
                             generateTransitionDefinition(color, unselectedColor)
                         }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
index 6742be4..16f154d 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
@@ -49,14 +49,13 @@
  * @param onCheckedChange callback to be invoked when Switch is being clicked,
  * therefore the change of checked state is requested.
  * if `null`, Switch appears in [checked] state and remains disabled
- * @param color optional active color for Switch,
- * by default [MaterialColors.secondaryVariant] will be used
+ * @param color active color for Switch,
  */
 @Composable
 fun Switch(
     checked: Boolean,
     onCheckedChange: ((Boolean) -> Unit)?,
-    color: Color = +themeColor { secondaryVariant }
+    color: Color = (+MaterialTheme.colors()).secondaryVariant
 ) {
     Wrap {
         Ripple(bounded = false) {
@@ -94,11 +93,11 @@
 
 @Composable
 private fun DrawSwitch(checked: Boolean, checkedThumbColor: Color, thumbValue: ValueHolder<Float>) {
-    val thumbColor = if (checked) checkedThumbColor else +themeColor { surface }
+    val thumbColor = if (checked) checkedThumbColor else (+MaterialTheme.colors()).surface
     val trackColor = if (checked) {
         checkedThumbColor.copy(alpha = CheckedTrackOpacity)
     } else {
-        (+themeColor { onSurface }).copy(alpha = UncheckedTrackOpacity)
+        (+MaterialTheme.colors()).onSurface.copy(alpha = UncheckedTrackOpacity)
     }
     Draw { canvas, parentSize ->
         drawTrack(canvas, parentSize, trackColor)
@@ -159,4 +158,4 @@
 private val SwitchHeight = ThumbDiameter
 private val ThumbPathLength = TrackWidth - ThumbDiameter
 
-private val AnimationBuilder = TweenBuilder<Float>().apply { duration = 100 }
\ No newline at end of file
+private val AnimationBuilder = TweenBuilder<Float>().apply { duration = 100 }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt b/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
index 351decf..e4b1931 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
@@ -129,7 +129,7 @@
     },
     tab: @Composable() (Int, T) -> Unit
 ) {
-    Surface(color = +themeColor { primary }) {
+    Surface(color = (+MaterialTheme.colors()).primary) {
         val divider = TabRow.Divider
 
         val tabs = @Composable {
@@ -367,7 +367,7 @@
      */
     @Composable
     fun Indicator() {
-        ColoredRect(color = +themeColor { onPrimary }, height = IndicatorHeight)
+        ColoredRect(color = (+MaterialTheme.colors()).onPrimary, height = IndicatorHeight)
     }
 
     /**
@@ -412,14 +412,14 @@
     }
 
     internal val Divider = @Composable {
-        val  { onPrimary }
+        val >
         Divider(color = (onPrimary.copy(alpha = DividerOpacity)))
     }
 }
 
 /**
  * A Tab represents a single page of content using a text label and/or image. It represents its
- * selected state by tinting the text label and/or image with [MaterialColors.onPrimary].
+ * selected state by tinting the text label and/or image with [ColorPalette.onPrimary].
  *
  * This should typically be used inside of a [TabRow], see the corresponding documentation for
  * example usage.
@@ -431,7 +431,7 @@
  */
 @Composable
 fun Tab(text: String? = null, icon: Image? = null, selected: Boolean, onSelected: () -> Unit) {
-    val tint = +themeColor { onPrimary }
+    val tint = (+MaterialTheme.colors()).onPrimary
     when {
         text != null && icon != null -> CombinedTab(text, icon, selected, onSelected, tint)
         text != null -> TextTab(text, selected, onSelected, tint)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Text.kt b/ui/ui-material/src/main/java/androidx/ui/material/Text.kt
index b4aca7b..8ab917c 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Text.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Text.kt
@@ -18,7 +18,6 @@
 
 import androidx.annotation.FloatRange
 import androidx.ui.graphics.Color
-import androidx.compose.ambient
 import androidx.compose.effectOf
 import androidx.compose.unaryPlus
 import androidx.ui.core.currentTextStyle
@@ -26,22 +25,21 @@
 
 /**
  * Tries to match the background color to correlated text color. For example,
- * on [MaterialColors.primary] background [MaterialColors.onPrimary] will be used.
- * If the background is not from the [MaterialColors] the text color will not be
+ * on [ColorPalette.primary] background [ColorPalette.onPrimary] will be used.
+ * If the background is not from the [ColorPalette] the text color will not be
  * automatically resolved.
  */
 fun textColorForBackground(background: Color) = effectOf<Color?> {
-    with(+ambient(Colors)) {
-        when (background) {
-            primary -> onPrimary
-            primaryVariant -> onPrimary
-            secondary -> onSecondary
-            secondaryVariant -> onSecondary
-            this.background -> onBackground
-            surface -> onSurface
-            error -> onError
-            else -> null
-        }
+    val themeColorPalette = +MaterialTheme.colors()
+    when (background) {
+        themeColorPalette.primary -> themeColorPalette.onPrimary
+        themeColorPalette.primaryVariant -> themeColorPalette.onPrimary
+        themeColorPalette.secondary -> themeColorPalette.onSecondary
+        themeColorPalette.secondaryVariant -> themeColorPalette.onSecondary
+        themeColorPalette.background -> themeColorPalette.onBackground
+        themeColorPalette.surface -> themeColorPalette.onSurface
+        themeColorPalette.error -> themeColorPalette.onError
+        else -> null
     }
 }
 
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
index 9cf4053..878e5c6 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
@@ -23,7 +23,7 @@
 import androidx.ui.engine.geometry.Shape
 import androidx.ui.foundation.shape.border.Border
 import androidx.ui.graphics.Color
-import androidx.ui.material.themeColor
+import androidx.ui.material.MaterialTheme
 import androidx.ui.material.themeShape
 
 /**
@@ -39,7 +39,7 @@
 @Composable
 fun Card(
     shape: Shape = +themeShape { card },
-    color: Color = +themeColor { surface },
+    color: Color = (+MaterialTheme.colors()).surface,
     border: Border? = null,
     elevation: Dp = 1.dp,
     children: @Composable() () -> Unit
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
index 5cc302d..6972aa4 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
@@ -35,9 +35,9 @@
 import androidx.ui.foundation.shape.RectangleShape
 import androidx.ui.foundation.shape.border.DrawBorder
 import androidx.ui.graphics.Color
-import androidx.ui.material.MaterialColors
+import androidx.ui.material.ColorPalette
+import androidx.ui.material.MaterialTheme
 import androidx.ui.material.textColorForBackground
-import androidx.ui.material.themeColor
 import androidx.ui.text.TextStyle
 
 /**
@@ -56,8 +56,8 @@
  * visually relates to other surfaces and how that surface casts shadows.
  *
  * The text color for inner [Text] components will try to match the correlated color
- * for the background [color]. For example, on [MaterialColors.surface] background
- * [MaterialColors.onSurface] will be used for text. To modify these default style
+ * for the background [color]. For example, on [ColorPalette.surface] background
+ * [ColorPalette.onSurface] will be used for text. To modify these default style
  * values use [CurrentTextStyleProvider] or provide direct styling to your components.
  * @see textColorForBackground
  *
@@ -73,7 +73,7 @@
 fun Surface(
     modifier: Modifier = Modifier.None,
     shape: Shape = RectangleShape,
-    color: Color = +themeColor { surface },
+    color: Color = (+MaterialTheme.colors()).surface,
     border: Border? = null,
     elevation: Dp = 0.dp,
     children: @Composable() () -> Unit