Loading res/layout/widgets_full_sheet_paged_view.xml +5 −4 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ </com.android.launcher3.widget.picker.WidgetPagedView> <!-- SearchAndRecommendationsView contains the tab layout as well --> <com.android.launcher3.widget.picker.SearchAndRecommendationsView <com.android.launcher3.views.StickyHeaderLayout android:id="@+id/search_and_recommendations_container" android:layout_width="match_parent" android:layout_height="wrap_content" Loading @@ -68,7 +68,7 @@ android:background="?android:attr/colorBackground" android:paddingBottom="8dp" android:paddingHorizontal="@dimen/widget_list_horizontal_margin" android:clipToPadding="false"> launcher:layout_sticky="true"> <include layout="@layout/widgets_search_bar" /> </FrameLayout> Loading @@ -92,7 +92,8 @@ android:paddingLeft="@dimen/widget_tabs_horizontal_padding" android:paddingRight="@dimen/widget_tabs_horizontal_padding" android:background="?android:attr/colorBackground" style="@style/TextHeadline"> style="@style/TextHeadline" launcher:layout_sticky="true"> <Button android:id="@+id/tab_personal" Loading Loading @@ -121,5 +122,5 @@ style="?android:attr/borderlessButtonStyle" /> </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip> </com.android.launcher3.widget.picker.SearchAndRecommendationsView> </com.android.launcher3.views.StickyHeaderLayout> </merge> No newline at end of file res/layout/widgets_full_sheet_recyclerview.xml +6 −4 Original line number Diff line number Diff line Loading @@ -13,7 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:launcher="http://schemas.android.com/apk/res-auto" > <com.android.launcher3.widget.picker.WidgetsRecyclerView android:id="@+id/primary_widgets_list_view" android:layout_below="@id/collapse_handle" Loading @@ -23,7 +24,7 @@ android:clipToPadding="false" /> <!-- SearchAndRecommendationsView without the tab layout as well --> <com.android.launcher3.widget.picker.SearchAndRecommendationsView <com.android.launcher3.views.StickyHeaderLayout android:id="@+id/search_and_recommendations_container" android:layout_width="match_parent" android:layout_height="wrap_content" Loading @@ -50,7 +51,8 @@ android:background="?android:attr/colorBackground" android:paddingHorizontal="@dimen/widget_list_horizontal_margin" android:paddingBottom="8dp" android:clipToPadding="false"> android:clipToPadding="false" launcher:layout_sticky="true" > <include layout="@layout/widgets_search_bar" /> </FrameLayout> Loading @@ -63,6 +65,6 @@ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding" android:paddingHorizontal="@dimen/widget_list_horizontal_margin" android:visibility="gone" /> </com.android.launcher3.widget.picker.SearchAndRecommendationsView> </com.android.launcher3.views.StickyHeaderLayout> </merge> No newline at end of file res/values/attrs.xml +4 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,10 @@ <attr name="layout_ignoreInsets" format="boolean" /> </declare-styleable> <declare-styleable name="StickyScroller_Layout"> <attr name="layout_sticky" format="boolean" /> </declare-styleable> <declare-styleable name="GridDisplayOption"> <attr name="name" format="string" /> Loading src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java→src/com/android/launcher3/views/StickyHeaderLayout.java +327 −0 Original line number Diff line number Diff line Loading @@ -13,58 +13,55 @@ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.widget.picker; package com.android.launcher3.views; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; import android.animation.Animator; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.FloatProperty; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; 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. * 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 * illusion of a sticky header. */ final class SearchAndRecommendationsScrollController implements public class StickyHeaderLayout extends LinearLayout implements RecyclerView.OnChildAttachStateChangeListener { private static final FloatProperty<SearchAndRecommendationsScrollController> SCROLL_OFFSET = new FloatProperty<SearchAndRecommendationsScrollController>("scrollAnimOffset") { private static final FloatProperty<StickyHeaderLayout> SCROLL_OFFSET = new FloatProperty<StickyHeaderLayout>("scrollAnimOffset") { @Override public void setValue(SearchAndRecommendationsScrollController controller, float offset) { controller.mScrollOffset = offset; controller.updateHeaderScroll(); public void setValue(StickyHeaderLayout view, float offset) { view.mScrollOffset = offset; view.updateHeaderScroll(); } @Override public Float get(SearchAndRecommendationsScrollController controller) { return controller.mScrollOffset; public Float get(StickyHeaderLayout view) { return view.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 RecyclerView mCurrentRecyclerView; private EmptySpaceView mCurrentEmptySpaceView; private float mLastScroll = 0; Loading @@ -72,22 +69,29 @@ final class SearchAndRecommendationsScrollController implements private Animator mOffsetAnimator; private boolean mShouldForwardToRecyclerView = false; private int mHeaderHeight; SearchAndRecommendationsScrollController( 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); public StickyHeaderLayout(Context context) { this(context, /* attrs= */ null); } public StickyHeaderLayout(Context context, AttributeSet attrs) { this(context, attrs, /* defStyleAttr= */ 0); } public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, /* defStyleRes= */ 0); } mContainer.setSearchAndRecommendationScrollController(this); public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) { /** * Sets the recycler view, this sticky header should track */ public void setCurrentRecyclerView(RecyclerView currentRecyclerView) { boolean animateReset = mCurrentRecyclerView != null; if (mCurrentRecyclerView != null) { mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this); Loading @@ -104,16 +108,11 @@ final class SearchAndRecommendationsScrollController implements 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); int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); MyLayoutParams lp = (MyLayoutParams) child.getLayoutParams(); child.setTranslationY(Math.max(mLastScroll, lp.scrollLimit)); } } Loading @@ -121,25 +120,14 @@ final class SearchAndRecommendationsScrollController implements return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY()); } /** * Updates the scrollable header height * * @return {@code true} if the header height or dependent property changed. */ public boolean updateHeaderHeight() { boolean hasSizeUpdated = false; int headerHeight = mContainer.getMeasuredHeight(); if (headerHeight != mHeaderHeight) { mHeaderHeight = headerHeight; hasSizeUpdated = true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mCurrentEmptySpaceView != null && mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) { hasSizeUpdated = true; mHeaderHeight = getMeasuredHeight(); if (mCurrentEmptySpaceView != null) { mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight); } return hasSizeUpdated; } /** Resets any previous view translation. */ Loading @@ -160,23 +148,21 @@ final class SearchAndRecommendationsScrollController implements } } /** * Returns {@code true} if a touch event should be intercepted by this controller. */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY)); return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY)) || super.onInterceptTouchEvent(event); } /** * Returns {@code true} if this controller has intercepted and consumed a touch event. */ @Override public boolean onTouchEvent(MotionEvent event) { return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY); return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY) || super.onTouchEvent(event); } private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) { float dx = mCurrentRecyclerView.getLeft() - mContainer.getLeft(); float dy = mCurrentRecyclerView.getTop() - mContainer.getTop(); float dx = mCurrentRecyclerView.getLeft() - getLeft(); float dy = mCurrentRecyclerView.getTop() - getTop(); event.offsetLocation(dx, dy); try { return method.proxyEvent(mCurrentRecyclerView, event); Loading Loading @@ -216,8 +202,126 @@ final class SearchAndRecommendationsScrollController implements } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); // Update various stick parameters int count = getChildCount(); int stickyHeaderHeight = 0; for (int i = 0; i < count; i++) { View v = getChildAt(i); MyLayoutParams lp = (MyLayoutParams) v.getLayoutParams(); if (lp.sticky) { lp.scrollLimit = -v.getTop() + stickyHeaderHeight; stickyHeaderHeight += v.getHeight(); } else { lp.scrollLimit = Integer.MIN_VALUE; } } updateHeaderScroll(); } @Override protected LayoutParams generateDefaultLayoutParams() { return new MyLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { return new MyLayoutParams(lp.width, lp.height); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MyLayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof MyLayoutParams; } private static class MyLayoutParams extends LayoutParams { public final boolean sticky; public int scrollLimit; MyLayoutParams(int width, int height) { super(width, height); sticky = false; } MyLayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StickyScroller_Layout); sticky = a.getBoolean(R.styleable.StickyScroller_Layout_layout_sticky, false); a.recycle(); } } private interface MotionEventProxyMethod { boolean proxyEvent(ViewGroup view, MotionEvent event); } /** * Empty view which allows listening for 'Y' changes */ public static class EmptySpaceView extends View { private Runnable mOnYChangeCallback; private int mHeight = 0; public 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(); } } } } src/com/android/launcher3/widget/picker/SearchAndRecommendationsView.javadeleted 100644 → 0 +0 −62 Original line number Diff line number Diff line /* * 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 android.util.AttributeSet; import android.view.MotionEvent; import android.widget.LinearLayout; /** * A {@link LinearLayout} container for holding search and widgets recommendation. * * <p>This class intercepts touch events and dispatch them to the right view. */ public class SearchAndRecommendationsView extends LinearLayout { private SearchAndRecommendationsScrollController mController; public SearchAndRecommendationsView(Context context) { this(context, /* attrs= */ null); } public SearchAndRecommendationsView(Context context, AttributeSet attrs) { this(context, attrs, /* defStyleAttr= */ 0); } public SearchAndRecommendationsView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, /* defStyleRes= */ 0); } public SearchAndRecommendationsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public void setSearchAndRecommendationScrollController( SearchAndRecommendationsScrollController controller) { mController = controller; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mController.onInterceptTouchEvent(event) || super.onInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { return mController.onTouchEvent(event) || super.onTouchEvent(event); } } Loading
res/layout/widgets_full_sheet_paged_view.xml +5 −4 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ </com.android.launcher3.widget.picker.WidgetPagedView> <!-- SearchAndRecommendationsView contains the tab layout as well --> <com.android.launcher3.widget.picker.SearchAndRecommendationsView <com.android.launcher3.views.StickyHeaderLayout android:id="@+id/search_and_recommendations_container" android:layout_width="match_parent" android:layout_height="wrap_content" Loading @@ -68,7 +68,7 @@ android:background="?android:attr/colorBackground" android:paddingBottom="8dp" android:paddingHorizontal="@dimen/widget_list_horizontal_margin" android:clipToPadding="false"> launcher:layout_sticky="true"> <include layout="@layout/widgets_search_bar" /> </FrameLayout> Loading @@ -92,7 +92,8 @@ android:paddingLeft="@dimen/widget_tabs_horizontal_padding" android:paddingRight="@dimen/widget_tabs_horizontal_padding" android:background="?android:attr/colorBackground" style="@style/TextHeadline"> style="@style/TextHeadline" launcher:layout_sticky="true"> <Button android:id="@+id/tab_personal" Loading Loading @@ -121,5 +122,5 @@ style="?android:attr/borderlessButtonStyle" /> </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip> </com.android.launcher3.widget.picker.SearchAndRecommendationsView> </com.android.launcher3.views.StickyHeaderLayout> </merge> No newline at end of file
res/layout/widgets_full_sheet_recyclerview.xml +6 −4 Original line number Diff line number Diff line Loading @@ -13,7 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:launcher="http://schemas.android.com/apk/res-auto" > <com.android.launcher3.widget.picker.WidgetsRecyclerView android:id="@+id/primary_widgets_list_view" android:layout_below="@id/collapse_handle" Loading @@ -23,7 +24,7 @@ android:clipToPadding="false" /> <!-- SearchAndRecommendationsView without the tab layout as well --> <com.android.launcher3.widget.picker.SearchAndRecommendationsView <com.android.launcher3.views.StickyHeaderLayout android:id="@+id/search_and_recommendations_container" android:layout_width="match_parent" android:layout_height="wrap_content" Loading @@ -50,7 +51,8 @@ android:background="?android:attr/colorBackground" android:paddingHorizontal="@dimen/widget_list_horizontal_margin" android:paddingBottom="8dp" android:clipToPadding="false"> android:clipToPadding="false" launcher:layout_sticky="true" > <include layout="@layout/widgets_search_bar" /> </FrameLayout> Loading @@ -63,6 +65,6 @@ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding" android:paddingHorizontal="@dimen/widget_list_horizontal_margin" android:visibility="gone" /> </com.android.launcher3.widget.picker.SearchAndRecommendationsView> </com.android.launcher3.views.StickyHeaderLayout> </merge> No newline at end of file
res/values/attrs.xml +4 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,10 @@ <attr name="layout_ignoreInsets" format="boolean" /> </declare-styleable> <declare-styleable name="StickyScroller_Layout"> <attr name="layout_sticky" format="boolean" /> </declare-styleable> <declare-styleable name="GridDisplayOption"> <attr name="name" format="string" /> Loading
src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java→src/com/android/launcher3/views/StickyHeaderLayout.java +327 −0 Original line number Diff line number Diff line Loading @@ -13,58 +13,55 @@ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.widget.picker; package com.android.launcher3.views; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; import android.animation.Animator; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.FloatProperty; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; 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. * 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 * illusion of a sticky header. */ final class SearchAndRecommendationsScrollController implements public class StickyHeaderLayout extends LinearLayout implements RecyclerView.OnChildAttachStateChangeListener { private static final FloatProperty<SearchAndRecommendationsScrollController> SCROLL_OFFSET = new FloatProperty<SearchAndRecommendationsScrollController>("scrollAnimOffset") { private static final FloatProperty<StickyHeaderLayout> SCROLL_OFFSET = new FloatProperty<StickyHeaderLayout>("scrollAnimOffset") { @Override public void setValue(SearchAndRecommendationsScrollController controller, float offset) { controller.mScrollOffset = offset; controller.updateHeaderScroll(); public void setValue(StickyHeaderLayout view, float offset) { view.mScrollOffset = offset; view.updateHeaderScroll(); } @Override public Float get(SearchAndRecommendationsScrollController controller) { return controller.mScrollOffset; public Float get(StickyHeaderLayout view) { return view.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 RecyclerView mCurrentRecyclerView; private EmptySpaceView mCurrentEmptySpaceView; private float mLastScroll = 0; Loading @@ -72,22 +69,29 @@ final class SearchAndRecommendationsScrollController implements private Animator mOffsetAnimator; private boolean mShouldForwardToRecyclerView = false; private int mHeaderHeight; SearchAndRecommendationsScrollController( 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); public StickyHeaderLayout(Context context) { this(context, /* attrs= */ null); } public StickyHeaderLayout(Context context, AttributeSet attrs) { this(context, attrs, /* defStyleAttr= */ 0); } public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, /* defStyleRes= */ 0); } mContainer.setSearchAndRecommendationScrollController(this); public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) { /** * Sets the recycler view, this sticky header should track */ public void setCurrentRecyclerView(RecyclerView currentRecyclerView) { boolean animateReset = mCurrentRecyclerView != null; if (mCurrentRecyclerView != null) { mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this); Loading @@ -104,16 +108,11 @@ final class SearchAndRecommendationsScrollController implements 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); int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); MyLayoutParams lp = (MyLayoutParams) child.getLayoutParams(); child.setTranslationY(Math.max(mLastScroll, lp.scrollLimit)); } } Loading @@ -121,25 +120,14 @@ final class SearchAndRecommendationsScrollController implements return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY()); } /** * Updates the scrollable header height * * @return {@code true} if the header height or dependent property changed. */ public boolean updateHeaderHeight() { boolean hasSizeUpdated = false; int headerHeight = mContainer.getMeasuredHeight(); if (headerHeight != mHeaderHeight) { mHeaderHeight = headerHeight; hasSizeUpdated = true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mCurrentEmptySpaceView != null && mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) { hasSizeUpdated = true; mHeaderHeight = getMeasuredHeight(); if (mCurrentEmptySpaceView != null) { mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight); } return hasSizeUpdated; } /** Resets any previous view translation. */ Loading @@ -160,23 +148,21 @@ final class SearchAndRecommendationsScrollController implements } } /** * Returns {@code true} if a touch event should be intercepted by this controller. */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY)); return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY)) || super.onInterceptTouchEvent(event); } /** * Returns {@code true} if this controller has intercepted and consumed a touch event. */ @Override public boolean onTouchEvent(MotionEvent event) { return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY); return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY) || super.onTouchEvent(event); } private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) { float dx = mCurrentRecyclerView.getLeft() - mContainer.getLeft(); float dy = mCurrentRecyclerView.getTop() - mContainer.getTop(); float dx = mCurrentRecyclerView.getLeft() - getLeft(); float dy = mCurrentRecyclerView.getTop() - getTop(); event.offsetLocation(dx, dy); try { return method.proxyEvent(mCurrentRecyclerView, event); Loading Loading @@ -216,8 +202,126 @@ final class SearchAndRecommendationsScrollController implements } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); // Update various stick parameters int count = getChildCount(); int stickyHeaderHeight = 0; for (int i = 0; i < count; i++) { View v = getChildAt(i); MyLayoutParams lp = (MyLayoutParams) v.getLayoutParams(); if (lp.sticky) { lp.scrollLimit = -v.getTop() + stickyHeaderHeight; stickyHeaderHeight += v.getHeight(); } else { lp.scrollLimit = Integer.MIN_VALUE; } } updateHeaderScroll(); } @Override protected LayoutParams generateDefaultLayoutParams() { return new MyLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { return new MyLayoutParams(lp.width, lp.height); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MyLayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof MyLayoutParams; } private static class MyLayoutParams extends LayoutParams { public final boolean sticky; public int scrollLimit; MyLayoutParams(int width, int height) { super(width, height); sticky = false; } MyLayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StickyScroller_Layout); sticky = a.getBoolean(R.styleable.StickyScroller_Layout_layout_sticky, false); a.recycle(); } } private interface MotionEventProxyMethod { boolean proxyEvent(ViewGroup view, MotionEvent event); } /** * Empty view which allows listening for 'Y' changes */ public static class EmptySpaceView extends View { private Runnable mOnYChangeCallback; private int mHeight = 0; public 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(); } } } }
src/com/android/launcher3/widget/picker/SearchAndRecommendationsView.javadeleted 100644 → 0 +0 −62 Original line number Diff line number Diff line /* * 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 android.util.AttributeSet; import android.view.MotionEvent; import android.widget.LinearLayout; /** * A {@link LinearLayout} container for holding search and widgets recommendation. * * <p>This class intercepts touch events and dispatch them to the right view. */ public class SearchAndRecommendationsView extends LinearLayout { private SearchAndRecommendationsScrollController mController; public SearchAndRecommendationsView(Context context) { this(context, /* attrs= */ null); } public SearchAndRecommendationsView(Context context, AttributeSet attrs) { this(context, attrs, /* defStyleAttr= */ 0); } public SearchAndRecommendationsView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, /* defStyleRes= */ 0); } public SearchAndRecommendationsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public void setSearchAndRecommendationScrollController( SearchAndRecommendationsScrollController controller) { mController = controller; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mController.onInterceptTouchEvent(event) || super.onInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { return mController.onTouchEvent(event) || super.onTouchEvent(event); } }