1 /*
<lambda>null2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.material.demos
18
19 import android.os.Bundle
20 import androidx.activity.ComponentActivity
21 import androidx.activity.compose.setContent
22 import androidx.compose.animation.core.FastOutSlowInEasing
23 import androidx.compose.foundation.background
24 import androidx.compose.foundation.layout.Box
25 import androidx.compose.foundation.layout.fillMaxWidth
26 import androidx.compose.foundation.layout.height
27 import androidx.compose.foundation.layout.padding
28 import androidx.compose.foundation.lazy.LazyColumn
29 import androidx.compose.foundation.lazy.LazyListState
30 import androidx.compose.foundation.lazy.rememberLazyListState
31 import androidx.compose.foundation.shape.CircleShape
32 import androidx.compose.foundation.shape.RoundedCornerShape
33 import androidx.compose.material.BottomAppBar
34 import androidx.compose.material.Colors
35 import androidx.compose.material.ExtendedFloatingActionButton
36 import androidx.compose.material.FabPosition
37 import androidx.compose.material.MaterialTheme
38 import androidx.compose.material.Scaffold
39 import androidx.compose.material.Text
40 import androidx.compose.material.TopAppBar
41 import androidx.compose.material.lightColors
42 import androidx.compose.runtime.Composable
43 import androidx.compose.runtime.LaunchedEffect
44 import androidx.compose.runtime.MutableState
45 import androidx.compose.runtime.mutableFloatStateOf
46 import androidx.compose.runtime.snapshotFlow
47 import androidx.compose.ui.Alignment
48 import androidx.compose.ui.Modifier
49 import androidx.compose.ui.graphics.Color
50 import androidx.compose.ui.graphics.lerp
51 import androidx.compose.ui.graphics.toArgb
52 import androidx.compose.ui.unit.dp
53 import kotlinx.coroutines.flow.collect
54
55 /**
56 * Demo activity that animates the primary, secondary, and background colours in the [MaterialTheme]
57 * as the user scrolls. This has the effect of going from a 'light' theme to a 'dark' theme.
58 */
59 class DynamicThemeActivity : ComponentActivity() {
60 private val scrollFraction = mutableFloatStateOf(0f)
61
62 override fun onCreate(savedInstanceState: Bundle?) {
63 super.onCreate(savedInstanceState)
64 setContent {
65 val palette = interpolateTheme(scrollFraction.floatValue)
66 val darkenedPrimary = palette.darkenedPrimary
67 @Suppress("DEPRECATION")
68 window.statusBarColor = darkenedPrimary
69 @Suppress("DEPRECATION")
70 window.navigationBarColor = darkenedPrimary
71
72 DynamicThemeApp(scrollFraction, palette)
73 }
74 }
75
76 private val Colors.darkenedPrimary: Int
77 get() {
78 return with(primary) {
79 copy(red = red * 0.75f, green = green * 0.75f, blue = blue * 0.75f)
80 }
81 .toArgb()
82 }
83 }
84
85 private typealias ScrollFraction = MutableState<Float>
86
87 private val LazyListState.scrollOffset: Float
88 get() {
89 val total = layoutInfo.totalItemsCount
90 if (total == 0 || layoutInfo.visibleItemsInfo.isEmpty()) {
91 return 0f
92 } else {
93 val itemSize = layoutInfo.visibleItemsInfo.first().size
94 val currentOffset = firstVisibleItemIndex * itemSize + firstVisibleItemScrollOffset
95 return (currentOffset.toFloat() / (total * itemSize)).coerceIn(0f, 1f)
96 }
97 }
98
99 @Composable
DynamicThemeAppnull100 private fun DynamicThemeApp(scrollFraction: ScrollFraction, palette: Colors) {
101 MaterialTheme(palette) {
102 val state = rememberLazyListState()
103 LaunchedEffect(state) {
104 snapshotFlow { state.scrollOffset }.collect { scrollFraction.value = it }
105 }
106 Scaffold(
107 topBar = { TopAppBar({ Text("Scroll down!") }) },
108 bottomBar = { BottomAppBar(cutoutShape = CircleShape) {} },
109 floatingActionButton = { Fab(scrollFraction) },
110 floatingActionButtonPosition = FabPosition.Center,
111 isFloatingActionButtonDocked = true,
112 content = { innerPadding ->
113 LazyColumn(state = state, contentPadding = innerPadding) { items(20) { Card(it) } }
114 }
115 )
116 }
117 }
118
119 @Composable
Fabnull120 private fun Fab(scrollFraction: ScrollFraction) {
121 val fabText = emojiForScrollFraction(scrollFraction.value)
122 ExtendedFloatingActionButton(
123 text = { Text(fabText, style = MaterialTheme.typography.h5) },
124 onClick = {}
125 )
126 }
127
128 @Composable
Cardnull129 private fun Card(index: Int) {
130 val shapeColor = lerp(Color(0xFF303030), Color.White, index / 19f)
131 val textColor = lerp(Color.White, Color(0xFF303030), index / 19f)
132 // TODO: ideally this would be a Card but currently Surface consumes every
133 // colour from the Material theme to work out text colour, so we end up doing a
134 // large amount of work here when the top level theme changes
135 Box(
136 Modifier.padding(25.dp)
137 .fillMaxWidth()
138 .height(150.dp)
139 .background(shapeColor, RoundedCornerShape(10.dp)),
140 contentAlignment = Alignment.Center
141 ) {
142 Text("Card ${index + 1}", color = textColor)
143 }
144 }
145
interpolateThemenull146 private fun interpolateTheme(fraction: Float): Colors {
147 val interpolatedFraction = FastOutSlowInEasing.transform(fraction)
148
149 val primary = lerp(Color(0xFF6200EE), Color(0xFF303030), interpolatedFraction)
150 val secondary = lerp(Color(0xFF03DAC6), Color(0xFFBB86FC), interpolatedFraction)
151 val background = lerp(Color.White, Color(0xFF121212), interpolatedFraction)
152
153 return lightColors(primary = primary, secondary = secondary, background = background)
154 }
155
156 /** 'Animate' the emoji in the FAB from 'sun' to 'moon' as we darken the theme */
emojiForScrollFractionnull157 private fun emojiForScrollFraction(fraction: Float): String {
158 return when {
159 // Sun
160 fraction < 1 / 7f -> "\u2600"
161 // Sun behind small cloud
162 fraction < 2 / 7f -> "\uD83C\uDF24"
163 // Sun behind cloud
164 fraction < 3 / 7f -> "\uD83C\uDF25"
165 // Cloud
166 fraction < 4 / 7f -> "\u2601"
167 // Cloud with rain
168 fraction < 5 / 7f -> "\uD83C\uDF27"
169 // Cloud with lightning
170 fraction < 6 / 7f -> "\uD83C\uDF29"
171 // Moon
172 else -> "\uD83C\uDF15"
173 }
174 }
175