Merge "Generalizing the PredicitonScroll view so that in can be used in all-apps" into tm-qpr-dev

This commit is contained in:
Sunny Goyal
2022-07-12 21:33:17 +00:00
committed by Android (Google) Code Review
8 changed files with 385 additions and 397 deletions
+5 -4
View File
@@ -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"
@@ -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>
@@ -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"
@@ -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>
@@ -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"
@@ -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"
@@ -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>
@@ -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>
+4
View File
@@ -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" />
@@ -0,0 +1,327 @@
/*
* 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.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.LinearLayout;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.R;
/**
* 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.
*/
public class StickyHeaderLayout extends LinearLayout implements
RecyclerView.OnChildAttachStateChangeListener {
private static final FloatProperty<StickyHeaderLayout> SCROLL_OFFSET =
new FloatProperty<StickyHeaderLayout>("scrollAnimOffset") {
@Override
public void setValue(StickyHeaderLayout view, float offset) {
view.mScrollOffset = offset;
view.updateHeaderScroll();
}
@Override
public Float get(StickyHeaderLayout view) {
return view.mScrollOffset;
}
};
private static final MotionEventProxyMethod INTERCEPT_PROXY = ViewGroup::onInterceptTouchEvent;
private static final MotionEventProxyMethod TOUCH_PROXY = ViewGroup::onTouchEvent;
private RecyclerView mCurrentRecyclerView;
private EmptySpaceView mCurrentEmptySpaceView;
private float mLastScroll = 0;
private float mScrollOffset = 0;
private Animator mOffsetAnimator;
private boolean mShouldForwardToRecyclerView = false;
private int mHeaderHeight;
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);
}
public StickyHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* Sets the recycler view, this sticky header should track
*/
public void setCurrentRecyclerView(RecyclerView currentRecyclerView) {
boolean animateReset = mCurrentRecyclerView != null;
if (mCurrentRecyclerView != null) {
mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this);
}
mCurrentRecyclerView = currentRecyclerView;
mCurrentRecyclerView.addOnChildAttachStateChangeListener(this);
findCurrentEmptyView();
reset(animateReset);
}
public int getHeaderHeight() {
return mHeaderHeight;
}
private void updateHeaderScroll() {
mLastScroll = getCurrentScroll();
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));
}
}
private float getCurrentScroll() {
return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeaderHeight = getMeasuredHeight();
if (mCurrentEmptySpaceView != null) {
mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight);
}
}
/** Resets any previous view translation. */
public void reset(boolean animate) {
if (mOffsetAnimator != null) {
mOffsetAnimator.cancel();
mOffsetAnimator = null;
}
mScrollOffset = 0;
if (!animate) {
updateHeaderScroll();
} else {
float startValue = mLastScroll - getCurrentScroll();
mOffsetAnimator = ObjectAnimator.ofFloat(this, SCROLL_OFFSET, startValue, 0);
mOffsetAnimator.addListener(forEndCallback(() -> mOffsetAnimator = null));
mOffsetAnimator.start();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY))
|| super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY)
|| super.onTouchEvent(event);
}
private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) {
float dx = mCurrentRecyclerView.getLeft() - getLeft();
float dy = mCurrentRecyclerView.getTop() - getTop();
event.offsetLocation(dx, dy);
try {
return method.proxyEvent(mCurrentRecyclerView, event);
} finally {
event.offsetLocation(-dx, -dy);
}
}
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
if (view instanceof EmptySpaceView) {
findCurrentEmptyView();
}
}
@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;
}
}
}
@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();
}
}
}
}
@@ -1,223 +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 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;
import android.widget.TextView;
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.
*/
final class SearchAndRecommendationsScrollController implements
RecyclerView.OnChildAttachStateChangeListener {
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 EmptySpaceView mCurrentEmptySpaceView;
private float mLastScroll = 0;
private float mScrollOffset = 0;
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);
mContainer.setSearchAndRecommendationScrollController(this);
}
public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
boolean animateReset = mCurrentRecyclerView != null;
if (mCurrentRecyclerView != null) {
mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this);
}
mCurrentRecyclerView = currentRecyclerView;
mCurrentRecyclerView.addOnChildAttachStateChangeListener(this);
findCurrentEmptyView();
reset(animateReset);
}
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 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;
}
if (mCurrentEmptySpaceView != null
&& mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) {
hasSizeUpdated = true;
}
return hasSizeUpdated;
}
/** Resets any previous view translation. */
public void reset(boolean animate) {
if (mOffsetAnimator != null) {
mOffsetAnimator.cancel();
mOffsetAnimator = null;
}
mScrollOffset = 0;
if (!animate) {
updateHeaderScroll();
} else {
float startValue = mLastScroll - getCurrentScroll();
mOffsetAnimator = ObjectAnimator.ofFloat(this, SCROLL_OFFSET, startValue, 0);
mOffsetAnimator.addListener(forEndCallback(() -> mOffsetAnimator = null));
mOffsetAnimator.start();
}
}
/**
* Returns {@code true} if a touch event should be intercepted by this controller.
*/
public boolean onInterceptTouchEvent(MotionEvent event) {
return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY));
}
/**
* Returns {@code true} if this controller has intercepted and consumed a touch event.
*/
public boolean onTouchEvent(MotionEvent event) {
return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY);
}
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);
}
}
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
if (view instanceof EmptySpaceView) {
findCurrentEmptyView();
}
}
@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);
}
}
@@ -1,62 +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 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);
}
}
@@ -62,11 +62,13 @@ import com.android.launcher3.pm.UserCache;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.RecyclerViewFastScroller;
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.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;
@@ -161,7 +163,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet
private boolean mIsNoWidgetsViewNeeded;
private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS;
private TextView mNoWidgetsView;
private SearchAndRecommendationsScrollController mSearchScrollController;
private StickyHeaderLayout mSearchScrollView;
private WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
private View mTabBar;
private View mSearchBarContainer;
private WidgetsSearchBar mSearchBar;
private TextView mHeaderTitle;
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -214,17 +222,23 @@ public class WidgetsFullSheet extends BaseWidgetSheet
}
mNoWidgetsView = findViewById(R.id.no_widgets_text);
mSearchScrollController = new SearchAndRecommendationsScrollController(
findViewById(R.id.search_and_recommendations_container));
mSearchScrollController.setCurrentRecyclerView(
findViewById(R.id.primary_widgets_list_view));
mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
mSearchScrollView = findViewById(R.id.search_and_recommendations_container);
mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view));
mRecommendedWidgetsTable = mSearchScrollView.findViewById(R.id.recommended_widget_table);
mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
mTabBar = mSearchScrollView.findViewById(R.id.tabs);
mSearchBarContainer = mSearchScrollView.findViewById(R.id.search_bar_container);
mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar);
mHeaderTitle = mSearchScrollView.findViewById(R.id.title);
onRecommendedWidgetsBound();
onWidgetsBound();
mSearchScrollController.mSearchBar.initialize(
mSearchBar.initialize(
mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
setUpEducationViewsIfNeeded();
@@ -258,7 +272,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
reset();
resetExpandedHeaders();
mCurrentWidgetsRecyclerView = recyclerView;
mSearchScrollController.setCurrentRecyclerView(recyclerView);
mSearchScrollView.setCurrentRecyclerView(recyclerView);
}
}
@@ -285,7 +299,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
}
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
mSearchScrollController.reset(/* animate= */ true);
mSearchScrollView.reset(/* animate= */ true);
}
@VisibleForTesting
@@ -355,8 +369,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
@Override
protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
setContentViewChildHorizontalMargin(mSearchScrollController.mContainer,
contentHorizontalMarginInPx);
setContentViewChildHorizontalMargin(mSearchScrollView, contentHorizontalMarginInPx);
if (mViewPager == null) {
setContentViewChildHorizontalPadding(
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView,
@@ -390,16 +403,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
if (mSearchScrollController.updateHeaderHeight()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
if (updateMaxSpansPerRow()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
if (mSearchScrollController.updateHeaderHeight()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
@@ -460,7 +465,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
if (mHasWorkProfile) {
mViewPager.setVisibility(VISIBLE);
mSearchScrollController.mTabBar.setVisibility(VISIBLE);
mTabBar.setVisibility(VISIBLE);
AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
onActivePageChanged(mViewPager.getCurrentPage());
@@ -508,10 +513,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet
private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
mIsInSearchMode = isInSearchMode;
if (isInSearchMode) {
mSearchScrollController.mRecommendedWidgetsTable.setVisibility(GONE);
mRecommendedWidgetsTable.setVisibility(GONE);
if (mHasWorkProfile) {
mViewPager.setVisibility(GONE);
mSearchScrollController.mTabBar.setVisibility(GONE);
mTabBar.setVisibility(GONE);
} else {
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.setVisibility(GONE);
}
@@ -539,7 +544,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet
}
List<WidgetItem> recommendedWidgets =
mActivityContext.getPopupDataProvider().getRecommendedWidgets();
WidgetsRecommendationTableLayout table = mSearchScrollController.mRecommendedWidgetsTable;
if (recommendedWidgets.size() > 0) {
float noWidgetsViewHeight = 0;
if (mIsNoWidgetsViewNeeded) {
@@ -562,9 +566,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet
List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
WidgetsTableUtils.groupWidgetItemsIntoTableWithoutReordering(
recommendedWidgets, mMaxSpansPerRow);
table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
mRecommendedWidgetsTable.setRecommendedWidgets(
recommendedWidgetsInTable, maxTableHeight);
} else {
table.setVisibility(GONE);
mRecommendedWidgetsTable.setVisibility(GONE);
}
}
@@ -619,10 +624,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
}
if (mSearchScrollController.mSearchBar.isSearchBarFocused()
&& !getPopupContainer().isEventOverView(
mSearchScrollController.mSearchBarContainer, ev)) {
mSearchScrollController.mSearchBar.clearSearchBarFocus();
if (mSearchBar.isSearchBarFocused()
&& !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) {
mSearchBar.clearSearchBarFocus();
}
}
return super.onControllerInterceptTouchEvent(ev);
@@ -663,8 +667,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
@Override
public int getHeaderViewHeight() {
return measureHeightWithVerticalMargins(mSearchScrollController.mHeaderTitle)
+ measureHeightWithVerticalMargins(mSearchScrollController.mSearchBarContainer);
return measureHeightWithVerticalMargins(mHeaderTitle)
+ measureHeightWithVerticalMargins(mSearchBarContainer);
}
/** private the height, in pixel, + the vertical margins of a given view. */
@@ -681,14 +685,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mIsInSearchMode) {
mSearchScrollController.mSearchBar.reset();
mSearchBar.reset();
}
}
@Override
public boolean onBackPressed() {
if (mIsInSearchMode) {
mSearchScrollController.mSearchBar.reset();
mSearchBar.reset();
return true;
}
return super.onBackPressed();
@@ -701,10 +705,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet
}
@Nullable private View getViewToShowEducationTip() {
if (mSearchScrollController.mRecommendedWidgetsTable.getVisibility() == VISIBLE
&& mSearchScrollController.mRecommendedWidgetsTable.getChildCount() > 0) {
return ((ViewGroup) mSearchScrollController.mRecommendedWidgetsTable.getChildAt(0))
.getChildAt(0);
if (mRecommendedWidgetsTable.getVisibility() == VISIBLE
&& mRecommendedWidgetsTable.getChildCount() > 0) {
return ((ViewGroup) mRecommendedWidgetsTable.getChildAt(0)).getChildAt(0);
}
AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
@@ -801,7 +804,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
}
private int getEmptySpaceHeight() {
return mSearchScrollController.getHeaderHeight();
return mSearchScrollView.getHeaderHeight();
}
void setup(WidgetsRecyclerView recyclerView) {
@@ -15,16 +15,12 @@
*/
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.views.StickyHeaderLayout.EmptySpaceView;
import com.android.launcher3.widget.model.WidgetListSpaceEntry;
import java.util.List;
@@ -52,64 +48,4 @@ public class WidgetsSpaceViewHolderBinder
@ListPosition int position, List<Object> payloads) {
((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();
}
}
}
}