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
This commit is contained in:
parent
55861dc50b
commit
77acf12905
|
@ -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="?android:attr/dialogCornerRadius"
|
||||
android:topRightRadius="?android:attr/dialogCornerRadius"
|
||||
android:bottomLeftRadius="0dp"
|
||||
android:bottomRightRadius="0dp"
|
||||
/>
|
||||
android:topLeftRadius="@dimen/dialogCornerRadius"
|
||||
android:topRightRadius="@dimen/dialogCornerRadius" />
|
||||
</shape>
|
|
@ -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>
|
|
@ -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="@dimen/default_dialog_corner_radius"
|
||||
android:topRightRadius="@dimen/default_dialog_corner_radius"
|
||||
android:bottomLeftRadius="0dp"
|
||||
android:bottomRightRadius="0dp"
|
||||
/>
|
||||
</shape>
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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" />
|
||||
<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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -82,7 +82,7 @@ public final class WidgetsListAdapterTest {
|
|||
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))
|
||||
|
|
|
@ -41,7 +41,6 @@ import com.android.launcher3.model.WidgetItem;
|
|||
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 @@ public final class WidgetsListHeaderViewHolderBinderTest {
|
|||
@Mock
|
||||
private DeviceProfile mDeviceProfile;
|
||||
@Mock
|
||||
private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
|
||||
@Mock
|
||||
private OnHeaderClickListener mOnHeaderClickListener;
|
||||
|
||||
@Before
|
||||
|
@ -99,18 +96,10 @@ public final class WidgetsListHeaderViewHolderBinderTest {
|
|||
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
|
||||
|
|
|
@ -41,7 +41,6 @@ import com.android.launcher3.model.WidgetItem;
|
|||
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 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest {
|
|||
@Mock
|
||||
private DeviceProfile mDeviceProfile;
|
||||
@Mock
|
||||
private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
|
||||
@Mock
|
||||
private OnHeaderClickListener mOnHeaderClickListener;
|
||||
|
||||
@Before
|
||||
|
@ -99,18 +96,10 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest {
|
|||
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
|
||||
|
|
|
@ -107,19 +107,12 @@ public final class WidgetsListTableViewHolderBinderTest {
|
|||
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
|
||||
|
|
|
@ -17,8 +17,12 @@ package com.android.launcher3.recyclerview;
|
|||
|
||||
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 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
|||
* @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 @@ public interface ViewHolderBinder<T, V extends ViewHolder> {
|
|||
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
|
||||
|
|
|
@ -28,6 +28,7 @@ import android.util.AttributeSet;
|
|||
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 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
|
|||
protected final SingleAxisSwipeDetector mSwipeDetector;
|
||||
protected final ObjectAnimator mOpenCloseAnimator;
|
||||
|
||||
protected View mContent;
|
||||
protected ViewGroup mContent;
|
||||
protected final View mColorScrim;
|
||||
protected Interpolator mScrollInterpolator;
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ import android.view.ViewConfiguration;
|
|||
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 @@ public class RecyclerViewFastScroller extends View {
|
|||
|
||||
protected BaseRecyclerView mRv;
|
||||
private RecyclerView.OnScrollListener mOnScrollListener;
|
||||
@Nullable private OnFastScrollChangeListener mOnFastScrollChangeListener;
|
||||
|
||||
private int mDownX;
|
||||
private int mDownY;
|
||||
|
@ -208,7 +206,6 @@ public class RecyclerViewFastScroller extends View {
|
|||
int rvCurrentOffsetY = mRv.getCurrentScrollY();
|
||||
if (mRvOffsetY != rvCurrentOffsetY) {
|
||||
mRvOffsetY = mRv.getCurrentScrollY();
|
||||
notifyScrollChanged();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -216,7 +213,6 @@ public class RecyclerViewFastScroller extends View {
|
|||
mThumbOffsetY = y;
|
||||
invalidate();
|
||||
mRvOffsetY = mRv.getCurrentScrollY();
|
||||
notifyScrollChanged();
|
||||
}
|
||||
|
||||
public int getThumbOffsetY() {
|
||||
|
@ -461,23 +457,4 @@ public class RecyclerViewFastScroller extends View {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -73,11 +73,13 @@ public abstract class WidgetsListBaseEntry {
|
|||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
|
|
@ -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 (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
|
||||
int yDisplacementForTabs = Math.max(-mCurrentRecyclerViewScrollY,
|
||||
-mCollapsibleHeightForTabs);
|
||||
mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
|
||||
if (mCurrentEmptySpaceView != null
|
||||
&& mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) {
|
||||
hasSizeUpdated = true;
|
||||
}
|
||||
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 @@ final class SearchAndRecommendationsScrollController implements
|
|||
* 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,13 +57,12 @@ import com.android.launcher3.compat.AccessibilityManagerCompat;
|
|||
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 @@ import java.util.stream.IntStream;
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
};
|
||||
|
||||
private final int mTabsHeight;
|
||||
private final int mViewPagerTopPadding;
|
||||
private final int mSearchAndRecommendationContainerBottomMargin;
|
||||
private final int mWidgetSheetContentHorizontalPadding;
|
||||
|
||||
@Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
|
||||
|
@ -158,10 +154,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
|
||||
@Override
|
||||
protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
|
||||
setContentViewChildHorizontalMargin(mSearchAndRecommendationViewHolder.mContainer,
|
||||
setContentViewChildHorizontalMargin(mSearchScrollController.mContainer,
|
||||
contentHorizontalMarginInPx);
|
||||
if (mViewPager == null) {
|
||||
setContentViewChildHorizontalMargin(
|
||||
|
@ -385,14 +356,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
|
||||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
// 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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
mViewPager.snapToPage(AdapterHolder.PRIMARY);
|
||||
}
|
||||
attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
|
||||
|
||||
mSearchAndRecommendationsScrollController.updateMarginAndPadding();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -505,10 +474,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
}
|
||||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
|
||||
@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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
}
|
||||
|
||||
@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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
LayoutInflater.from(context),
|
||||
apps.getWidgetCache(),
|
||||
apps.getIconCache(),
|
||||
this::getEmptySpaceHeight,
|
||||
/* iconClickListener= */ WidgetsFullSheet.this,
|
||||
/* iconLongClickListener= */ WidgetsFullSheet.this);
|
||||
mWidgetsListAdapter.setHasStableIds(true);
|
||||
|
@ -801,13 +767,17 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.CachingWidgetPreviewLoader;
|
|||
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.Comparator;
|
|||
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 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
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 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
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 class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
|
||||
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 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
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 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
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 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
/** 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 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
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 class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
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 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -31,16 +31,13 @@ public final class WidgetsListHeaderViewHolderBinder implements
|
|||
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 @@ public final class WidgetsListHeaderViewHolderBinder implements
|
|||
|
||||
@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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -32,16 +32,13 @@ public final class WidgetsListSearchHeaderViewHolderBinder implements
|
|||
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 @@ public final class WidgetsListSearchHeaderViewHolderBinder implements
|
|||
|
||||
@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,
|
||||
|
|
|
@ -54,7 +54,6 @@ public final class WidgetsListTableViewHolderBinder
|
|||
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 @@ public final class WidgetsListTableViewHolderBinder
|
|||
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 @@ public final class WidgetsListTableViewHolderBinder
|
|||
|
||||
@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(
|
||||
|
|
|
@ -53,7 +53,6 @@ public final class WidgetsRecommendationTableLayout extends TableLayout {
|
|||
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 @@ public final class WidgetsRecommendationTableLayout extends TableLayout {
|
|||
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 @@ public final class WidgetsRecommendationTableLayout extends TableLayout {
|
|||
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);
|
||||
|
|
|
@ -23,7 +23,6 @@ import android.view.MotionEvent;
|
|||
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.BaseRecyclerView;
|
|||
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 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
|
|||
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 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
|
|||
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 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
|
|||
// 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 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
|
|||
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 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
|
|||
}
|
||||
} else if (entry instanceof WidgetsListContentEntry) {
|
||||
totalItemsHeight += mLastVisibleWidgetContentTableHeight;
|
||||
} else if (entry instanceof WidgetListSpaceEntry) {
|
||||
totalItemsHeight += mWidgetEmptySpaceHeight;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Can't estimate height for " + entry);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue