diff options
author | 2025-03-10 06:28:53 +0000 | |
---|---|---|
committer | 2025-03-13 03:13:54 +0000 | |
commit | 165058c81c7d402cf6580e57f5efb7dd009e941d (patch) | |
tree | 9ac17c67b71deeb95b5174a4e10760566a83908a | |
parent | 595ae7b5cb5550b63fa29d1a2e664e4ea8101ed2 (diff) |
[DocsUI M3] Restyle drag drop badge for M3
* When there are multiple dragging files:
* add an additional drag layer
* add a file counter
* Update drag show (radius/offset) to match the new M3 spec.
* drag shadow view is an isolated view without the theme
context, so M3 color attributes like "?attr/colorXXX"
can't be used directly, instead, system color tokens are
being used in light/dark mode. Since system color tokens
are only available in SDK 31, fallback static colors are
being used in the original colors.xml.
Check the attached bug for demo.
Bug: 377771158
Test: m DocumentsUIGoogle && manual inspection
Flag: com.android.documentsui.flags.use_material3
Change-Id: Ia00bb0037a19813b4137d9d6cd7a42441b7f255e
16 files changed, 406 insertions, 80 deletions
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/drawable/drag_file_counter_background.xml b/res/flag(com.android.documentsui.flags.use_material3)/drawable/drag_file_counter_background.xml new file mode 100644 index 000000000..e7e02ee7d --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/drawable/drag_file_counter_background.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2025 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@color/drag_file_counter_background" /> + <corners android:radius="@dimen/drag_file_counter_height" /> +</shape> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/drawable/drag_shadow_background.xml b/res/flag(com.android.documentsui.flags.use_material3)/drawable/drag_shadow_background.xml index eed005eca..0052f4609 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/drawable/drag_shadow_background.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/drawable/drag_shadow_background.xml @@ -17,9 +17,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/item_drag_shadow_background" /> - <corners - android:bottomRightRadius="2dp" - android:bottomLeftRadius="2dp" - android:topLeftRadius="2dp" - android:topRightRadius="2dp"/> + <corners android:radius="@dimen/drag_content_radius" /> </shape> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/drawable/drop_badge_container_background.xml b/res/flag(com.android.documentsui.flags.use_material3)/drawable/drop_badge_container_background.xml new file mode 100644 index 000000000..7dff0ada8 --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/drawable/drop_badge_container_background.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2025 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. +--> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:bottom="@dimen/drop_icon_offset" + android:left="0dp" + android:right="@dimen/drop_icon_offset" + android:top="0dp"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/drop_mime_icon_wrapper_radius" /> + <solid android:color="@color/drag_mime_icon_wrapper_background" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/res/flag(com.android.documentsui.flags.use_material3)/drawable/ic_drop_copy_badge.xml b/res/flag(com.android.documentsui.flags.use_material3)/drawable/ic_drop_copy_badge.xml index 370c4fe70..c929806fe 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/drawable/ic_drop_copy_badge.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/drawable/ic_drop_copy_badge.xml @@ -14,24 +14,19 @@ Copyright (C) 2024 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="14dp" - android:height="14dp" - android:viewportWidth="28.0" - android:viewportHeight="28.0"> - - <group - android:name="whiteBg"> + android:width="14dp" + android:height="14dp" + android:viewportWidth="14" + android:viewportHeight="14"> <path - android:fillColor="#FFFFFFFF" - android:pathData="M0,15a15,15 0 1,0 30,0a15,15 0 1,0 -30,0" /> - </group> - - <group - android:name="badge" - android:translateX="2" - android:translateY="2"> - <path - android:fillColor="#FF0B8043" - android:pathData="M13,0 C5.824,0 0,5.824 0,13 C0,20.176 5.824,26 13,26 C20.176,26 26,20.176 26,13 C26,5.824 20.176,0 13,0 L13,0 Z M19,14 L14,14 L14,19 L12,19 L12,14 L7,14 L7,12 L12,12 L12,7 L14,7 L14,12 L19,12 L19,14 Z" /> + android:pathData="M7,0L7,0A7,7 0,0 1,14 7L14,7A7,7 0,0 1,7 14L7,14A7,7 0,0 1,0 7L0,7A7,7 0,0 1,7 0z" + android:fillColor="@color/drop_icon_copy_container_background"/> + <group> + <clip-path + android:pathData="M1,1h12v12h-12z"/> + <path + android:pathData="M6.387,11.438V7.613H2.563V6.387H6.387V2.563H7.613V6.387H11.438V7.613H7.613V11.438H6.387Z" + android:fillColor="@color/drop_icon_symbol_color"/> </group> </vector> + diff --git a/res/flag(com.android.documentsui.flags.use_material3)/drawable/ic_reject_drop_badge.xml b/res/flag(com.android.documentsui.flags.use_material3)/drawable/ic_reject_drop_badge.xml index 06db34617..ea2c3950c 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/drawable/ic_reject_drop_badge.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/drawable/ic_reject_drop_badge.xml @@ -15,24 +15,18 @@ Copyright (C) 2024 The Android Open Source Project --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="14dp" - android:height="14dp" - android:viewportWidth="28.0" - android:viewportHeight="28.0"> - - <group - android:name="whiteBg"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M0,15a15,15 0 1,0 30,0a15,15 0 1,0 -30,0" /> - </group> - - <group - android:name="badge" - android:translateX="2" - android:translateY="2"> + android:width="14dp" + android:height="14dp" + android:viewportWidth="14" + android:viewportHeight="14"> + <path + android:pathData="M7,0L7,0A7,7 0,0 1,14 7L14,7A7,7 0,0 1,7 14L7,14A7,7 0,0 1,0 7L0,7A7,7 0,0 1,7 0z" + android:fillColor="@color/drop_icon_reject_container_background"/> + <group> + <clip-path + android:pathData="M1,1h12v12h-12z"/> <path - android:fillColor="#FFC53929" - android:pathData="M3.8056487,3.8056487 C-1.26854957,8.87984696 -1.26854957,17.1162267 3.8056487,22.190425 C8.87984696,27.2646233 17.1162267,27.2646233 22.190425,22.190425 C27.2646233,17.1162267 27.2646233,8.87984696 22.190425,3.8056487 C17.1162267,-1.26854957 8.87984696,-1.26854957 3.8056487,3.8056487 L3.8056487,3.8056487 Z M16.5335708,17.9477843 L12.9980369,14.4122504 L9.46250295,17.9477843 L8.04828938,16.5335708 L11.5838233,12.9980369 L8.04828938,9.46250295 L9.46250295,8.04828938 L12.9980369,11.5838233 L16.5335708,8.04828938 L17.9477843,9.46250295 L14.4122504,12.9980369 L17.9477843,16.5335708 L16.5335708,17.9477843 L16.5335708,17.9477843 Z" /> + android:pathData="M7,12.038C6.308,12.038 5.654,11.908 5.037,11.65C4.429,11.383 3.896,11.021 3.438,10.563C2.979,10.096 2.617,9.558 2.35,8.95C2.092,8.333 1.962,7.679 1.962,6.988C1.962,6.287 2.092,5.637 2.35,5.037C2.617,4.429 2.979,3.896 3.438,3.438C3.896,2.979 4.429,2.621 5.037,2.362C5.654,2.096 6.308,1.962 7,1.962C7.7,1.962 8.354,2.096 8.962,2.362C9.571,2.621 10.104,2.979 10.563,3.438C11.021,3.896 11.379,4.429 11.637,5.037C11.904,5.637 12.038,6.287 12.038,6.988C12.038,7.679 11.904,8.333 11.637,8.95C11.379,9.558 11.021,10.096 10.563,10.563C10.104,11.021 9.571,11.383 8.962,11.65C8.354,11.908 7.7,12.038 7,12.038ZM7,10.813C7.417,10.813 7.817,10.75 8.2,10.625C8.583,10.5 8.925,10.317 9.225,10.075L3.912,4.762C3.679,5.079 3.5,5.425 3.375,5.8C3.25,6.175 3.188,6.571 3.188,6.988C3.188,8.046 3.558,8.95 4.3,9.7C5.042,10.442 5.942,10.813 7,10.813ZM10.087,9.212C10.321,8.896 10.5,8.55 10.625,8.175C10.75,7.8 10.813,7.404 10.813,6.988C10.813,5.929 10.442,5.033 9.7,4.3C8.958,3.558 8.058,3.188 7,3.188C6.583,3.188 6.188,3.25 5.813,3.375C5.446,3.492 5.104,3.662 4.787,3.888L10.087,9.212Z" + android:fillColor="@color/drop_icon_symbol_color"/> </group> </vector> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/additional_drag_shadow.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/additional_drag_shadow.xml new file mode 100644 index 000000000..e829baefd --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/additional_drag_shadow.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2025 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. +--> + +<!-- Transparent container so shadow layer can be drawn --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/drag_shadow_width" + android:layout_height="@dimen/drag_shadow_height" + android:background="@color/item_drag_shadow_container_background"> + + <View + android:layout_width="@dimen/drag_content_width" + android:layout_height="@dimen/drag_content_height" + android:layout_marginTop="@dimen/drag_additional_layer_margin_top" + android:layout_marginStart="@dimen/drag_shadow_radius" + android:background="@drawable/drag_shadow_background" /> +</FrameLayout>
\ No newline at end of file diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/drag_shadow_layout.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/drag_shadow_layout.xml index c3de2399c..8181cd403 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/layout/drag_shadow_layout.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/drag_shadow_layout.xml @@ -15,34 +15,53 @@ --> <!-- Transparent container so shadow layer can be drawn --> -<LinearLayout +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:padding="8dp" + android:layout_width="@dimen/drag_shadow_width" + android:layout_height="@dimen/drag_shadow_height" android:background="@color/item_drag_shadow_container_background"> <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingStart="12dp" - android:paddingEnd="12dp" + android:layout_width="@dimen/drag_content_width" + android:layout_height="@dimen/drag_content_height" + android:layout_marginTop="@dimen/drag_file_counter_offset" + android:layout_marginStart="@dimen/drag_content_margin_start" + android:paddingHorizontal="@dimen/space_extra_small_4" android:orientation="horizontal" android:gravity="center_vertical" android:background="@drawable/drag_shadow_background"> - <include layout="@layout/drop_badge"/> + <LinearLayout + android:layout_width="@dimen/drop_badge_container_size" + android:layout_height="@dimen/drop_badge_container_size" + android:orientation="horizontal" + android:gravity="center_horizontal" + android:background="@drawable/drop_badge_container_background"> + + <include layout="@layout/drop_badge"/> + + </LinearLayout> + <TextView android:id="@android:id/title" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginStart="@dimen/space_extra_small_4" android:maxLines="1" android:ellipsize="end" android:textAlignment="viewStart" - android:textAppearance="@style/Subhead" - android:paddingStart="6dp" - android:paddingBottom="1dp"/> + android:textAppearance="@style/DragBadgeText" /> </LinearLayout> -</LinearLayout> + + <TextView + android:id="@+id/drag_file_counter" + android:layout_width="wrap_content" + android:layout_height="@dimen/drag_file_counter_height" + android:paddingHorizontal="@dimen/space_extra_small_4" + android:layout_gravity="top|end" + android:background="@drawable/drag_file_counter_background" + android:textAppearance="@style/DragCounterText" + android:visibility="gone" /> +</FrameLayout> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/drop_badge.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/drop_badge.xml index e2f0d35cd..a4210d07c 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/layout/drop_badge.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/drop_badge.xml @@ -17,8 +17,8 @@ <com.android.documentsui.DropBadgeView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/icon" - android:layout_width="26dp" - android:layout_height="26dp" + android:layout_width="match_parent" + android:layout_height="match_parent" android:scaleType="centerInside" android:contentDescription="@null" android:duplicateParentState="true"/>
\ No newline at end of file diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values-night-v31/colors.xml b/res/flag(com.android.documentsui.flags.use_material3)/values-night-v31/colors.xml index 2105936ab..b75afa868 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values-night-v31/colors.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values-night-v31/colors.xml @@ -37,4 +37,24 @@ </color> <!-- neutral variant 100 --> <color name="fragment_pick_active_text_color">@android:color/black</color> + + <!-- All drag drop badge colors, we can only use system color tokens because there's no theme + context inside the view. + --> + <!-- ?attr/colorTertiary --> + <color name="drag_file_counter_background">@android:color/system_accent3_200</color> + <!-- ?attr/colorOnTertiary --> + <color name="drag_file_counter_text_color">@android:color/system_accent3_800</color> + <!-- ?attr/colorSurfaceDim --> + <color name="item_drag_shadow_background">@color/m3_ref_palette_dynamic_neutral_variant6</color> + <!-- ?attr/colorSurfaceContainerLowest --> + <color name="drag_mime_icon_wrapper_background"> + @color/m3_ref_palette_dynamic_neutral_variant4 + </color> + <!-- ?attr/colorOnSurface --> + <color name="drag_content_text_color">@android:color/system_neutral1_100</color> + <!-- ?attr/colorOnError --> + <color name="drop_icon_symbol_color">@color/m3_ref_palette_error20</color> + <!-- ?attr/colorError --> + <color name="drop_icon_reject_container_background">@color/m3_ref_palette_error80</color> </resources> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values-v31/colors.xml b/res/flag(com.android.documentsui.flags.use_material3)/values-v31/colors.xml index 3096f8a3e..b7e11e885 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values-v31/colors.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values-v31/colors.xml @@ -37,4 +37,22 @@ </color> <!-- accent 600 --> <color name="fragment_pick_active_text_color">@android:color/white</color> + + <!-- All drag drop badge colors, we can only use system color tokens because there's no theme + context inside the view. + --> + <!-- ?attr/colorTertiary --> + <color name="drag_file_counter_background">@android:color/system_accent3_600</color> + <!-- ?attr/colorOnTertiary --> + <color name="drag_file_counter_text_color">@android:color/system_accent3_0</color> + <!-- ?attr/colorSurfaceDim --> + <color name="item_drag_shadow_background">@color/m3_ref_palette_dynamic_neutral_variant87</color> + <!-- ?attr/colorSurfaceContainerLowest --> + <color name="drag_mime_icon_wrapper_background">@android:color/system_neutral2_0</color> + <!-- ?attr/colorOnSurface --> + <color name="drag_content_text_color">@android:color/system_neutral1_900</color> + <!-- ?attr/colorOnError --> + <color name="drop_icon_symbol_color">@android:color/white</color> + <!-- ?attr/colorError --> + <color name="drop_icon_reject_container_background">@color/m3_ref_palette_error40</color> </resources> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/colors.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/colors.xml index 77a41868f..84a8f720a 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/colors.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/colors.xml @@ -30,10 +30,24 @@ <color name="chip_background_disable_color">#fff1f3f4</color> <color name="menu_search_background">@android:color/transparent</color> <color name="item_breadcrumb_background_hovered">#1affffff</color> - <color name="item_drag_shadow_background">@android:color/white</color> + + <!-- All the colors used inside the drag drop badge don't support Material color attributes + because the drag view and shadow are isolated views rendered without theme context, so + we need to use static colors for SDK <= 30, and use system color tokens for SDK >= 31. + Check the variables with the same name defined in the v31 version of colors.xml. + --> + <color name="item_drag_shadow_background">#E3D7DD</color> <color name="item_drag_shadow_container_background"> @android:color/transparent </color> + <color name="drag_file_counter_background">#825344</color> + <color name="drag_file_counter_text_color">@android:color/white</color> + <color name="drag_mime_icon_wrapper_background">@android:color/white</color> + <color name="drag_content_text_color">#201A1E</color> + <color name="drop_icon_symbol_color">@android:color/white</color> + <color name="drop_icon_copy_container_background">#1EA446</color> + <color name="drop_icon_reject_container_background">#BA1A1A</color> + <color name="tool_bar_gradient_max">#7f000000</color> <color name="band_select_background">?attr/colorPrimaryInverse</color> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml index 76e194c4f..b912c5d6c 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml @@ -111,11 +111,37 @@ <dimen name="nav_rail_item_icon_bg_height">32dp</dimen> <dimen name="nav_rail_burger_icon_size">56dp</dimen> - <dimen name="drag_shadow_width">176dp</dimen> - <dimen name="drag_shadow_height">64dp</dimen> - <dimen name="drag_shadow_radius">4dp</dimen> + <dimen name="drag_content_width">160dp</dimen> + <dimen name="drag_content_height">40dp</dimen> + <!-- drag_additional_layer_offset + drag_shadow_radius --> + <dimen name="drag_content_margin_start">7dp</dimen> + <dimen name="drag_content_radius">12dp</dimen> + <dimen name="drag_additional_layer_offset">4dp</dimen> + <!-- drag_additional_layer_offset + drag_file_counter_offset --> + <dimen name="drag_additional_layer_margin_top">12dp</dimen> + <dimen name="drag_file_counter_offset">8dp</dimen> + <dimen name="drag_file_counter_height">20dp</dimen> + <!-- drag shadow width/height need to cater all the elements drawing inside it, including + drag content, offset, shadow and drag file counter, so: + * width = drag_content_width + drag_file_counter_offset + drag_additional_layer_offset + drag_shadow_radius + * width = drag_content_height + drag_file_counter_offset + drag_additional_layer_offset + drag_shadow_radius + --> + <dimen name="drag_shadow_width">175dp</dimen> + <dimen name="drag_shadow_height">55dp</dimen> + <dimen name="drag_shadow_radius">3dp</dimen> + <dimen name="drag_shadow_2_radius">2dp</dimen> + <dimen name="drag_shadow_y_offset">1dp</dimen> + <!-- TODO(b/379776735): remove this after use_material3 flag is launched. --> <dimen name="drag_shadow_padding">8dp</dimen> + <dimen name="drop_mime_icon_wrapper_size">24dp</dimen> + <dimen name="drop_mime_icon_wrapper_radius">4dp</dimen> + <dimen name="drop_icon_height">14dp</dimen> + <dimen name="drop_icon_width">14dp</dimen> + <dimen name="drop_icon_offset">3dp</dimen> + <!-- drag_mime_icon_wrapper_size + drop_icon_offset --> + <dimen name="drop_badge_container_size">27dp</dimen> + <dimen name="doc_header_sort_icon_size">32dp</dimen> <dimen name="doc_header_height">48dp</dimen> @@ -123,9 +149,6 @@ <dimen name="dropdown_sort_widget_size">54dp</dimen> <dimen name="dropdown_sort_text_size">18sp</dimen> - <dimen name="drop_icon_height">14dp</dimen> - <dimen name="drop_icon_width">14dp</dimen> - <dimen name="header_message_horizontal_padding">8dp</dimen> <dimen name="fastscroll_default_thickness">8dp</dimen> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml index 82b498569..37dd8a135 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml @@ -150,4 +150,14 @@ <item name="fontFamily">@string/config_fontFamily</item> </style> + <style name="DragBadgeText" parent="@style/TextAppearance.Material3.TitleSmall"> + <item name="android:textColor">@color/drag_content_text_color</item> + <item name="fontFamily">@string/config_fontFamily</item> + </style> + + <style name="DragCounterText" parent="@style/TextAppearance.Material3.LabelMedium"> + <item name="android:textColor">@color/drag_file_counter_text_color</item> + <item name="fontFamily">@string/config_fontFamily</item> + </style> + </resources> diff --git a/src/com/android/documentsui/DragAndDropManager.java b/src/com/android/documentsui/DragAndDropManager.java index bed8764ae..158d43c95 100644 --- a/src/com/android/documentsui/DragAndDropManager.java +++ b/src/com/android/documentsui/DragAndDropManager.java @@ -16,6 +16,8 @@ package com.android.documentsui; +import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled; + import android.content.ClipData; import android.content.Context; import android.graphics.drawable.Drawable; @@ -277,7 +279,9 @@ public interface DragAndDropManager { final Drawable icon; final int size = srcs.size(); - if (size == 1) { + // If use_material3 flag is ON, we always show the icon/title for the first file even + // when we have multiple files. + if (size == 1 || isUseMaterial3FlagEnabled()) { DocumentInfo doc = srcs.get(0); title = doc.displayName; icon = iconHelper.getDocumentIcon(mContext, doc); @@ -287,6 +291,10 @@ public interface DragAndDropManager { icon = mDefaultShadowIcon; } + if (isUseMaterial3FlagEnabled()) { + mShadowBuilder.updateDragFileCount(size); + } + mShadowBuilder.updateTitle(title); mShadowBuilder.updateIcon(icon); diff --git a/src/com/android/documentsui/DragShadowBuilder.java b/src/com/android/documentsui/DragShadowBuilder.java index 10a0106d5..be76302e8 100644 --- a/src/com/android/documentsui/DragShadowBuilder.java +++ b/src/com/android/documentsui/DragShadowBuilder.java @@ -16,7 +16,7 @@ package com.android.documentsui; -import com.android.documentsui.DragAndDropManager.State; +import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled; import android.content.Context; import android.graphics.Canvas; @@ -29,6 +29,12 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; +import androidx.annotation.Nullable; + +import com.android.documentsui.DragAndDropManager.State; + +import java.text.NumberFormat; + class DragShadowBuilder extends View.DragShadowBuilder { private final View mShadowView; @@ -37,8 +43,18 @@ class DragShadowBuilder extends View.DragShadowBuilder { private final int mWidth; private final int mHeight; private final int mShadowRadius; - private int mPadding; + private final int mPadding; private Paint paint; + // This will be null if use_material3 flag is OFF. + private final @Nullable View mAdditionalShadowView; + // This will always be 0 if the use_material3 flag is OFF. + private int mDragFileCount = 0; + // The following 5 dimensions will be 0 if the use_material3 flag is OFF. + private final int mDragContentRadius; + private final int mAdditionalLayerOffset; + private final int mDragFileCounterOffset; + private final int mShadow2Radius; + private final int mShadowYOffset; DragShadowBuilder(Context context) { mWidth = context.getResources().getDimensionPixelSize(R.dimen.drag_shadow_width); @@ -49,6 +65,28 @@ class DragShadowBuilder extends View.DragShadowBuilder { mShadowView = LayoutInflater.from(context).inflate(R.layout.drag_shadow_layout, null); mTitle = (TextView) mShadowView.findViewById(android.R.id.title); mIcon = (DropBadgeView) mShadowView.findViewById(android.R.id.icon); + if (isUseMaterial3FlagEnabled()) { + mAdditionalShadowView = + LayoutInflater.from(context).inflate(R.layout.additional_drag_shadow, null); + mDragContentRadius = + context.getResources().getDimensionPixelSize(R.dimen.drag_content_radius); + mAdditionalLayerOffset = + context.getResources() + .getDimensionPixelSize(R.dimen.drag_additional_layer_offset); + mDragFileCounterOffset = + context.getResources().getDimensionPixelSize(R.dimen.drag_file_counter_offset); + mShadow2Radius = + context.getResources().getDimensionPixelSize(R.dimen.drag_shadow_2_radius); + mShadowYOffset = + context.getResources().getDimensionPixelSize(R.dimen.drag_shadow_y_offset); + } else { + mAdditionalShadowView = null; + mDragContentRadius = 0; + mAdditionalLayerOffset = 0; + mDragFileCounterOffset = 0; + mShadow2Radius = 0; + mShadowYOffset = 0; + } // Important for certain APIs mShadowView.setLayerType(View.LAYER_TYPE_SOFTWARE, paint); @@ -67,23 +105,86 @@ class DragShadowBuilder extends View.DragShadowBuilder { Rect r = canvas.getClipBounds(); // Calling measure is necessary in order for all child views to get correctly laid out. mShadowView.measure( - View.MeasureSpec.makeMeasureSpec(r.right- r.left, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(r.right - r.left, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(r.bottom - r.top , View.MeasureSpec.EXACTLY)); mShadowView.layout(r.left, r.top, r.right, r.bottom); // Since DragShadow is not an actual view drawn in hardware-accelerated window, // android:elevation does not work; we need to draw the shadow ourselves manually. paint.setColor(Color.TRANSPARENT); - // Shadow 1 - int opacity = (int) (255 * 0.1); - paint.setShadowLayer(mShadowRadius, 0, 0, Color.argb(opacity, 0, 0, 0)); - canvas.drawRect(r.left + mPadding, r.top + mPadding, r.right - mPadding, - r.bottom - mPadding, paint); - // Shadow 2 - opacity = (int) (255 * 0.24); - paint.setShadowLayer(mShadowRadius, 0, mShadowRadius, Color.argb(opacity, 0, 0, 0)); - canvas.drawRect(r.left + mPadding, r.top + mPadding, r.right - mPadding, - r.bottom - mPadding, paint); + + // Layers on the canvas (from bottom to top): + // 1. Two shadows for the additional drag layer (if drag file count > 1) + // 2. The additional layer view itself (if drag file count > 1) + // 3. Two shadows for the drag content layer (icon, title) + // 4. The drag content layer itself + final int shadowOneOpacity = (int) (255 * 0.15); + final int shadowTwoOpacity = (int) (255 * 0.30); + if (mAdditionalShadowView != null && mDragFileCount > 1) { + mAdditionalShadowView.measure( + View.MeasureSpec.makeMeasureSpec(r.right - r.left, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(r.bottom - r.top , View.MeasureSpec.EXACTLY)); + mAdditionalShadowView.layout(r.left, r.top, r.right, r.bottom); + // Shadow 1 + paint.setShadowLayer( + mShadowRadius, 0, mShadowYOffset, Color.argb(shadowOneOpacity, 0, 0, 0)); + canvas.drawRoundRect( + r.left + mShadowRadius, + r.top + mDragFileCounterOffset + mAdditionalLayerOffset, + r.right - mDragFileCounterOffset - mAdditionalLayerOffset, + r.bottom - mShadowRadius, + mDragContentRadius, + mDragContentRadius, + paint); + // Shadow 2 + paint.setShadowLayer( + mShadow2Radius, 0, mShadowYOffset, Color.argb(shadowTwoOpacity, 0, 0, 0)); + canvas.drawRoundRect( + r.left + mShadowRadius, + r.top + mDragFileCounterOffset + mAdditionalLayerOffset, + r.right - mDragFileCounterOffset - mAdditionalLayerOffset, + r.bottom - mShadowRadius, + mDragContentRadius, + mDragContentRadius, + paint); + mAdditionalShadowView.draw(canvas); + } + + if (isUseMaterial3FlagEnabled()) { + // Shadow 1 + paint.setShadowLayer( + mShadowRadius, 0, mShadowYOffset, Color.argb(shadowOneOpacity, 0, 0, 0)); + canvas.drawRoundRect( + r.left + mShadowRadius + mAdditionalLayerOffset, + r.top + mDragFileCounterOffset, + r.right - mDragFileCounterOffset, + r.bottom - mShadowRadius - mAdditionalLayerOffset, + mDragContentRadius, + mDragContentRadius, + paint); + // Shadow 2 + paint.setShadowLayer( + mShadow2Radius, 0, mShadowYOffset, Color.argb(shadowTwoOpacity, 0, 0, 0)); + canvas.drawRoundRect( + r.left + mShadowRadius + mAdditionalLayerOffset, + r.top + mDragFileCounterOffset, + r.right - mDragFileCounterOffset, + r.bottom - mShadowRadius - mAdditionalLayerOffset, + mDragContentRadius, + mDragContentRadius, + paint); + } else { + // Shadow 1 + int opacity = (int) (255 * 0.1); + paint.setShadowLayer(mShadowRadius, 0, 0, Color.argb(opacity, 0, 0, 0)); + canvas.drawRect(r.left + mPadding, r.top + mPadding, r.right - mPadding, + r.bottom - mPadding, paint); + // Shadow 2 + opacity = (int) (255 * 0.24); + paint.setShadowLayer(mShadowRadius, 0, mShadowRadius, Color.argb(opacity, 0, 0, 0)); + canvas.drawRect(r.left + mPadding, r.top + mPadding, r.right - mPadding, + r.bottom - mPadding, paint); + } mShadowView.draw(canvas); } @@ -98,4 +199,19 @@ class DragShadowBuilder extends View.DragShadowBuilder { void onStateUpdated(@State int state) { mIcon.updateState(state); } + + void updateDragFileCount(int count) { + if (!isUseMaterial3FlagEnabled()) { + return; + } + mDragFileCount = count; + TextView dragFileCountView = mShadowView.findViewById(R.id.drag_file_counter); + if (dragFileCountView != null) { + dragFileCountView.setVisibility(count > 1 ? View.VISIBLE : View.GONE); + if (count > 1) { + NumberFormat numberFormat = NumberFormat.getInstance(); + dragFileCountView.setText(numberFormat.format(count)); + } + } + } } diff --git a/tests/unit/com/android/documentsui/DragAndDropManagerTests.java b/tests/unit/com/android/documentsui/DragAndDropManagerTests.java index d7a701f75..27b4074fd 100644 --- a/tests/unit/com/android/documentsui/DragAndDropManagerTests.java +++ b/tests/unit/com/android/documentsui/DragAndDropManagerTests.java @@ -26,17 +26,22 @@ import android.content.ClipData; import android.content.ClipDescription; import android.graphics.drawable.Drawable; import android.os.PersistableBundle; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.Pair; import android.view.KeyEvent; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.documentsui.DragAndDropManager.RuntimeDragAndDropManager; import com.android.documentsui.DragAndDropManager.State; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.RootInfo; +import com.android.documentsui.flags.Flags; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperations; import com.android.documentsui.testing.ClipDatas; @@ -52,6 +57,7 @@ import com.android.documentsui.testing.TestSelectionDetails; import com.android.documentsui.testing.Views; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -93,6 +99,9 @@ public class DragAndDropManagerTests { private DragAndDropManager mManager; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { mEnv = TestEnv.create(); @@ -191,7 +200,8 @@ public class DragAndDropManagerTests { } @Test - public void testStartDrag_BuildsCorrectShadow_MultipleDocs() { + @RequiresFlagsDisabled({Flags.FLAG_USE_MATERIAL3}) + public void testStartDrag_BuildsCorrectShadow_MultipleDocs_M3Disabled() { mManager.startDrag( mStartDragView, Arrays.asList(TestEnv.FILE_APK, TestEnv.FILE_JPG), @@ -208,6 +218,24 @@ public class DragAndDropManagerTests { } @Test + @RequiresFlagsEnabled({Flags.FLAG_USE_MATERIAL3}) + public void testStartDrag_BuildsCorrectShadow_MultipleDocs() { + mManager.startDrag( + mStartDragView, + Arrays.asList(TestEnv.FILE_APK, TestEnv.FILE_JPG), + TestProvidersAccess.HOME, + Arrays.asList(TestEnv.FOLDER_0.derivedUri, TestEnv.FILE_APK.derivedUri, + TestEnv.FILE_JPG.derivedUri), + mDetails, + mIconHelper, + TestEnv.FOLDER_0); + + mShadowBuilder.title.assertLastArgument(TestEnv.FILE_APK.displayName); + mShadowBuilder.icon.assertLastArgument(mIconHelper.nextDocumentIcon); + mShadowBuilder.count.assertLastArgument(2); + } + + @Test public void testCanSpringOpen_ReturnsFalse_RootNotSupportCreate() { mManager.startDrag( mStartDragView, @@ -860,6 +888,7 @@ public class DragAndDropManagerTests { public TestEventListener<String> title; public TestEventListener<Drawable> icon; public TestEventListener<Integer> state; + public TestEventListener<Integer> count; private TestDragShadowBuilder() { super(null); @@ -880,6 +909,11 @@ public class DragAndDropManagerTests { this.state.accept(state); } + @Override + void updateDragFileCount(int count) { + this.count.accept(count); + } + public static TestDragShadowBuilder create() { TestDragShadowBuilder builder = Mockito.mock(TestDragShadowBuilder.class, Mockito.CALLS_REAL_METHODS); @@ -887,6 +921,7 @@ public class DragAndDropManagerTests { builder.title = new TestEventListener<>(); builder.icon = new TestEventListener<>(); builder.state = new TestEventListener<>(); + builder.count = new TestEventListener<>(); return builder; } |