Fixing header jump
Linking header position to an empty entry in the recyclerView,
instead of calculating the vertical scroll position. This
allows the header to be in sync with the recyclerView scroll and
item animations
Other simplifications:
> Moving top collapse handle out of header view (it doesn't scroll)
> Removing background clipping logic from full-sheet
> Moving tab bar inside the header view
Bug: 196464142
Test: Verified on device
Change-Id: Iae5a0ae9af7ce258e1b391b8e85c5c270fe56197
diff --git a/res/drawable-v28/widgets_bottom_sheet_background.xml b/res/drawable-v28/widgets_bottom_sheet_background.xml
deleted file mode 100644
index 7fb8681..0000000
--- a/res/drawable-v28/widgets_bottom_sheet_background.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 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/surface" />
- <corners
- android:topLeftRadius="?android:attr/dialogCornerRadius"
- android:topRightRadius="?android:attr/dialogCornerRadius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"
- />
-</shape>
\ No newline at end of file
diff --git a/res/drawable/widgets_bottom_sheet_background.xml b/res/drawable/bg_widgets_full_sheet.xml
similarity index 65%
rename from res/drawable/widgets_bottom_sheet_background.xml
rename to res/drawable/bg_widgets_full_sheet.xml
index b877546..dfcd354 100644
--- a/res/drawable/widgets_bottom_sheet_background.xml
+++ b/res/drawable/bg_widgets_full_sheet.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
+<!-- Copyright (C) 2021 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.
@@ -14,13 +13,11 @@
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/surface" />
+ android:shape="rectangle" >
+ <solid android:color="?android:attr/colorBackground" />
<corners
- android:topLeftRadius="@dimen/default_dialog_corner_radius"
- android:topRightRadius="@dimen/default_dialog_corner_radius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"
- />
+ android:topLeftRadius="@dimen/dialogCornerRadius"
+ android:topRightRadius="@dimen/dialogCornerRadius" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_picker_handle.xml b/res/drawable/bg_widgets_picker_handle.xml
deleted file mode 100644
index 68681a6..0000000
--- a/res/drawable/bg_widgets_picker_handle.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 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>
- <shape android:shape="rectangle">
- <solid android:color="?android:attr/colorBackground" />
- <padding android:top="16dp"/>
- </shape>
- </item>
- <item android:gravity="center">
- <shape android:shape="rectangle">
- <solid android:color="?android:attr/textColorSecondary" />
- <size android:width="48dp" android:height="2dp" />
- </shape>
- </item>
-</layer-list>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
index 3d330dc..1a2cfc6 100644
--- a/res/layout/widgets_bottom_sheet_content.xml
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -18,7 +18,7 @@
android:id="@+id/widgets_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/widgets_bottom_sheet_background"
+ android:background="@drawable/bg_rounded_corner_bottom_sheet"
android:paddingTop="16dp"
android:orientation="vertical">
<View
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 96b73c2..8afd40b 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -19,13 +19,21 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:theme="?attr/widgetsTheme" >
+ android:theme="?attr/widgetsTheme">
- <com.android.launcher3.views.TopRoundedCornerView
+ <com.android.launcher3.views.SpringRelativeLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?android:attr/colorBackground">
+ android:background="@drawable/bg_widgets_full_sheet">
+
+ <View
+ android:id="@+id/collapse_handle"
+ android:layout_width="48dp"
+ android:layout_height="2dp"
+ android:layout_marginTop="16dp"
+ android:layout_centerHorizontal="true"
+ android:background="?android:attr/textColorSecondary"/>
<TextView
android:id="@+id/no_widgets_text"
@@ -35,6 +43,7 @@
android:visibility="gone"
android:fontFamily="sans-serif-medium"
android:textSize="20sp"
+ android:layout_below="@id/search_and_recommendations_container"
tools:text="No widgets available" />
<!-- Fast scroller popup -->
@@ -57,9 +66,10 @@
android:id="@+id/search_widgets_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_below="@id/collapse_handle"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:visibility="gone"
android:clipToPadding="false" />
- </com.android.launcher3.views.TopRoundedCornerView>
+ </com.android.launcher3.views.SpringRelativeLayout>
</com.android.launcher3.widget.picker.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index fefad19..85f14cd 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -22,7 +22,7 @@
android:layout_height="match_parent"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToPadding="false"
- android:paddingTop="@dimen/widget_picker_view_pager_top_padding"
+ android:layout_below="@id/collapse_handle"
android:descendantFocusability="afterDescendants"
launcher:pageIndicator="@+id/tabs">
@@ -40,5 +40,84 @@
</com.android.launcher3.workprofile.PersonalWorkPagedView>
- <include layout="@layout/widgets_personal_work_tabs"/>
+ <!-- SearchAndRecommendationsView contains the tab layout as well -->
+ <com.android.launcher3.widget.picker.SearchAndRecommendationsView
+ android:id="@+id/search_and_recommendations_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:layout_below="@id/collapse_handle"
+ android:paddingBottom="0dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textSize="24sp"
+ android:layout_marginTop="24dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/widget_button_text"/>
+
+ <FrameLayout
+ android:id="@+id/search_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="0.1dp"
+ android:background="?android:attr/colorBackground"
+ android:paddingBottom="8dp"
+ android:clipToPadding="false">
+ <include layout="@layout/widgets_search_bar" />
+ </FrameLayout>
+
+ <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+ android:id="@+id/recommended_widget_table"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:background="@drawable/widgets_recommendation_background"
+ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+ android:visibility="gone" />
+
+ <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal"
+ android:paddingVertical="8dp"
+ android:paddingLeft="@dimen/widget_tabs_horizontal_padding"
+ android:paddingRight="@dimen/widget_tabs_horizontal_padding"
+ android:background="?android:attr/colorBackground"
+ style="@style/TextHeadline">
+
+ <Button
+ android:id="@+id/tab_personal"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
+ android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
+ android:layout_weight="1"
+ android:background="@drawable/all_apps_tabs_background"
+ android:text="@string/widgets_full_sheet_personal_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp"
+ style="?android:attr/borderlessButtonStyle" />
+
+ <Button
+ android:id="@+id/tab_work"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
+ android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
+ android:layout_weight="1"
+ android:background="@drawable/all_apps_tabs_background"
+ android:text="@string/widgets_full_sheet_work_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp"
+ style="?android:attr/borderlessButtonStyle" />
+ </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
+
+ </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
index 0f7e020..dde82ea 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -13,10 +13,54 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.widget.picker.WidgetsRecyclerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/primary_widgets_list_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
- android:clipToPadding="false" />
\ No newline at end of file
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.android.launcher3.widget.picker.WidgetsRecyclerView
+ android:id="@+id/primary_widgets_list_view"
+ android:layout_below="@id/collapse_handle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:clipToPadding="false" />
+
+ <!-- SearchAndRecommendationsView without the tab layout as well -->
+ <com.android.launcher3.widget.picker.SearchAndRecommendationsView
+ android:id="@+id/search_and_recommendations_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:layout_below="@id/collapse_handle"
+ android:paddingBottom="16dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textSize="24sp"
+ android:layout_marginTop="24dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/widget_button_text"/>
+
+ <FrameLayout
+ android:id="@+id/search_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="0.1dp"
+ android:background="?android:attr/colorBackground"
+ android:paddingBottom="8dp"
+ android:clipToPadding="false">
+ <include layout="@layout/widgets_search_bar" />
+ </FrameLayout>
+
+ <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+ android:id="@+id/recommended_widget_table"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:background="@drawable/widgets_recommendation_background"
+ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+ android:visibility="gone" />
+ </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
+
+</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_search_and_recommendations.xml b/res/layout/widgets_full_sheet_search_and_recommendations.xml
deleted file mode 100644
index 938c8ed..0000000
--- a/res/layout/widgets_full_sheet_search_and_recommendations.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 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.
--->
-<com.android.launcher3.widget.picker.SearchAndRecommendationsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/search_and_recommendations_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
- android:layout_marginBottom="16dp"
- android:orientation="vertical">
-
- <View
- android:id="@+id/collapse_handle"
- android:layout_width="match_parent"
- android:layout_height="18dp"
- android:elevation="0.1dp"
- android:background="@drawable/bg_widgets_picker_handle"/>
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textSize="24sp"
- android:layout_marginTop="24dp"
- android:textColor="?android:attr/textColorSecondary"
- android:text="@string/widget_button_text"/>
-
- <FrameLayout
- android:id="@+id/search_bar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:elevation="0.1dp"
- android:background="?android:attr/colorBackground"
- android:paddingBottom="8dp"
- android:clipToPadding="false">
- <include layout="@layout/widgets_search_bar" />
- </FrameLayout>
-
- <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
- android:id="@+id/recommended_widget_table"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:background="@drawable/widgets_recommendation_background"
- android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
- android:visibility="gone" />
-</com.android.launcher3.widget.picker.SearchAndRecommendationsView>
diff --git a/res/layout/widgets_personal_work_tabs.xml b/res/layout/widgets_personal_work_tabs.xml
deleted file mode 100644
index 532c422..0000000
--- a/res/layout/widgets_personal_work_tabs.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 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.
--->
-
-<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tabs"
- android:layout_width="match_parent"
- android:layout_height="@dimen/all_apps_header_pill_height"
- android:gravity="center_horizontal"
- android:orientation="horizontal"
- android:layout_marginHorizontal="@dimen/widget_tabs_horizontal_margin"
- style="@style/TextHeadline">
-
- <Button
- android:id="@+id/tab_personal"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
- android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
- android:layout_weight="1"
- android:background="@drawable/all_apps_tabs_background"
- android:text="@string/widgets_full_sheet_personal_tab"
- android:textColor="@color/all_apps_tab_text"
- android:textSize="14sp"
- style="?android:attr/borderlessButtonStyle" />
-
- <Button
- android:id="@+id/tab_work"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
- android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
- android:layout_weight="1"
- android:background="@drawable/all_apps_tabs_background"
- android:text="@string/widgets_full_sheet_work_tab"
- android:textColor="@color/all_apps_tab_text"
- android:textSize="14sp"
- style="?android:attr/borderlessButtonStyle" />
-</com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index da37250..8457bd8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -143,8 +143,7 @@
<dimen name="widget_cell_font_size">14sp</dimen>
<dimen name="widget_tabs_button_horizontal_padding">4dp</dimen>
- <dimen name="widget_tabs_horizontal_margin">32dp</dimen>
- <dimen name="widget_apps_header_pill_height">48dp</dimen>
+ <dimen name="widget_tabs_horizontal_padding">16dp</dimen>
<dimen name="widget_apps_tabs_vertical_padding">6dp</dimen>
<dimen name="recommended_widgets_table_vertical_padding">8dp</dimen>
@@ -178,8 +177,6 @@
<dimen name="widget_picker_education_tip_max_width">308dp</dimen>
<dimen name="widget_picker_education_tip_min_margin">4dp</dimen>
- <dimen name="widget_picker_view_pager_top_padding">10dp</dimen>
-
<!-- Padding applied to shortcut previews -->
<dimen name="shortcut_preview_padding_left">0dp</dimen>
<dimen name="shortcut_preview_padding_right">0dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index 0e2dff0..ebc4075 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -17,6 +17,7 @@
<resources>
<item type="id" name="apps_list_view_work" />
<item type="id" name="tag_widget_entry" />
+ <item type="id" name="view_type_widgets_space" />
<item type="id" name="view_type_widgets_list" />
<item type="id" name="view_type_widgets_header" />
<item type="id" name="view_type_widgets_search_header" />
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index c730fc0..fb44ca1 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -82,7 +82,7 @@
mTestProfile.numColumns = 5;
mUserHandle = Process.myUserHandle();
mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
- mIconCache, null, null);
+ mIconCache, () -> 0, null, null);
mAdapter.registerAdapterDataObserver(mListener);
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 81b0c5f..b7d7788 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -41,7 +41,6 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@@ -79,8 +78,6 @@
@Mock
private DeviceProfile mDeviceProfile;
@Mock
- private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- @Mock
private OnHeaderClickListener mOnHeaderClickListener;
@Before
@@ -99,18 +96,10 @@
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
-
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
LayoutInflater.from(mTestActivity),
mOnHeaderClickListener,
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
+ new WidgetsListDrawableFactory(mTestActivity));
}
@After
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index a0ba7c3..2b4cea0 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -41,7 +41,6 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
@@ -79,8 +78,6 @@
@Mock
private DeviceProfile mDeviceProfile;
@Mock
- private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- @Mock
private OnHeaderClickListener mOnHeaderClickListener;
@Before
@@ -99,18 +96,10 @@
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
-
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
LayoutInflater.from(mTestActivity),
mOnHeaderClickListener,
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
+ new WidgetsListDrawableFactory(mTestActivity));
}
@After
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 8f9d132..9f66fb7 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -107,19 +107,12 @@
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListTableViewHolderBinder(
LayoutInflater.from(mTestActivity),
mOnIconClickListener,
mOnLongClickListener,
new CachingWidgetPreviewLoader(mWidgetPreviewLoader),
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
+ new WidgetsListDrawableFactory(mTestActivity));
}
@After
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
index 5b8d5bc..6215827 100644
--- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -17,8 +17,12 @@
import android.view.ViewGroup;
+import androidx.annotation.IntDef;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Creates and populates views with data
*
@@ -26,6 +30,15 @@
* @param <V> A subclass of {@link ViewHolder} which holds references to views.
*/
public interface ViewHolderBinder<T, V extends ViewHolder> {
+
+ int POSITION_DEFAULT = 0;
+ int POSITION_FIRST = 1 << 0;
+ int POSITION_LAST = 1 << 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {POSITION_DEFAULT, POSITION_FIRST, POSITION_LAST}, flag = true)
+ @interface ListPosition {}
+
/**
* Creates a new view, and attach it to the parent {@link ViewGroup}. Then, populates UI
* references in a {@link ViewHolder}.
@@ -33,7 +46,7 @@
V newViewHolder(ViewGroup parent);
/** Populate UI references in {@link ViewHolder} with data. */
- void bindViewHolder(V viewHolder, T data, int position);
+ void bindViewHolder(V viewHolder, T data, @ListPosition int position);
/**
* Called when the view is recycled. Views are recycled in batches once they are sufficiently
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 92ca8a1..8ac40b8 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -28,6 +28,7 @@
import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
@@ -68,7 +69,7 @@
protected final SingleAxisSwipeDetector mSwipeDetector;
protected final ObjectAnimator mOpenCloseAnimator;
- protected View mContent;
+ protected ViewGroup mContent;
protected final View mColorScrim;
protected Interpolator mScrollInterpolator;
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 1c2534d..2b0f707 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -40,7 +40,6 @@
import android.view.WindowInsets;
import android.widget.TextView;
-import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.recyclerview.widget.RecyclerView;
@@ -131,7 +130,6 @@
protected BaseRecyclerView mRv;
private RecyclerView.OnScrollListener mOnScrollListener;
- @Nullable private OnFastScrollChangeListener mOnFastScrollChangeListener;
private int mDownX;
private int mDownY;
@@ -208,7 +206,6 @@
int rvCurrentOffsetY = mRv.getCurrentScrollY();
if (mRvOffsetY != rvCurrentOffsetY) {
mRvOffsetY = mRv.getCurrentScrollY();
- notifyScrollChanged();
}
return;
}
@@ -216,7 +213,6 @@
mThumbOffsetY = y;
invalidate();
mRvOffsetY = mRv.getCurrentScrollY();
- notifyScrollChanged();
}
public int getThumbOffsetY() {
@@ -461,23 +457,4 @@
public void setIsRecyclerViewFirstChildInParent(boolean isRecyclerViewFirstChildInParent) {
mIsRecyclerViewFirstChildInParent = isRecyclerViewFirstChildInParent;
}
-
- public void setOnFastScrollChangeListener(
- @Nullable OnFastScrollChangeListener onFastScrollChangeListener) {
- mOnFastScrollChangeListener = onFastScrollChangeListener;
- }
-
- private void notifyScrollChanged() {
- if (mOnFastScrollChangeListener != null) {
- mOnFastScrollChangeListener.onScrollChanged();
- }
- }
-
- /**
- * A callback that is invoked when there is a scroll change in {@link RecyclerViewFastScroller}.
- */
- public interface OnFastScrollChangeListener {
- /** Called when the recycler view scroll has changed. */
- void onScrollChanged();
- }
}
diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java
deleted file mode 100644
index 92cce92..0000000
--- a/src/com/android/launcher3/views/TopRoundedCornerView.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2018 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 com.android.launcher3.views;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-
-import com.android.launcher3.util.Themes;
-
-/**
- * View with top rounded corners.
- */
-public class TopRoundedCornerView extends SpringRelativeLayout {
-
- private final RectF mRect = new RectF();
- private final Path mClipPath = new Path();
- private float[] mRadii;
-
- public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- float radius = Themes.getDialogCornerRadius(context);
- mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0};
- }
-
- public TopRoundedCornerView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.save();
- canvas.clipPath(mClipPath);
- super.draw(canvas);
- canvas.restore();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- mClipPath.reset();
- mClipPath.addRoundRect(mRect, mRadii, Path.Direction.CW);
- }
-}
diff --git a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
new file mode 100644
index 0000000..e62425f
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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 com.android.launcher3.widget.model;
+
+import com.android.launcher3.model.data.PackageItemInfo;
+
+import java.util.Collections;
+
+/**
+ * Entry representing the top empty space
+ */
+public class WidgetListSpaceEntry extends WidgetsListBaseEntry {
+
+ public WidgetListSpaceEntry() {
+ super(new PackageItemInfo(""), "", Collections.EMPTY_LIST);
+ mPkgItem.title = "";
+ }
+
+ @Override
+ public int getRank() {
+ return RANK_WIDGETS_TOP_SPACE;
+ }
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index abc79ff..1d1c9dc 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -73,11 +73,13 @@
}
@Retention(SOURCE)
- @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER, RANK_WIDGETS_LIST_CONTENT})
+ @IntDef({RANK_WIDGETS_TOP_SPACE, RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER,
+ RANK_WIDGETS_LIST_CONTENT})
public @interface Rank {
}
- public static final int RANK_WIDGETS_LIST_HEADER = 1;
- public static final int RANK_WIDGETS_LIST_SEARCH_HEADER = 2;
- public static final int RANK_WIDGETS_LIST_CONTENT = 3;
+ public static final int RANK_WIDGETS_TOP_SPACE = 1;
+ public static final int RANK_WIDGETS_LIST_HEADER = 2;
+ public static final int RANK_WIDGETS_LIST_SEARCH_HEADER = 3;
+ public static final int RANK_WIDGETS_LIST_CONTENT = 4;
}
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index 6643779..716dcf3 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -15,297 +15,148 @@
*/
package com.android.launcher3.widget.picker;
-import android.animation.ValueAnimator;
-import android.graphics.Point;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.widget.RelativeLayout;
+import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
-import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.widget.picker.WidgetsFullSheet.SearchAndRecommendationViewHolder;
-import com.android.launcher3.workprofile.PersonalWorkPagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
/**
* A controller which measures & updates {@link WidgetsFullSheet}'s views padding, margin and
* vertical displacement upon scrolling.
*/
final class SearchAndRecommendationsScrollController implements
- RecyclerViewFastScroller.OnFastScrollChangeListener, ValueAnimator.AnimatorUpdateListener {
- private final boolean mHasWorkProfile;
- private final SearchAndRecommendationViewHolder mViewHolder;
- private final View mSearchAndRecommendationViewParent;
- private final WidgetsRecyclerView mPrimaryRecyclerView;
- private final WidgetsRecyclerView mSearchRecyclerView;
- private final TextView mNoWidgetsView;
- private final int mTabsHeight;
- private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
- private final Point mTempOffset = new Point();
- private int mBottomInset;
+ RecyclerView.OnChildAttachStateChangeListener {
- // The following are only non null if mHasWorkProfile is true.
- @Nullable private final WidgetsRecyclerView mWorkRecyclerView;
- @Nullable private final View mPrimaryWorkTabsView;
- @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
+ private static final FloatProperty<SearchAndRecommendationsScrollController> SCROLL_OFFSET =
+ new FloatProperty<SearchAndRecommendationsScrollController>("scrollAnimOffset") {
+ @Override
+ public void setValue(SearchAndRecommendationsScrollController controller, float offset) {
+ controller.mScrollOffset = offset;
+ controller.updateHeaderScroll();
+ }
+
+ @Override
+ public Float get(SearchAndRecommendationsScrollController controller) {
+ return controller.mScrollOffset;
+ }
+ };
+
+ private static final MotionEventProxyMethod INTERCEPT_PROXY = ViewGroup::onInterceptTouchEvent;
+ private static final MotionEventProxyMethod TOUCH_PROXY = ViewGroup::onTouchEvent;
+
+ final SearchAndRecommendationsView mContainer;
+ final View mSearchBarContainer;
+ final WidgetsSearchBar mSearchBar;
+ final TextView mHeaderTitle;
+ final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
+ @Nullable final View mTabBar;
private WidgetsRecyclerView mCurrentRecyclerView;
- private int mCurrentRecyclerViewScrollY = 0;
+ private EmptySpaceView mCurrentEmptySpaceView;
- private OnContentChangeListener mOnContentChangeListener = () -> onScrollChanged();
-
- /**
- * The vertical distance, in pixels, until the search is pinned at the top of the screen when
- * the user scrolls down the recycler view.
- */
- private int mCollapsibleHeightForSearch = 0;
- /**
- * The vertical distance, in pixels, until the recommendation table disappears from the top of
- * the screen when the user scrolls down the recycler view.
- */
- private int mCollapsibleHeightForRecommendation = 0;
- /**
- * The vertical distance, in pixels, until the tabs is pinned at the top of the screen when the
- * user scrolls down the recycler view.
- *
- * <p>Always 0 if there is no work profile.
- */
- private int mCollapsibleHeightForTabs = 0;
+ private float mLastScroll = 0;
+ private float mScrollOffset = 0;
+ private Animator mOffsetAnimator;
private boolean mShouldForwardToRecyclerView = false;
+ private int mHeaderHeight;
+
SearchAndRecommendationsScrollController(
- boolean hasWorkProfile,
- int tabsHeight,
- SearchAndRecommendationViewHolder viewHolder,
- WidgetsRecyclerView primaryRecyclerView,
- @Nullable WidgetsRecyclerView workRecyclerView,
- WidgetsRecyclerView searchRecyclerView,
- @Nullable View personalWorkTabsView,
- @Nullable PersonalWorkPagedView primaryWorkViewPager,
- TextView noWidgetsView) {
- mHasWorkProfile = hasWorkProfile;
- mViewHolder = viewHolder;
- mViewHolder.mContainer.setSearchAndRecommendationScrollController(this);
- mSearchAndRecommendationViewParent = (View) mViewHolder.mContainer.getParent();
- mPrimaryRecyclerView = primaryRecyclerView;
- mWorkRecyclerView = workRecyclerView;
- mSearchRecyclerView = searchRecyclerView;
- mPrimaryWorkTabsView = personalWorkTabsView;
- mPrimaryWorkViewPager = primaryWorkViewPager;
- mTabsHeight = tabsHeight;
- mNoWidgetsView = noWidgetsView;
- setCurrentRecyclerView(mPrimaryRecyclerView, /* animateReset= */ false);
+ SearchAndRecommendationsView searchAndRecommendationContainer) {
+ mContainer = searchAndRecommendationContainer;
+ mSearchBarContainer = mContainer.findViewById(R.id.search_bar_container);
+ mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
+ mHeaderTitle = mContainer.findViewById(R.id.title);
+ mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
+ mTabBar = mContainer.findViewById(R.id.tabs);
+
+ mContainer.setSearchAndRecommendationScrollController(this);
}
public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
- setCurrentRecyclerView(currentRecyclerView, /* animateReset= */ true);
- }
-
- /** Sets the current active {@link WidgetsRecyclerView}. */
- private void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView,
- boolean animateReset) {
- if (mCurrentRecyclerView == currentRecyclerView) {
- return;
- }
+ boolean animateReset = mCurrentRecyclerView != null;
if (mCurrentRecyclerView != null) {
- mCurrentRecyclerView.setOnContentChangeListener(null);
+ mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this);
}
mCurrentRecyclerView = currentRecyclerView;
- mCurrentRecyclerView.setOnContentChangeListener(mOnContentChangeListener);
+ mCurrentRecyclerView.addOnChildAttachStateChangeListener(this);
+ findCurrentEmptyView();
reset(animateReset);
}
- /**
- * Updates padding of {@link WidgetsFullSheet} contents to include {@code bottomInset} wherever
- * necessary.
- */
- public boolean updateBottomInset(int bottomInset) {
- mBottomInset = bottomInset;
- return updateMarginAndPadding();
+ public int getHeaderHeight() {
+ return mHeaderHeight;
+ }
+
+ private void updateHeaderScroll() {
+ mLastScroll = getCurrentScroll();
+ mHeaderTitle.setTranslationY(mLastScroll);
+ mRecommendedWidgetsTable.setTranslationY(mLastScroll);
+
+ float searchYDisplacement = Math.max(mLastScroll, -mSearchBarContainer.getTop());
+ mSearchBarContainer.setTranslationY(searchYDisplacement);
+
+ if (mTabBar != null) {
+ float tabsDisplacement = Math.max(mLastScroll, -mTabBar.getTop()
+ + mSearchBarContainer.getHeight());
+ mTabBar.setTranslationY(tabsDisplacement);
+ }
+ }
+
+ private float getCurrentScroll() {
+ return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY());
}
/**
- * Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
+ * Updates the scrollable header height
*
- * @return {@code true} if margins or/and padding of views in the search and recommendations
- * container have been updated.
+ * @return {@code true} if the header height or dependent property changed.
*/
- public boolean updateMarginAndPadding() {
- boolean hasMarginOrPaddingUpdated = false;
- mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
- mCollapsibleHeightForRecommendation =
- measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
- + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
- + measureHeightWithVerticalMargins((View) mViewHolder.mSearchBarContainer)
- + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
+ public boolean updateHeaderHeight() {
+ boolean hasSizeUpdated = false;
- int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
- int noWidgetsViewHeight = topContainerHeight - mBottomInset;
-
- if (mHasWorkProfile) {
- mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
- + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
- // In a work profile setup, the full widget sheet contains the following views:
- // ------- (pinned) -|
- // Widgets (collapsible) -|---> LinearLayout for search & recommendations
- // Search bar (pinned) -|
- // Widgets recommendation (collapsible)-|
- // Personal | Work (pinned)
- // View Pager
- //
- // Views after the search & recommendations are not bound by RelativelyLayout param.
- // To position them on the expected location, padding & margin are added to these views
-
- // Tabs should have a padding of the height of the search & recommendations container.
- RelativeLayout.LayoutParams tabsLayoutParams =
- (RelativeLayout.LayoutParams) mPrimaryWorkTabsView.getLayoutParams();
- tabsLayoutParams.topMargin = topContainerHeight;
- mPrimaryWorkTabsView.setLayoutParams(tabsLayoutParams);
-
- // Instead of setting the top offset directly, we split the top offset into two values:
- // 1. topOffsetAfterAllViewsCollapsed: this is the top offset after all collapsible
- // views are no longer visible on the screen.
- // This value is set as the margin for the view pager.
- // 2. mMaxCollapsibleDistance
- // This value is set as the padding for the recycler views in order to work with
- // clipToPadding="false", which is an attribute for not showing top / bottom padding
- // when a recycler view has not reached the top or bottom of the list.
- // e.g. a list of 10 entries, only 3 entries are visible at a time.
- // case 1: recycler view is scrolled to the top. Top padding is visible/
- // (top padding)
- // item 1
- // item 2
- // item 3
- //
- // case 2: recycler view is scrolled to the middle. No padding is visible.
- // item 4
- // item 5
- // item 6
- //
- // case 3: recycler view is scrolled to the end. bottom padding is visible.
- // item 8
- // item 9
- // item 10
- // (bottom padding): not set in this case.
- //
- // When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
- // mMaxCollapsibleDistance should equal to the top container height.
- int topOffsetAfterAllViewsCollapsed =
- topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
-
- if (mPrimaryWorkTabsView.getVisibility() == View.VISIBLE) {
- noWidgetsViewHeight += mTabsHeight;
- }
-
- RelativeLayout.LayoutParams viewPagerLayoutParams =
- (RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
- if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
- viewPagerLayoutParams.topMargin = topOffsetAfterAllViewsCollapsed;
- mPrimaryWorkViewPager.setLayoutParams(viewPagerLayoutParams);
- hasMarginOrPaddingUpdated = true;
- }
-
- if (mPrimaryRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
- mPrimaryRecyclerView.setPadding(
- mPrimaryRecyclerView.getPaddingLeft(),
- mCollapsibleHeightForTabs,
- mPrimaryRecyclerView.getPaddingRight(),
- mPrimaryRecyclerView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- if (mWorkRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
- mWorkRecyclerView.setPadding(
- mWorkRecyclerView.getPaddingLeft(),
- mCollapsibleHeightForTabs,
- mWorkRecyclerView.getPaddingRight(),
- mWorkRecyclerView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- } else {
- if (mPrimaryRecyclerView.getPaddingTop() != topContainerHeight) {
- mPrimaryRecyclerView.setPadding(
- mPrimaryRecyclerView.getPaddingLeft(),
- topContainerHeight,
- mPrimaryRecyclerView.getPaddingRight(),
- mPrimaryRecyclerView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- }
- if (mSearchRecyclerView.getPaddingTop() != topContainerHeight) {
- mSearchRecyclerView.setPadding(
- mSearchRecyclerView.getPaddingLeft(),
- topContainerHeight,
- mSearchRecyclerView.getPaddingRight(),
- mSearchRecyclerView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- if (mNoWidgetsView.getPaddingTop() != noWidgetsViewHeight) {
- mNoWidgetsView.setPadding(
- mNoWidgetsView.getPaddingLeft(),
- noWidgetsViewHeight,
- mNoWidgetsView.getPaddingRight(),
- mNoWidgetsView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- return hasMarginOrPaddingUpdated;
- }
-
- @Override
- public void onScrollChanged() {
- int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
- if (recyclerViewYOffset < 0) return;
- mCurrentRecyclerViewScrollY = recyclerViewYOffset;
- if (mAnimator.isStarted()) {
- mAnimator.cancel();
- }
- applyVerticalTransition();
- }
-
- /**
- * Changes the displacement of collapsible views (e.g. title & widget recommendations) and fixed
- * views (e.g. recycler views, tabs) upon scrolling / content changes in the recycler view.
- */
- private void applyVerticalTransition() {
- if (mCollapsibleHeightForRecommendation > 0) {
- int yDisplacement = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForRecommendation);
- mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
- mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
+ int headerHeight = mContainer.getMeasuredHeight();
+ if (headerHeight != mHeaderHeight) {
+ mHeaderHeight = headerHeight;
+ hasSizeUpdated = true;
}
- if (mCollapsibleHeightForSearch > 0) {
- int searchYDisplacement = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForSearch);
- mViewHolder.mSearchBarContainer.setTranslationY(searchYDisplacement);
+ if (mCurrentEmptySpaceView != null
+ && mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) {
+ hasSizeUpdated = true;
}
-
- if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
- int yDisplacementForTabs = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForTabs);
- mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
- }
+ return hasSizeUpdated;
}
/** Resets any previous view translation. */
public void reset(boolean animate) {
- if (mCurrentRecyclerViewScrollY == 0) {
- return;
- }
- if (mAnimator.isStarted()) {
- mAnimator.cancel();
+ if (mOffsetAnimator != null) {
+ mOffsetAnimator.cancel();
+ mOffsetAnimator = null;
}
- if (animate) {
- mAnimator.setIntValues(mCurrentRecyclerViewScrollY, 0);
- mAnimator.addUpdateListener(this);
- mAnimator.setDuration(300);
- mAnimator.start();
+ mScrollOffset = 0;
+ if (!animate) {
+ updateHeaderScroll();
} else {
- mCurrentRecyclerViewScrollY = 0;
- applyVerticalTransition();
+ float startValue = mLastScroll - getCurrentScroll();
+ mOffsetAnimator = ObjectAnimator.ofFloat(this, SCROLL_OFFSET, startValue, 0);
+ mOffsetAnimator.addListener(forEndCallback(() -> mOffsetAnimator = null));
+ mOffsetAnimator.start();
}
}
@@ -313,61 +164,60 @@
* Returns {@code true} if a touch event should be intercepted by this controller.
*/
public boolean onInterceptTouchEvent(MotionEvent event) {
- calculateMotionEventOffset(mTempOffset);
- event.offsetLocation(mTempOffset.x, mTempOffset.y);
- try {
- mShouldForwardToRecyclerView = mCurrentRecyclerView.onInterceptTouchEvent(event);
- return mShouldForwardToRecyclerView;
- } finally {
- event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
- }
+ return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY));
}
/**
* Returns {@code true} if this controller has intercepted and consumed a touch event.
*/
public boolean onTouchEvent(MotionEvent event) {
- if (mShouldForwardToRecyclerView) {
- calculateMotionEventOffset(mTempOffset);
- event.offsetLocation(mTempOffset.x, mTempOffset.y);
- try {
- return mCurrentRecyclerView.onTouchEvent(event);
- } finally {
- event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
- }
- }
- return false;
+ return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY);
}
- private void calculateMotionEventOffset(Point p) {
- p.x = mViewHolder.mContainer.getLeft() - mCurrentRecyclerView.getLeft()
- - mSearchAndRecommendationViewParent.getLeft();
- p.y = mViewHolder.mContainer.getTop() - mCurrentRecyclerView.getTop()
- - mSearchAndRecommendationViewParent.getTop();
- }
-
- /** private the height, in pixel, + the vertical margins of a given view. */
- private static int measureHeightWithVerticalMargins(View view) {
- if (view.getVisibility() != View.VISIBLE) {
- return 0;
+ private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) {
+ float dx = mCurrentRecyclerView.getLeft() - mContainer.getLeft();
+ float dy = mCurrentRecyclerView.getTop() - mContainer.getTop();
+ event.offsetLocation(dx, dy);
+ try {
+ return method.proxyEvent(mCurrentRecyclerView, event);
+ } finally {
+ event.offsetLocation(-dx, -dy);
}
- MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
- return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
- + marginLayoutParams.topMargin;
}
@Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mCurrentRecyclerViewScrollY = (Integer) animation.getAnimatedValue();
- applyVerticalTransition();
+ public void onChildViewAttachedToWindow(@NonNull View view) {
+ if (view instanceof EmptySpaceView) {
+ findCurrentEmptyView();
+ }
}
- /**
- * A listener to be notified when there is a content change in the recycler view that may affect
- * the relative position of the search and recommendation container.
- */
- public interface OnContentChangeListener {
- /** Notifies a content change in the recycler view. */
- void onContentChanged();
+ @Override
+ public void onChildViewDetachedFromWindow(@NonNull View view) {
+ if (view == mCurrentEmptySpaceView) {
+ findCurrentEmptyView();
+ }
+ }
+
+ private void findCurrentEmptyView() {
+ if (mCurrentEmptySpaceView != null) {
+ mCurrentEmptySpaceView.setOnYChangeCallback(null);
+ mCurrentEmptySpaceView = null;
+ }
+ int childCount = mCurrentRecyclerView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mCurrentRecyclerView.getChildAt(i);
+ if (view instanceof EmptySpaceView) {
+ mCurrentEmptySpaceView = (EmptySpaceView) view;
+ mCurrentEmptySpaceView.setFixedHeight(getHeaderHeight());
+ mCurrentEmptySpaceView.setOnYChangeCallback(this::updateHeaderScroll);
+ return;
+ }
+ }
+ }
+
+ private interface MotionEventProxyMethod {
+
+ boolean proxyEvent(ViewGroup view, MotionEvent event);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index be83f9a..9dbfa87 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -57,13 +57,12 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.views.TopRoundedCornerView;
+import com.android.launcher3.views.SpringRelativeLayout;
import com.android.launcher3.views.WidgetsEduView;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.search.SearchModeListener;
-import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
import com.android.launcher3.widget.util.WidgetsTableUtils;
import com.android.launcher3.workprofile.PersonalWorkPagedView;
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
@@ -79,7 +78,6 @@
public class WidgetsFullSheet extends BaseWidgetSheet
implements ProviderChangedListener, OnActivePageChangedListener,
WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
- private static final String TAG = WidgetsFullSheet.class.getSimpleName();
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
@@ -149,8 +147,6 @@
};
private final int mTabsHeight;
- private final int mViewPagerTopPadding;
- private final int mSearchAndRecommendationContainerBottomMargin;
private final int mWidgetSheetContentHorizontalPadding;
@Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
@@ -158,10 +154,8 @@
private boolean mIsInSearchMode;
private boolean mIsNoWidgetsViewNeeded;
private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS;
- private View mTabsView;
private TextView mNoWidgetsView;
- private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
- private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
+ private SearchAndRecommendationsScrollController mSearchScrollController;
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -174,14 +168,6 @@
mTabsHeight = mHasWorkProfile
? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
: 0;
- mViewPagerTopPadding = mHasWorkProfile
- ? getContext().getResources()
- .getDimensionPixelSize(R.dimen.widget_picker_view_pager_top_padding)
- : 0;
- mSearchAndRecommendationContainerBottomMargin = resources.getDimensionPixelSize(
- mHasWorkProfile
- ? R.dimen.search_and_recommended_widgets_container_small_bottom_margin
- : R.dimen.search_and_recommended_widgets_container_bottom_margin);
mWidgetSheetContentHorizontalPadding = 2 * resources.getDimensionPixelSize(
R.dimen.widget_cell_horizontal_padding);
}
@@ -194,12 +180,11 @@
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.container);
- TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
: R.layout.widgets_full_sheet_recyclerview;
- layoutInflater.inflate(contentLayoutRes, springLayout, true);
+ layoutInflater.inflate(contentLayoutRes, mContent, true);
RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view));
@@ -209,7 +194,6 @@
mViewPager.initParentViews(this);
mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
- mTabsView = findViewById(R.id.tabs);
findViewById(R.id.tab_personal)
.setOnClickListener((View view) -> mViewPager.snapToPage(0));
findViewById(R.id.tab_work)
@@ -220,33 +204,18 @@
mViewPager = null;
}
- layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
- true);
mNoWidgetsView = findViewById(R.id.no_widgets_text);
- mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
+ mSearchScrollController = new SearchAndRecommendationsScrollController(
findViewById(R.id.search_and_recommendations_container));
- TopRoundedCornerView.LayoutParams layoutParams =
- (TopRoundedCornerView.LayoutParams)
- mSearchAndRecommendationViewHolder.mContainer.getLayoutParams();
- layoutParams.bottomMargin = mSearchAndRecommendationContainerBottomMargin;
- mSearchAndRecommendationViewHolder.mContainer.setLayoutParams(layoutParams);
- mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
- mHasWorkProfile,
- mTabsHeight,
- mSearchAndRecommendationViewHolder,
- findViewById(R.id.primary_widgets_list_view),
- mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
- findViewById(R.id.search_widgets_list_view),
- mTabsView,
- mViewPager,
- mNoWidgetsView);
- fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
-
+ mSearchScrollController.setCurrentRecyclerView(
+ findViewById(R.id.primary_widgets_list_view));
+ mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
+ mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
onRecommendedWidgetsBound();
onWidgetsBound();
- mSearchAndRecommendationViewHolder.mSearchBar.initialize(
+ mSearchScrollController.mSearchBar.initialize(
mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
setUpEducationViewsIfNeeded();
@@ -270,12 +239,13 @@
reset();
resetExpandedHeaders();
mCurrentWidgetsRecyclerView = recyclerView;
- mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
+ mSearchScrollController.setCurrentRecyclerView(recyclerView);
}
}
private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
- boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
+ // The first item is always an empty space entry. Look for any more items.
+ boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
mNoWidgetsView.setText(
@@ -291,7 +261,7 @@
mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
}
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
- mSearchAndRecommendationsScrollController.reset(/* animate= */ true);
+ mSearchScrollController.reset(/* animate= */ true);
}
@VisibleForTesting
@@ -340,7 +310,8 @@
if (mHasWorkProfile) {
setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
}
- mSearchAndRecommendationsScrollController.updateBottomInset(insets.bottom);
+ ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = insets.bottom;
+
if (insets.bottom > 0) {
setupNavBarColor();
} else {
@@ -360,7 +331,7 @@
@Override
protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
- setContentViewChildHorizontalMargin(mSearchAndRecommendationViewHolder.mContainer,
+ setContentViewChildHorizontalMargin(mSearchScrollController.mContainer,
contentHorizontalMarginInPx);
if (mViewPager == null) {
setContentViewChildHorizontalMargin(
@@ -385,14 +356,14 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ if (mSearchScrollController.updateHeaderHeight()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
if (updateMaxSpansPerRow()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ if (mSearchScrollController.updateHeaderHeight()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@@ -455,7 +426,7 @@
if (mHasWorkProfile) {
mViewPager.setVisibility(VISIBLE);
- mTabsView.setVisibility(VISIBLE);
+ mSearchScrollController.mTabBar.setVisibility(VISIBLE);
AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
onActivePageChanged(mViewPager.getCurrentPage());
@@ -465,9 +436,9 @@
// Update recommended widgets section so that it occupies appropriate space on screen to
// leave enough space for presence/absence of mNoWidgetsView.
boolean isNoWidgetsViewNeeded =
- mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.getItemCount() == 0
+ !mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.hasVisibleEntries()
|| (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK)
- .mWidgetsListAdapter.getItemCount() == 0);
+ .mWidgetsListAdapter.hasVisibleEntries());
if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) {
mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded;
onRecommendedWidgetsBound();
@@ -491,8 +462,6 @@
mViewPager.snapToPage(AdapterHolder.PRIMARY);
}
attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
-
- mSearchAndRecommendationsScrollController.updateMarginAndPadding();
}
@Override
@@ -505,10 +474,10 @@
private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
mIsInSearchMode = isInSearchMode;
if (isInSearchMode) {
- mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.setVisibility(GONE);
+ mSearchScrollController.mRecommendedWidgetsTable.setVisibility(GONE);
if (mHasWorkProfile) {
mViewPager.setVisibility(GONE);
- mTabsView.setVisibility(GONE);
+ mSearchScrollController.mTabBar.setVisibility(GONE);
} else {
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.setVisibility(GONE);
}
@@ -536,8 +505,7 @@
}
List<WidgetItem> recommendedWidgets =
mActivityContext.getPopupDataProvider().getRecommendedWidgets();
- WidgetsRecommendationTableLayout table =
- mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
+ WidgetsRecommendationTableLayout table = mSearchScrollController.mRecommendedWidgetsTable;
if (recommendedWidgets.size() > 0) {
float noWidgetsViewHeight = 0;
if (mIsNoWidgetsViewNeeded) {
@@ -554,7 +522,7 @@
makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
MeasureSpec.EXACTLY));
float maxTableHeight = (mContent.getMeasuredHeight()
- - mTabsHeight - mViewPagerTopPadding - getHeaderViewHeight()
+ - mTabsHeight - getHeaderViewHeight()
- noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
@@ -617,10 +585,10 @@
mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
}
- if (mSearchAndRecommendationViewHolder.mSearchBar.isSearchBarFocused()
+ if (mSearchScrollController.mSearchBar.isSearchBarFocused()
&& !getPopupContainer().isEventOverView(
- mSearchAndRecommendationViewHolder.mSearchBarContainer, ev)) {
- mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
+ mSearchScrollController.mSearchBarContainer, ev)) {
+ mSearchScrollController.mSearchBar.clearSearchBarFocus();
}
}
return super.onControllerInterceptTouchEvent(ev);
@@ -661,10 +629,8 @@
@Override
public int getHeaderViewHeight() {
- return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
- + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
- + measureHeightWithVerticalMargins(
- (View) mSearchAndRecommendationViewHolder.mSearchBarContainer);
+ return measureHeightWithVerticalMargins(mSearchScrollController.mHeaderTitle)
+ + measureHeightWithVerticalMargins(mSearchScrollController.mSearchBarContainer);
}
/** private the height, in pixel, + the vertical margins of a given view. */
@@ -681,14 +647,14 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mIsInSearchMode) {
- mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ mSearchScrollController.mSearchBar.reset();
}
}
@Override
public boolean onBackPressed() {
if (mIsInSearchMode) {
- mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ mSearchScrollController.mSearchBar.reset();
return true;
}
return super.onBackPressed();
@@ -701,11 +667,10 @@
}
@Nullable private View getViewToShowEducationTip() {
- if (mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getVisibility() == VISIBLE
- && mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getChildCount() > 0
- ) {
- return ((ViewGroup) mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
- .getChildAt(0)).getChildAt(0);
+ if (mSearchScrollController.mRecommendedWidgetsTable.getVisibility() == VISIBLE
+ && mSearchScrollController.mRecommendedWidgetsTable.getChildCount() > 0) {
+ return ((ViewGroup) mSearchScrollController.mRecommendedWidgetsTable.getChildAt(0))
+ .getChildAt(0);
}
AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
@@ -782,6 +747,7 @@
LayoutInflater.from(context),
apps.getWidgetCache(),
apps.getIconCache(),
+ this::getEmptySpaceHeight,
/* iconClickListener= */ WidgetsFullSheet.this,
/* iconLongClickListener= */ WidgetsFullSheet.this);
mWidgetsListAdapter.setHasStableIds(true);
@@ -801,13 +767,17 @@
mWidgetsListItemAnimator.setSupportsChangeAnimations(false);
}
+ private int getEmptySpaceHeight() {
+ return mSearchScrollController.getHeaderHeight();
+ }
+
void setup(WidgetsRecyclerView recyclerView) {
mWidgetsRecyclerView = recyclerView;
mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator);
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
mWidgetsRecyclerView.setEdgeEffectFactory(
- ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
+ ((SpringRelativeLayout) mContent).createEdgeEffectFactory());
// Recycler view binds to fast scroller when it is attached to screen. Make sure
// search recycler view is bound to fast scroller if user is in search mode at the time
// of attachment.
@@ -818,29 +788,4 @@
mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
}
}
-
- final class SearchAndRecommendationViewHolder {
- final SearchAndRecommendationsView mContainer;
- final View mCollapseHandle;
- final View mSearchBarContainer;
- final WidgetsSearchBar mSearchBar;
- final TextView mHeaderTitle;
- final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
-
- SearchAndRecommendationViewHolder(
- SearchAndRecommendationsView searchAndRecommendationContainer) {
- mContainer = searchAndRecommendationContainer;
- mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
- mSearchBarContainer = mContainer.findViewById(R.id.search_bar_container);
- mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
- mHeaderTitle = mContainer.findViewById(R.id.title);
- mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
- mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
- getRecyclerView().onTouchEvent(event);
- return false;
- });
- mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
- mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
- }
- }
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 1125b82..1ad1f7a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -16,6 +16,9 @@
package com.android.launcher3.widget.picker;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_APP_EXPANDED;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_DEFAULT;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_FIRST;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_LAST;
import android.content.Context;
import android.graphics.Rect;
@@ -52,6 +55,7 @@
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@@ -64,6 +68,7 @@
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
+import java.util.function.IntSupplier;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -84,6 +89,7 @@
private static final boolean DEBUG = false;
/** Uniquely identifies widgets list view type within the app. */
+ private static final int VIEW_TYPE_WIDGETS_SPACE = R.id.view_type_widgets_space;
private static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
@@ -97,7 +103,7 @@
private final WidgetListBaseRowEntryComparator mRowComparator =
new WidgetListBaseRowEntryComparator();
- private List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
+ private final List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
@Nullable private PackageUserKey mWidgetsContentVisiblePackageUserKey = null;
@@ -118,6 +124,7 @@
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
+ IntSupplier emptySpaceHeightProvider,
OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
mContext = context;
mLauncher = Launcher.getLauncher(context);
@@ -126,22 +133,23 @@
WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(
layoutInflater, iconClickListener, iconLongClickListener,
- mCachingPreviewLoader, listDrawableFactory, /* listAdapter= */ this);
+ mCachingPreviewLoader, listDrawableFactory);
mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_HEADER,
new WidgetsListHeaderViewHolderBinder(
layoutInflater,
/* onHeaderClickListener= */ this,
- listDrawableFactory,
- /* listAdapter= */ this));
+ listDrawableFactory));
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_SEARCH_HEADER,
new WidgetsListSearchHeaderViewHolderBinder(
layoutInflater,
/* onHeaderClickListener= */ this,
- listDrawableFactory,
- /* listAdapter= */ this));
+ listDrawableFactory));
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_SPACE,
+ new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
mShortcutPreviewPadding =
2 * context.getResources()
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
@@ -205,6 +213,14 @@
return mVisibleEntries.size();
}
+ /**
+ * Returns true if the adapter has entries which will be visible to the user
+ */
+ public boolean hasVisibleEntries() {
+ // Account for the 1st space entry
+ return getItemCount() > 1;
+ }
+
/** Returns all items that will be drawn in a recycler view. */
public List<WidgetsListBaseEntry> getItems() {
return mVisibleEntries;
@@ -218,8 +234,9 @@
/** Updates the widget list based on {@code tempEntries}. */
public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
mCachingPreviewLoader.clearAll();
- mAllEntries = tempEntries.stream().sorted(mRowComparator)
- .collect(Collectors.toList());
+ mAllEntries.clear();
+ mAllEntries.add(new WidgetListSpaceEntry());
+ tempEntries.stream().sorted(mRowComparator).forEach(mAllEntries::add);
if (shouldClearVisibleEntries()) {
mVisibleEntries.clear();
}
@@ -247,8 +264,9 @@
getOffsetForPosition(previousPositionForPackageUserKey);
List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
- .filter(entry -> (mFilter == null || mFilter.test(entry))
+ .filter(entry -> ((mFilter == null || mFilter.test(entry))
&& mHeaderAndSelectedContentFilter.test(entry))
+ || entry instanceof WidgetListSpaceEntry)
.map(entry -> {
if (entry instanceof WidgetsListBaseEntry.Header<?>
&& matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
@@ -352,7 +370,13 @@
public void onBindViewHolder(ViewHolder holder, int pos) {
ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
- viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos);
+
+ // The first entry has an empty space, count from second entries.
+ int listPos = (pos > 1) ? POSITION_DEFAULT : POSITION_FIRST;
+ if (pos == (getItemCount() - 1)) {
+ listPos |= POSITION_LAST;
+ }
+ viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos);
holder.itemView.setTag(R.id.tag_widget_entry, entry);
}
@@ -395,6 +419,8 @@
return VIEW_TYPE_WIDGETS_HEADER;
} else if (entry instanceof WidgetsListSearchHeaderEntry) {
return VIEW_TYPE_WIDGETS_SEARCH_HEADER;
+ } else if (entry instanceof WidgetListSpaceEntry) {
+ return VIEW_TYPE_WIDGETS_SPACE;
}
throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index 2f8f1ba..00750bd 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -31,16 +31,13 @@
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
- private final WidgetsListAdapter mWidgetsListAdapter;
public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
OnHeaderClickListener onHeaderClickListener,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
}
@Override
@@ -53,14 +50,14 @@
@Override
public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
- int position) {
+ @ListPosition int position) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
widgetsListHeader.setListDrawableState(
WidgetsListDrawableState.obtain(
- /* isFirst= */ position == 0,
- /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
+ (position & POSITION_FIRST) != 0,
+ (position & POSITION_LAST) != 0,
/* isExpanded= */ data.isWidgetListShown()));
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
mOnHeaderClickListener.onHeaderClicked(
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java b/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java
deleted file mode 100644
index 2b7f544..0000000
--- a/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2021 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 com.android.launcher3.widget.picker;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.widget.picker.SearchAndRecommendationsScrollController.OnContentChangeListener;
-
-/**
- * A layout manager for the {@link WidgetsRecyclerView}.
- *
- * {@link #setOnContentChangeListener(OnContentChangeListener)} can be used to register a callback
- * for when the content of the layout manager has changed, following measurement and animation.
- */
-public final class WidgetsListLayoutManager extends LinearLayoutManager {
- @Nullable
- private OnContentChangeListener mOnContentChangeListener;
-
- public WidgetsListLayoutManager(Context context) {
- super(context);
- }
-
- @Override
- public void onLayoutCompleted(RecyclerView.State state) {
- super.onLayoutCompleted(state);
- if (mOnContentChangeListener != null) {
- mOnContentChangeListener.onContentChanged();
- }
- }
-
- public void setOnContentChangeListener(@Nullable OnContentChangeListener listener) {
- mOnContentChangeListener = listener;
- }
-}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
index 31dd9ee..1e2a3bf 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -32,16 +32,13 @@
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
- private final WidgetsListAdapter mWidgetsListAdapter;
public WidgetsListSearchHeaderViewHolderBinder(LayoutInflater layoutInflater,
OnHeaderClickListener onHeaderClickListener,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
}
@Override
@@ -54,14 +51,14 @@
@Override
public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
- WidgetsListSearchHeaderEntry data, int position) {
+ WidgetsListSearchHeaderEntry data, @ListPosition int position) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
widgetsListHeader.setListDrawableState(
WidgetsListDrawableState.obtain(
- /* isFirst= */ position == 0,
- /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
+ (position & POSITION_FIRST) != 0,
+ (position & POSITION_LAST) != 0,
/* isExpanded= */ data.isWidgetListShown()));
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
mOnHeaderClickListener.onHeaderClicked(isExpanded,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 9c06558..804b0ae 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -54,7 +54,6 @@
private final OnLongClickListener mIconLongClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
private final CachingWidgetPreviewLoader mWidgetPreviewLoader;
- private final WidgetsListAdapter mWidgetsListAdapter;
private boolean mApplyBitmapDeferred = false;
public WidgetsListTableViewHolderBinder(
@@ -62,14 +61,12 @@
OnClickListener iconClickListener,
OnLongClickListener iconLongClickListener,
CachingWidgetPreviewLoader widgetPreviewLoader,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
mWidgetPreviewLoader = widgetPreviewLoader;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
}
/**
@@ -97,15 +94,13 @@
@Override
public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry,
- int position) {
+ @ListPosition int position) {
WidgetsListTableView table = holder.mTableContainer;
if (DEBUG) {
Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
entry.mWidgets.size(), table.getChildCount()));
}
-
- table.setListDrawableState(
- position == mWidgetsListAdapter.getItemCount() - 1 ? LAST : MIDDLE);
+ table.setListDrawableState(((position & POSITION_LAST) != 0) ? LAST : MIDDLE);
List<ArrayList<WidgetItem>> widgetItemsTable =
WidgetsTableUtils.groupWidgetItemsIntoTable(
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 0b8ca34..60dfebe 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -53,7 +53,6 @@
private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
@Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
@Nullable private OnClickListener mWidgetCellOnClickListener;
- @Nullable private OnTouchListener mWidgetCellOnTouchListener;
public WidgetsRecommendationTableLayout(Context context) {
this(context, /* attrs= */ null);
@@ -79,11 +78,6 @@
mWidgetCellOnClickListener = widgetCellOnClickListener;
}
- /** Sets a {@link android.view.View.OnTouchListener} for all widget cells in this table. */
- public void setWidgetCellOnTouchListener(OnTouchListener widgetCellOnTouchListener) {
- mWidgetCellOnTouchListener = widgetCellOnTouchListener;
- }
-
/**
* Sets a list of recommended widgets that would like to be displayed in this table within the
* desired {@code recommendationTableMaxHeight}.
@@ -129,7 +123,6 @@
WidgetCell widget = (WidgetCell) LayoutInflater.from(
getContext()).inflate(R.layout.widget_cell, parent, false);
- widget.setOnTouchListener(mWidgetCellOnTouchListener);
View previewContainer = widget.findViewById(R.id.widget_preview_container);
previewContainer.setOnClickListener(mWidgetCellOnClickListener);
previewContainer.setOnLongClickListener(mWidgetCellOnLongClickListener);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 7671841..f780f03 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -23,7 +23,6 @@
import android.view.View;
import android.widget.TableLayout;
-import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
@@ -32,11 +31,12 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import com.android.launcher3.widget.picker.SearchAndRecommendationsScrollController.OnContentChangeListener;
+import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
/**
* The widgets recycler view.
@@ -50,10 +50,13 @@
private final Point mFastScrollerOffset = new Point();
private boolean mTouchDownOnScroller;
private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
+
+ // Cached sizes
private int mLastVisibleWidgetContentTableHeight = 0;
private int mWidgetHeaderHeight = 0;
+ private int mWidgetEmptySpaceHeight = 0;
+
private final int mSpacingBetweenEntries;
- @Nullable private OnContentChangeListener mOnContentChangeListener;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -82,9 +85,7 @@
super.onFinishInflate();
// create a layout manager with Launcher's context so that scroll position
// can be preserved during screen rotation.
- WidgetsListLayoutManager layoutManager = new WidgetsListLayoutManager(getContext());
- layoutManager.setOnContentChangeListener(mOnContentChangeListener);
- setLayoutManager(layoutManager);
+ setLayoutManager(new LinearLayoutManager(getContext()));
}
@Override
@@ -169,10 +170,12 @@
// This assumes there is ever only one content shown in this recycler view.
mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
} else if (view instanceof WidgetsListHeader
- && mLastVisibleWidgetContentTableHeight == 0
+ && mWidgetHeaderHeight == 0
&& view.getMeasuredHeight() > 0) {
// This assumes all header views are of the same height.
mWidgetHeaderHeight = view.getMeasuredHeight();
+ } else if (view instanceof EmptySpaceView && view.getMeasuredHeight() > 0) {
+ mWidgetEmptySpaceHeight = view.getMeasuredHeight();
}
}
@@ -251,14 +254,6 @@
scrollToPosition(0);
}
- public void setOnContentChangeListener(@Nullable OnContentChangeListener listener) {
- mOnContentChangeListener = listener;
- WidgetsListLayoutManager layoutManager = (WidgetsListLayoutManager) getLayoutManager();
- if (layoutManager != null) {
- layoutManager.setOnContentChangeListener(listener);
- }
- }
-
/**
* Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
* {@code untilIndex}.
@@ -283,6 +278,8 @@
}
} else if (entry instanceof WidgetsListContentEntry) {
totalItemsHeight += mLastVisibleWidgetContentTableHeight;
+ } else if (entry instanceof WidgetListSpaceEntry) {
+ totalItemsHeight += mWidgetEmptySpaceHeight;
} else {
throw new UnsupportedOperationException("Can't estimate height for " + entry);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
new file mode 100644
index 0000000..f33c2fa
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 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 com.android.launcher3.widget.picker;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
+
+import java.util.function.IntSupplier;
+
+/**
+ * {@link ViewHolderBinder} for binding the top empty space
+ */
+public class WidgetsSpaceViewHolderBinder
+ implements ViewHolderBinder<WidgetListSpaceEntry, ViewHolder> {
+
+ private final IntSupplier mEmptySpaceHeightProvider;
+
+ public WidgetsSpaceViewHolderBinder(IntSupplier emptySpaceHeightProvider) {
+ mEmptySpaceHeightProvider = emptySpaceHeightProvider;
+ }
+
+ @Override
+ public ViewHolder newViewHolder(ViewGroup parent) {
+ return new ViewHolder(new EmptySpaceView(parent.getContext())) { };
+ }
+
+ @Override
+ public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data, int position) {
+ ((EmptySpaceView) holder.itemView).setFixedHeight(mEmptySpaceHeightProvider.getAsInt());
+ }
+
+ /**
+ * Empty view which allows listening for 'Y' changes
+ */
+ public static class EmptySpaceView extends View {
+
+ private Runnable mOnYChangeCallback;
+ private int mHeight = 0;
+
+ private EmptySpaceView(Context context) {
+ super(context);
+ animate().setUpdateListener(v -> notifyYChanged());
+ }
+
+ /**
+ * Sets the height for the empty view
+ * @return true if the height changed, false otherwise
+ */
+ public boolean setFixedHeight(int height) {
+ if (mHeight != height) {
+ mHeight = height;
+ requestLayout();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, makeMeasureSpec(mHeight, EXACTLY));
+ }
+
+ public void setOnYChangeCallback(Runnable callback) {
+ mOnYChangeCallback = callback;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ notifyYChanged();
+ }
+
+ @Override
+ public void offsetTopAndBottom(int offset) {
+ super.offsetTopAndBottom(offset);
+ notifyYChanged();
+ }
+
+ @Override
+ public void setTranslationY(float translationY) {
+ super.setTranslationY(translationY);
+ notifyYChanged();
+ }
+
+ private void notifyYChanged() {
+ if (mOnYChangeCallback != null) {
+ mOnYChangeCallback.run();
+ }
+ }
+ }
+}