Loading res/values/dimens.xml +1 −0 Original line number Diff line number Diff line Loading @@ -183,6 +183,7 @@ <dimen name="widget_cell_add_button_height">48dp</dimen> <dimen name="widget_cell_add_button_start_padding">8dp</dimen> <dimen name="widget_cell_add_button_end_padding">16dp</dimen> <dimen name="widget_cell_add_button_scroll_padding">24dp</dimen> <dimen name="widget_tabs_button_horizontal_padding">4dp</dimen> <dimen name="widget_tabs_horizontal_padding">16dp</dimen> Loading src/com/android/launcher3/views/StickyHeaderLayout.java +19 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,9 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.R; import java.util.ArrayList; import java.util.List; /** * A {@link LinearLayout} container which allows scrolling parts of its content based on the * scroll of a different view. Views which are marked as sticky are not scrolled, giving the Loading Loading @@ -242,6 +245,22 @@ public class StickyHeaderLayout extends LinearLayout implements return p instanceof MyLayoutParams; } /** * Return a list of all the children that have the sticky layout param set. */ public List<View> getStickyChildren() { List<View> stickyChildren = new ArrayList<>(); int count = getChildCount(); for (int i = 0; i < count; i++) { View v = getChildAt(i); MyLayoutParams lp = (MyLayoutParams) v.getLayoutParams(); if (lp.sticky) { stickyChildren.add(v); } } return stickyChildren; } private static class MyLayoutParams extends LayoutParams { public final boolean sticky; Loading src/com/android/launcher3/widget/BaseWidgetSheet.java +48 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; Loading Loading @@ -141,6 +142,7 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<BaseActivity> } if (enableWidgetTapToAdd()) { scrollToWidgetCell(wc); if (mWidgetCellWithAddButton != null) { // If there is a add button currently showing, hide it. mWidgetCellWithAddButton.hideAddButton(/* animate= */ true); Loading Loading @@ -187,6 +189,52 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<BaseActivity> handleClose(true); } /** * Scroll to show the widget cell. If both the bottom and top of the cell are clipped, this will * prioritize showing the bottom of the cell (where the add button is). */ private void scrollToWidgetCell(@NonNull WidgetCell wc) { final int headerTopClip = getHeaderTopClip(wc); final Rect visibleRect = new Rect(); final boolean isPartiallyVisible = wc.getLocalVisibleRect(visibleRect); int scrollByY = 0; if (isPartiallyVisible) { final int scrollPadding = getResources() .getDimensionPixelSize(R.dimen.widget_cell_add_button_scroll_padding); final int topClip = visibleRect.top + headerTopClip; final int bottomClip = wc.getHeight() - visibleRect.bottom; if (bottomClip != 0) { scrollByY = bottomClip + scrollPadding; } else if (topClip != 0) { scrollByY = -topClip - scrollPadding; } } if (isPartiallyVisible && scrollByY == 0) { // Widget is fully visible. return; } else if (!isPartiallyVisible) { Log.e("BaseWidgetSheet", "click on invisible WidgetCell should not be possible"); return; } scrollCellContainerByY(wc, scrollByY); } /** * Find the nearest scrollable container of the given WidgetCell, and scroll by the given * amount. */ protected abstract void scrollCellContainerByY(WidgetCell wc, int scrollByY); /** * Return the top clip of any sticky headers over the given cell. */ protected int getHeaderTopClip(@NonNull WidgetCell cell) { return 0; } @Override public boolean onLongClick(View v) { TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick"); Loading src/com/android/launcher3/widget/WidgetsBottomSheet.java +13 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.Interpolator; import android.widget.ScrollView; import android.widget.TableLayout; Loading Loading @@ -282,4 +283,16 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { float distanceToMove, Interpolator interpolator, PendingAnimation target) { target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator); } @Override protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) { for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) { if (parent instanceof ScrollView scrollView) { scrollView.smoothScrollBy(0, scrollByY); return; } else if (parent == this) { return; } } } } src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +57 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.WindowInsets; import android.view.WindowInsetsController; import android.view.animation.AnimationUtils; Loading @@ -46,6 +47,7 @@ import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.annotation.VisibleForTesting; Loading @@ -69,6 +71,7 @@ import com.android.launcher3.views.SpringRelativeLayout; import com.android.launcher3.views.StickyHeaderLayout; import com.android.launcher3.views.WidgetsEduView; import com.android.launcher3.widget.BaseWidgetSheet; import com.android.launcher3.widget.WidgetCell; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.picker.search.SearchModeListener; import com.android.launcher3.widget.picker.search.WidgetsSearchBar; Loading Loading @@ -991,6 +994,60 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop(); } @Override protected int getHeaderTopClip(@NonNull WidgetCell cell) { StickyHeaderLayout header = findViewById(R.id.search_and_recommendations_container); if (header == null) { return 0; } Rect cellRect = new Rect(); boolean cellIsPartiallyVisible = cell.getGlobalVisibleRect(cellRect); if (cellIsPartiallyVisible) { Rect occludingRect = new Rect(); for (View headerChild : header.getStickyChildren()) { Rect childRect = new Rect(); boolean childVisible = headerChild.getGlobalVisibleRect(childRect); if (childVisible && childRect.intersect(cellRect)) { occludingRect.union(childRect); } } if (!occludingRect.isEmpty() && cellRect.top < occludingRect.bottom) { return occludingRect.bottom - cellRect.top; } } return 0; } @Override protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) { for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) { if (parent instanceof WidgetsRecyclerView recyclerView) { // Scrollable container for main widget list. recyclerView.smoothScrollBy(0, scrollByY); return; } else if (parent instanceof StickyHeaderLayout header) { // Scrollable container for recommendations. We still scroll on the recycler (even // though the recommendations are not in the recycler view) because the // StickyHeaderLayout scroll is connected to the currently visible recycler view. WidgetsRecyclerView recyclerView = findVisibleRecyclerView(); if (recyclerView != null) { recyclerView.smoothScrollBy(0, scrollByY); } return; } else if (parent == this) { return; } } } @Nullable private WidgetsRecyclerView findVisibleRecyclerView() { if (mViewPager != null) { return (WidgetsRecyclerView) mViewPager.getPageAt(mViewPager.getCurrentPage()); } return findViewById(R.id.primary_widgets_list_view); } /** A holder class for holding adapters & their corresponding recycler view. */ final class AdapterHolder { static final int PRIMARY = 0; Loading Loading
res/values/dimens.xml +1 −0 Original line number Diff line number Diff line Loading @@ -183,6 +183,7 @@ <dimen name="widget_cell_add_button_height">48dp</dimen> <dimen name="widget_cell_add_button_start_padding">8dp</dimen> <dimen name="widget_cell_add_button_end_padding">16dp</dimen> <dimen name="widget_cell_add_button_scroll_padding">24dp</dimen> <dimen name="widget_tabs_button_horizontal_padding">4dp</dimen> <dimen name="widget_tabs_horizontal_padding">16dp</dimen> Loading
src/com/android/launcher3/views/StickyHeaderLayout.java +19 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,9 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.R; import java.util.ArrayList; import java.util.List; /** * A {@link LinearLayout} container which allows scrolling parts of its content based on the * scroll of a different view. Views which are marked as sticky are not scrolled, giving the Loading Loading @@ -242,6 +245,22 @@ public class StickyHeaderLayout extends LinearLayout implements return p instanceof MyLayoutParams; } /** * Return a list of all the children that have the sticky layout param set. */ public List<View> getStickyChildren() { List<View> stickyChildren = new ArrayList<>(); int count = getChildCount(); for (int i = 0; i < count; i++) { View v = getChildAt(i); MyLayoutParams lp = (MyLayoutParams) v.getLayoutParams(); if (lp.sticky) { stickyChildren.add(v); } } return stickyChildren; } private static class MyLayoutParams extends LayoutParams { public final boolean sticky; Loading
src/com/android/launcher3/widget/BaseWidgetSheet.java +48 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; Loading Loading @@ -141,6 +142,7 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<BaseActivity> } if (enableWidgetTapToAdd()) { scrollToWidgetCell(wc); if (mWidgetCellWithAddButton != null) { // If there is a add button currently showing, hide it. mWidgetCellWithAddButton.hideAddButton(/* animate= */ true); Loading Loading @@ -187,6 +189,52 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<BaseActivity> handleClose(true); } /** * Scroll to show the widget cell. If both the bottom and top of the cell are clipped, this will * prioritize showing the bottom of the cell (where the add button is). */ private void scrollToWidgetCell(@NonNull WidgetCell wc) { final int headerTopClip = getHeaderTopClip(wc); final Rect visibleRect = new Rect(); final boolean isPartiallyVisible = wc.getLocalVisibleRect(visibleRect); int scrollByY = 0; if (isPartiallyVisible) { final int scrollPadding = getResources() .getDimensionPixelSize(R.dimen.widget_cell_add_button_scroll_padding); final int topClip = visibleRect.top + headerTopClip; final int bottomClip = wc.getHeight() - visibleRect.bottom; if (bottomClip != 0) { scrollByY = bottomClip + scrollPadding; } else if (topClip != 0) { scrollByY = -topClip - scrollPadding; } } if (isPartiallyVisible && scrollByY == 0) { // Widget is fully visible. return; } else if (!isPartiallyVisible) { Log.e("BaseWidgetSheet", "click on invisible WidgetCell should not be possible"); return; } scrollCellContainerByY(wc, scrollByY); } /** * Find the nearest scrollable container of the given WidgetCell, and scroll by the given * amount. */ protected abstract void scrollCellContainerByY(WidgetCell wc, int scrollByY); /** * Return the top clip of any sticky headers over the given cell. */ protected int getHeaderTopClip(@NonNull WidgetCell cell) { return 0; } @Override public boolean onLongClick(View v) { TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick"); Loading
src/com/android/launcher3/widget/WidgetsBottomSheet.java +13 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.Interpolator; import android.widget.ScrollView; import android.widget.TableLayout; Loading Loading @@ -282,4 +283,16 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { float distanceToMove, Interpolator interpolator, PendingAnimation target) { target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator); } @Override protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) { for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) { if (parent instanceof ScrollView scrollView) { scrollView.smoothScrollBy(0, scrollByY); return; } else if (parent == this) { return; } } } }
src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +57 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.WindowInsets; import android.view.WindowInsetsController; import android.view.animation.AnimationUtils; Loading @@ -46,6 +47,7 @@ import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.annotation.VisibleForTesting; Loading @@ -69,6 +71,7 @@ import com.android.launcher3.views.SpringRelativeLayout; import com.android.launcher3.views.StickyHeaderLayout; import com.android.launcher3.views.WidgetsEduView; import com.android.launcher3.widget.BaseWidgetSheet; import com.android.launcher3.widget.WidgetCell; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.picker.search.SearchModeListener; import com.android.launcher3.widget.picker.search.WidgetsSearchBar; Loading Loading @@ -991,6 +994,60 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop(); } @Override protected int getHeaderTopClip(@NonNull WidgetCell cell) { StickyHeaderLayout header = findViewById(R.id.search_and_recommendations_container); if (header == null) { return 0; } Rect cellRect = new Rect(); boolean cellIsPartiallyVisible = cell.getGlobalVisibleRect(cellRect); if (cellIsPartiallyVisible) { Rect occludingRect = new Rect(); for (View headerChild : header.getStickyChildren()) { Rect childRect = new Rect(); boolean childVisible = headerChild.getGlobalVisibleRect(childRect); if (childVisible && childRect.intersect(cellRect)) { occludingRect.union(childRect); } } if (!occludingRect.isEmpty() && cellRect.top < occludingRect.bottom) { return occludingRect.bottom - cellRect.top; } } return 0; } @Override protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) { for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) { if (parent instanceof WidgetsRecyclerView recyclerView) { // Scrollable container for main widget list. recyclerView.smoothScrollBy(0, scrollByY); return; } else if (parent instanceof StickyHeaderLayout header) { // Scrollable container for recommendations. We still scroll on the recycler (even // though the recommendations are not in the recycler view) because the // StickyHeaderLayout scroll is connected to the currently visible recycler view. WidgetsRecyclerView recyclerView = findVisibleRecyclerView(); if (recyclerView != null) { recyclerView.smoothScrollBy(0, scrollByY); } return; } else if (parent == this) { return; } } } @Nullable private WidgetsRecyclerView findVisibleRecyclerView() { if (mViewPager != null) { return (WidgetsRecyclerView) mViewPager.getPageAt(mViewPager.getCurrentPage()); } return findViewById(R.id.primary_widgets_list_view); } /** A holder class for holding adapters & their corresponding recycler view. */ final class AdapterHolder { static final int PRIMARY = 0; Loading