1 /*
2 * Copyright 2024 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.material3.benchmark
18
19 import androidx.compose.animation.core.snap
20 import androidx.compose.foundation.layout.fillMaxHeight
21 import androidx.compose.foundation.layout.fillMaxWidth
22 import androidx.compose.material.icons.Icons
23 import androidx.compose.material.icons.filled.Add
24 import androidx.compose.material.icons.filled.Check
25 import androidx.compose.material.icons.filled.Edit
26 import androidx.compose.material.icons.filled.Favorite
27 import androidx.compose.material.icons.filled.MoreVert
28 import androidx.compose.material.icons.filled.Person
29 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
30 import androidx.compose.material3.FloatingToolbarDefaults
31 import androidx.compose.material3.HorizontalFloatingToolbar
32 import androidx.compose.material3.Icon
33 import androidx.compose.material3.IconButton
34 import androidx.compose.material3.MaterialTheme
35 import androidx.compose.material3.VerticalFloatingToolbar
36 import androidx.compose.runtime.Composable
37 import androidx.compose.runtime.MutableState
38 import androidx.compose.runtime.mutableStateOf
39 import androidx.compose.runtime.remember
40 import androidx.compose.testutils.LayeredComposeTestCase
41 import androidx.compose.testutils.ToggleableTestCase
42 import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
43 import androidx.compose.testutils.benchmark.benchmarkFirstCompose
44 import androidx.compose.testutils.benchmark.benchmarkFirstDraw
45 import androidx.compose.testutils.benchmark.benchmarkFirstLayout
46 import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
47 import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
48 import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
49 import androidx.compose.testutils.benchmark.toggleStateBenchmarkLayout
50 import androidx.compose.testutils.benchmark.toggleStateBenchmarkMeasure
51 import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
52 import androidx.compose.ui.Modifier
53 import androidx.test.filters.LargeTest
54 import org.junit.Ignore
55 import org.junit.Rule
56 import org.junit.Test
57 import org.junit.runner.RunWith
58 import org.junit.runners.Parameterized
59
60 @LargeTest
61 @RunWith(Parameterized::class)
62 class FloatingToolbarBenchmark(private val type: FloatingToolbarType) {
63
64 companion object {
65 @Parameterized.Parameters(name = "{0}")
66 @JvmStatic
parametersnull67 fun parameters() = FloatingToolbarType.values()
68 }
69
70 @get:Rule val benchmarkRule = ComposeBenchmarkRule()
71
72 private val mFloatingToolbarTestCaseFactory = { FloatingToolbarTestCase(type) }
<lambda>null73 private val floatingToolbarWithFabTestCaseFactory = { FloatingToolbarWithFabTestCase(type) }
74
75 @Test
floatingToolbar_firstPixelnull76 fun floatingToolbar_firstPixel() {
77 benchmarkRule.benchmarkToFirstPixel(mFloatingToolbarTestCaseFactory)
78 }
79
80 @Test
floatingToolbarWithFab_firstPixelnull81 fun floatingToolbarWithFab_firstPixel() {
82 benchmarkRule.benchmarkToFirstPixel(floatingToolbarWithFabTestCaseFactory)
83 }
84
85 @Ignore
86 @Test
floatingToolbar_first_composenull87 fun floatingToolbar_first_compose() {
88 benchmarkRule.benchmarkFirstCompose(mFloatingToolbarTestCaseFactory)
89 }
90
91 @Ignore
92 @Test
floatingToolbar_first_measurenull93 fun floatingToolbar_first_measure() {
94 benchmarkRule.benchmarkFirstMeasure(mFloatingToolbarTestCaseFactory)
95 }
96
97 @Ignore
98 @Test
floatingToolbar_first_layoutnull99 fun floatingToolbar_first_layout() {
100 benchmarkRule.benchmarkFirstLayout(mFloatingToolbarTestCaseFactory)
101 }
102
103 @Ignore
104 @Test
floatingToolbar_first_drawnull105 fun floatingToolbar_first_draw() {
106 benchmarkRule.benchmarkFirstDraw(mFloatingToolbarTestCaseFactory)
107 }
108
109 @Test
floatingToolbarWithFab_toggleState_layoutnull110 fun floatingToolbarWithFab_toggleState_layout() {
111 benchmarkRule.toggleStateBenchmarkLayout(
112 caseFactory = floatingToolbarWithFabTestCaseFactory,
113 assertOneRecomposition = false
114 )
115 }
116
117 @Ignore
118 @Test
floatingToolbarWithFab_toggleState_recomposenull119 fun floatingToolbarWithFab_toggleState_recompose() {
120 benchmarkRule.toggleStateBenchmarkRecompose(
121 caseFactory = floatingToolbarWithFabTestCaseFactory,
122 assertOneRecomposition = false
123 )
124 }
125
126 @Ignore
127 @Test
floatingToolbarWithFab_toggleState_measurenull128 fun floatingToolbarWithFab_toggleState_measure() {
129 benchmarkRule.toggleStateBenchmarkMeasure(
130 caseFactory = floatingToolbarWithFabTestCaseFactory,
131 assertOneRecomposition = false
132 )
133 }
134
135 @Ignore
136 @Test
floatingToolbarWithFab_toggleState_drawnull137 fun floatingToolbarWithFab_toggleState_draw() {
138 benchmarkRule.toggleStateBenchmarkDraw(
139 caseFactory = floatingToolbarWithFabTestCaseFactory,
140 assertOneRecomposition = false
141 )
142 }
143 }
144
145 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
146 internal class FloatingToolbarTestCase(private val type: FloatingToolbarType) :
147 LayeredComposeTestCase() {
148 @Composable
MeasuredContentnull149 override fun MeasuredContent() {
150 when (type) {
151 FloatingToolbarType.Horizontal ->
152 HorizontalFloatingToolbar(
153 expanded = true,
154 modifier = Modifier.fillMaxWidth(),
155 leadingContent = { LeadingContent() },
156 trailingContent = { TrailingContent() },
157 content = { MainContent() },
158 )
159 FloatingToolbarType.Vertical ->
160 VerticalFloatingToolbar(
161 expanded = true,
162 modifier = Modifier.fillMaxHeight(),
163 leadingContent = { LeadingContent() },
164 trailingContent = { TrailingContent() },
165 content = { MainContent() },
166 )
167 }
168 }
169
170 @Composable
171 override fun ContentWrappers(content: @Composable () -> Unit) {
<lambda>null172 MaterialTheme { content() }
173 }
174 }
175
176 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
177 internal class FloatingToolbarWithFabTestCase(private val type: FloatingToolbarType) :
178 LayeredComposeTestCase(), ToggleableTestCase {
179 private lateinit var expanded: MutableState<Boolean>
180
181 @Composable
MeasuredContentnull182 override fun MeasuredContent() {
183 expanded = remember { mutableStateOf(false) }
184 when (type) {
185 FloatingToolbarType.Horizontal ->
186 HorizontalFloatingToolbar(
187 expanded = expanded.value,
188 floatingActionButton = { ToolbarFab() },
189 // Snap the expand and collapse animations.
190 animationSpec = snap()
191 ) {
192 ToolbarContent()
193 }
194 FloatingToolbarType.Vertical ->
195 VerticalFloatingToolbar(
196 expanded = expanded.value,
197 floatingActionButton = { ToolbarFab() },
198 // Snap the expand and collapse animations.
199 animationSpec = snap()
200 ) {
201 ToolbarContent()
202 }
203 }
204 }
205
206 @Composable
207 override fun ContentWrappers(content: @Composable () -> Unit) {
<lambda>null208 MaterialTheme { content() }
209 }
210
toggleStatenull211 override fun toggleState() {
212 expanded.value = !expanded.value
213 }
214 }
215
216 enum class FloatingToolbarType {
217 Horizontal,
218 Vertical,
219 }
220
221 @Composable
LeadingContentnull222 private fun LeadingContent() {
223 IconButton(onClick = { /* doSomething() */ }) {
224 Icon(Icons.Filled.Add, contentDescription = "Localized description")
225 }
226 }
227
228 @Composable
TrailingContentnull229 private fun TrailingContent() {
230 IconButton(onClick = { /* doSomething() */ }) {
231 Icon(Icons.Filled.Check, contentDescription = "Localized description")
232 }
233 }
234
235 @Composable
MainContentnull236 private fun MainContent() {
237 IconButton(onClick = { /* doSomething() */ }) {
238 Icon(Icons.Filled.Edit, contentDescription = "Localized description")
239 }
240 }
241
242 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
243 @Composable
ToolbarFabnull244 private fun ToolbarFab() {
245 FloatingToolbarDefaults.StandardFloatingActionButton(
246 onClick = { /* doSomething() */ },
247 ) {
248 Icon(Icons.Filled.Check, "Localized description")
249 }
250 }
251
252 @Composable
ToolbarContentnull253 private fun ToolbarContent() {
254 IconButton(onClick = { /* doSomething() */ }) {
255 Icon(Icons.Filled.Person, contentDescription = "Localized description")
256 }
257 IconButton(onClick = { /* doSomething() */ }) {
258 Icon(Icons.Filled.Edit, contentDescription = "Localized description")
259 }
260 IconButton(onClick = { /* doSomething() */ }) {
261 Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
262 }
263 IconButton(
264 onClick = { /* doSomething() */ },
265 ) {
266 Icon(Icons.Filled.MoreVert, contentDescription = "Localized description")
267 }
268 }
269