Merge "Renders widget recommendations" into sc-dev
This commit is contained in:
@@ -18,10 +18,11 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- The image of the widget. This view does not support padding. Any placement adjustment
|
||||
should be done using margins. -->
|
||||
should be done using margins.
|
||||
width & height are set at runtime after scaling the preview image. -->
|
||||
<com.android.launcher3.widget.WidgetImageView
|
||||
android:id="@+id/widget_preview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:importantForAccessibility="no"
|
||||
@@ -38,7 +39,7 @@
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="14sp" />
|
||||
android:textSize="@dimen/widget_cell_font_size" />
|
||||
|
||||
<!-- The original dimensions of the widget (can't be the same text as above due to different
|
||||
style. -->
|
||||
@@ -48,7 +49,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:textSize="14sp"
|
||||
android:textSize="@dimen/widget_cell_font_size"
|
||||
android:alpha="0.8" />
|
||||
|
||||
<TextView
|
||||
@@ -56,7 +57,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="14sp"
|
||||
android:textSize="@dimen/widget_cell_font_size"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
android:id="@+id/search_and_recommendations_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
<View
|
||||
android:id="@+id/collapse_handle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="2dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:elevation="2dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="?android:attr/textColorSecondary"/>
|
||||
<TextView
|
||||
@@ -36,4 +39,11 @@
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@string/widget_button_text"/>
|
||||
<include layout="@layout/widgets_search_bar"/>
|
||||
|
||||
<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="16dp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bg_widgets_searchbox">
|
||||
android:background="@drawable/bg_widgets_searchbox"
|
||||
android:elevation="2dp">
|
||||
|
||||
<com.android.launcher3.ExtendedEditText
|
||||
android:id="@+id/widgets_search_bar_edit_text"
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
<!-- Widget tray -->
|
||||
<dimen name="widget_cell_vertical_padding">8dp</dimen>
|
||||
<dimen name="widget_cell_horizontal_padding">16dp</dimen>
|
||||
<dimen name="widget_cell_font_size">14sp</dimen>
|
||||
|
||||
|
||||
<dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
|
||||
|
||||
@@ -74,6 +74,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
|
||||
protected int mPreviewHeight;
|
||||
protected int mPresetPreviewSize;
|
||||
private int mCellSize;
|
||||
private float mPreviewScale = 1f;
|
||||
|
||||
private WidgetImageView mWidgetImage;
|
||||
private TextView mWidgetName;
|
||||
@@ -254,8 +255,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
|
||||
}
|
||||
if (drawable != null) {
|
||||
LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
|
||||
layoutParams.width = drawable.getIntrinsicWidth();
|
||||
layoutParams.height = drawable.getIntrinsicHeight();
|
||||
layoutParams.width = (int) (drawable.getIntrinsicWidth() * mPreviewScale);
|
||||
layoutParams.height = (int) (drawable.getIntrinsicHeight() * mPreviewScale);
|
||||
mWidgetImage.setLayoutParams(layoutParams);
|
||||
|
||||
mWidgetImage.setDrawable(drawable, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
|
||||
@@ -305,10 +306,16 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
|
||||
|
||||
/** Sets the widget preview image size in number of cells. */
|
||||
public void setPreviewSize(int spanX, int spanY) {
|
||||
setPreviewSize(spanX, spanY, 1f);
|
||||
}
|
||||
|
||||
/** Sets the widget preview image size, in number of cells, and preview scale. */
|
||||
public void setPreviewSize(int spanX, int spanY, float previewScale) {
|
||||
int padding = 2 * getResources()
|
||||
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
|
||||
mPreviewWidth = mDeviceProfile.cellWidthPx * spanX + padding;
|
||||
mPreviewHeight = mDeviceProfile.cellHeightPx * spanY + padding;
|
||||
mPreviewScale = previewScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+110
-49
@@ -35,6 +35,7 @@ final class SearchAndRecommendationsScrollController implements
|
||||
private final SearchAndRecommendationViewHolder mViewHolder;
|
||||
private final WidgetsRecyclerView mPrimaryRecyclerView;
|
||||
private final WidgetsRecyclerView mSearchRecyclerView;
|
||||
private final int mTabsHeight;
|
||||
|
||||
// The following are only non null if mHasWorkProfile is true.
|
||||
@Nullable private final WidgetsRecyclerView mWorkRecyclerView;
|
||||
@@ -42,10 +43,28 @@ final class SearchAndRecommendationsScrollController implements
|
||||
@Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
|
||||
|
||||
private WidgetsRecyclerView mCurrentRecyclerView;
|
||||
private int mMaxCollapsibleHeight = 0;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
SearchAndRecommendationsScrollController(
|
||||
boolean hasWorkProfile,
|
||||
int tabsHeight,
|
||||
SearchAndRecommendationViewHolder viewHolder,
|
||||
WidgetsRecyclerView primaryRecyclerView,
|
||||
@Nullable WidgetsRecyclerView workRecyclerView,
|
||||
@@ -55,46 +74,63 @@ final class SearchAndRecommendationsScrollController implements
|
||||
mHasWorkProfile = hasWorkProfile;
|
||||
mViewHolder = viewHolder;
|
||||
mPrimaryRecyclerView = primaryRecyclerView;
|
||||
mCurrentRecyclerView = mPrimaryRecyclerView;
|
||||
mWorkRecyclerView = workRecyclerView;
|
||||
mSearchRecyclerView = searchRecyclerView;
|
||||
mPrimaryWorkTabsView = personalWorkTabsView;
|
||||
mPrimaryWorkViewPager = primaryWorkViewPager;
|
||||
mCurrentRecyclerView = mPrimaryRecyclerView;
|
||||
mTabsHeight = tabsHeight;
|
||||
}
|
||||
|
||||
/** Sets the current active {@link WidgetsRecyclerView}. */
|
||||
public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
|
||||
mCurrentRecyclerView = currentRecyclerView;
|
||||
mCurrentRecyclerView = currentRecyclerView;
|
||||
mViewHolder.mHeaderTitle.setTranslationY(0);
|
||||
mViewHolder.mRecommendedWidgetsTable.setTranslationY(0);
|
||||
mViewHolder.mSearchBar.setTranslationY(0);
|
||||
|
||||
if (mHasWorkProfile) {
|
||||
mPrimaryWorkTabsView.setTranslationY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
|
||||
*
|
||||
* @return {@code true} if margins or/and padding of views in the search and recommendations
|
||||
* container have been updated.
|
||||
*/
|
||||
public void updateMarginAndPadding() {
|
||||
// The maximum vertical distance, in pixels, until the last collapsible element is not
|
||||
// visible from the screen when the user scrolls down the recycler view.
|
||||
mMaxCollapsibleHeight = mViewHolder.mContainer.getPaddingTop()
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
|
||||
public boolean updateMarginAndPadding() {
|
||||
boolean hasMarginOrPaddingUpdated = false;
|
||||
mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
|
||||
mCollapsibleHeightForRecommendation =
|
||||
measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
|
||||
+ measureHeightWithVerticalMargins((View) mViewHolder.mSearchBar)
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
|
||||
|
||||
int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
|
||||
if (mHasWorkProfile) {
|
||||
mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
|
||||
+ measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
|
||||
// In a work profile setup, the full widget sheet contains the following views:
|
||||
// ------- -|
|
||||
// Widgets -|---> LinearLayout for search & recommendations
|
||||
// Search bar -|
|
||||
// Personal | Work
|
||||
// ------- (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.
|
||||
mPrimaryWorkTabsView.setPadding(
|
||||
mPrimaryWorkTabsView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mPrimaryWorkTabsView.getPaddingRight(),
|
||||
mPrimaryWorkTabsView.getPaddingBottom());
|
||||
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
|
||||
@@ -124,39 +160,52 @@ final class SearchAndRecommendationsScrollController implements
|
||||
//
|
||||
// When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
|
||||
// mMaxCollapsibleDistance should equal to the top container height.
|
||||
int tabsViewActualHeight = measureHeightWithVerticalMargins(mPrimaryWorkTabsView)
|
||||
- mPrimaryWorkTabsView.getPaddingTop();
|
||||
int topOffsetAfterAllViewsCollapsed =
|
||||
topContainerHeight + tabsViewActualHeight - mMaxCollapsibleHeight;
|
||||
topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
|
||||
|
||||
RelativeLayout.LayoutParams layoutParams =
|
||||
RelativeLayout.LayoutParams viewPagerLayoutParams =
|
||||
(RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
|
||||
layoutParams.setMargins(0, topOffsetAfterAllViewsCollapsed, 0, 0);
|
||||
mPrimaryWorkViewPager.setLayoutParams(layoutParams);
|
||||
mPrimaryWorkViewPager.requestLayout();
|
||||
if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
|
||||
viewPagerLayoutParams.topMargin = topOffsetAfterAllViewsCollapsed;
|
||||
mPrimaryWorkViewPager.setLayoutParams(viewPagerLayoutParams);
|
||||
hasMarginOrPaddingUpdated = true;
|
||||
}
|
||||
|
||||
mPrimaryRecyclerView.setPadding(
|
||||
mPrimaryRecyclerView.getPaddingLeft(),
|
||||
mMaxCollapsibleHeight,
|
||||
mPrimaryRecyclerView.getPaddingRight(),
|
||||
mPrimaryRecyclerView.getPaddingBottom());
|
||||
mWorkRecyclerView.setPadding(
|
||||
mWorkRecyclerView.getPaddingLeft(),
|
||||
mMaxCollapsibleHeight,
|
||||
mWorkRecyclerView.getPaddingRight(),
|
||||
mWorkRecyclerView.getPaddingBottom());
|
||||
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 {
|
||||
mPrimaryRecyclerView.setPadding(
|
||||
mPrimaryRecyclerView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mPrimaryRecyclerView.getPaddingRight(),
|
||||
mPrimaryRecyclerView.getPaddingBottom());
|
||||
if (mPrimaryRecyclerView.getPaddingTop() != topContainerHeight) {
|
||||
mPrimaryRecyclerView.setPadding(
|
||||
mPrimaryRecyclerView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mPrimaryRecyclerView.getPaddingRight(),
|
||||
mPrimaryRecyclerView.getPaddingBottom());
|
||||
hasMarginOrPaddingUpdated = true;
|
||||
}
|
||||
}
|
||||
mSearchRecyclerView.setPadding(
|
||||
mSearchRecyclerView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mSearchRecyclerView.getPaddingRight(),
|
||||
mSearchRecyclerView.getPaddingBottom());
|
||||
if (mSearchRecyclerView.getPaddingTop() != topContainerHeight) {
|
||||
mSearchRecyclerView.setPadding(
|
||||
mSearchRecyclerView.getPaddingLeft(),
|
||||
topContainerHeight,
|
||||
mSearchRecyclerView.getPaddingRight(),
|
||||
mSearchRecyclerView.getPaddingBottom());
|
||||
hasMarginOrPaddingUpdated = true;
|
||||
}
|
||||
return hasMarginOrPaddingUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,13 +217,22 @@ final class SearchAndRecommendationsScrollController implements
|
||||
// Always use the recycler view offset because fast scroller offset has a different scale.
|
||||
int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
|
||||
if (recyclerViewYOffset < 0) return;
|
||||
if (mMaxCollapsibleHeight > 0) {
|
||||
int yDisplacement = Math.max(-recyclerViewYOffset, -mMaxCollapsibleHeight);
|
||||
|
||||
if (mCollapsibleHeightForRecommendation > 0) {
|
||||
int yDisplacement = Math.max(-recyclerViewYOffset,
|
||||
-mCollapsibleHeightForRecommendation);
|
||||
mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
|
||||
mViewHolder.mSearchBar.setTranslationY(yDisplacement);
|
||||
if (mHasWorkProfile) {
|
||||
mPrimaryWorkTabsView.setTranslationY(yDisplacement);
|
||||
}
|
||||
mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
|
||||
}
|
||||
|
||||
if (mCollapsibleHeightForSearch > 0) {
|
||||
int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch);
|
||||
mViewHolder.mSearchBar.setTranslationY(searchYDisplacement);
|
||||
}
|
||||
|
||||
if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
|
||||
int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs);
|
||||
mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +247,9 @@ final class SearchAndRecommendationsScrollController implements
|
||||
|
||||
/** 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;
|
||||
}
|
||||
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
|
||||
return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
|
||||
+ marginLayoutParams.topMargin;
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.TextView;
|
||||
@@ -48,6 +49,7 @@ import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.views.RecyclerViewFastScroller;
|
||||
import com.android.launcher3.views.TopRoundedCornerView;
|
||||
import com.android.launcher3.widget.BaseWidgetSheet;
|
||||
@@ -55,6 +57,7 @@ import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListene
|
||||
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;
|
||||
|
||||
@@ -68,10 +71,15 @@ import java.util.function.Predicate;
|
||||
public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
implements Insettable, 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;
|
||||
private static final float VERTICAL_START_POSITION = 0.3f;
|
||||
// The widget recommendation table can easily take over the entire screen on devices with small
|
||||
// resolution or landscape on phone. This ratio defines the max percentage of content area that
|
||||
// the table can display.
|
||||
private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
|
||||
|
||||
private final Rect mInsets = new Rect();
|
||||
private final boolean mHasWorkProfile;
|
||||
@@ -81,10 +89,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mCurrentUser.equals(entry.mPkgItem.user);
|
||||
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
|
||||
mPrimaryWidgetsFilter.negate();
|
||||
private final int mTabsHeight;
|
||||
private final int mWidgetCellHorizontalPadding;
|
||||
|
||||
@Nullable private PersonalWorkPagedView mViewPager;
|
||||
private int mInitialTabsHeight = 0;
|
||||
private boolean mIsInSearchMode;
|
||||
private int mMaxSpansPerRow = 4;
|
||||
private View mTabsView;
|
||||
private TextView mNoWidgetsView;
|
||||
private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
|
||||
@@ -96,6 +106,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
|
||||
mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
|
||||
mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
|
||||
mTabsHeight = mHasWorkProfile
|
||||
? getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
|
||||
: 0;
|
||||
mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
|
||||
R.dimen.widget_cell_horizontal_padding);
|
||||
}
|
||||
|
||||
public WidgetsFullSheet(Context context, AttributeSet attrs) {
|
||||
@@ -140,6 +156,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
findViewById(R.id.search_and_recommendations_container));
|
||||
mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
|
||||
mHasWorkProfile,
|
||||
mTabsHeight,
|
||||
mSearchAndRecommendationViewHolder,
|
||||
findViewById(R.id.primary_widgets_list_view),
|
||||
mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
|
||||
@@ -150,6 +167,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
mNoWidgetsView = findViewById(R.id.no_widgets_text);
|
||||
|
||||
onRecommendedWidgetsBound();
|
||||
onWidgetsBound();
|
||||
|
||||
mSearchAndRecommendationViewHolder.mSearchBar.initialize(
|
||||
@@ -257,6 +275,22 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
if (updateMaxSpansPerRow()) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
|
||||
int widthUsed;
|
||||
if (mInsets.bottom > 0) {
|
||||
@@ -272,24 +306,29 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
widthUsed, heightMeasureSpec, heightUsed);
|
||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
|
||||
MeasureSpec.getSize(heightMeasureSpec));
|
||||
}
|
||||
|
||||
int paddingPx = 2 * getResources().getDimensionPixelOffset(
|
||||
R.dimen.widget_cell_horizontal_padding);
|
||||
int maxSpansPerRow = getMeasuredWidth() / (deviceProfile.cellWidthPx + paddingPx);
|
||||
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
maxSpansPerRow);
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
maxSpansPerRow);
|
||||
if (mHasWorkProfile) {
|
||||
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
maxSpansPerRow);
|
||||
/** Returns {@code true} if the max spans have been updated. */
|
||||
private boolean updateMaxSpansPerRow() {
|
||||
if (getMeasuredWidth() == 0) return false;
|
||||
|
||||
int previousMaxSpansPerRow = mMaxSpansPerRow;
|
||||
mMaxSpansPerRow = getMeasuredWidth()
|
||||
/ (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
|
||||
|
||||
if (previousMaxSpansPerRow != mMaxSpansPerRow) {
|
||||
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
mMaxSpansPerRow);
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
mMaxSpansPerRow);
|
||||
if (mHasWorkProfile) {
|
||||
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
|
||||
mMaxSpansPerRow);
|
||||
}
|
||||
onRecommendedWidgetsBound();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mInitialTabsHeight == 0 && mTabsView != null) {
|
||||
mInitialTabsHeight = measureHeightWithVerticalMargins(mTabsView);
|
||||
}
|
||||
|
||||
mSearchAndRecommendationsScrollController.updateMarginAndPadding();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -346,6 +385,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mViewPager.snapToPage(AdapterHolder.PRIMARY);
|
||||
}
|
||||
attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
|
||||
|
||||
mSearchAndRecommendationsScrollController.updateMarginAndPadding();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -357,6 +398,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
|
||||
mIsInSearchMode = isInSearchMode;
|
||||
mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
|
||||
.setVisibility(isInSearchMode ? GONE : VISIBLE);
|
||||
if (mHasWorkProfile) {
|
||||
mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
|
||||
mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
|
||||
@@ -374,6 +417,25 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecommendedWidgetsBound() {
|
||||
List<WidgetItem> recommendedWidgets =
|
||||
mLauncher.getPopupDataProvider().getRecommendedWidgets();
|
||||
WidgetsRecommendationTableLayout table =
|
||||
mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
|
||||
if (recommendedWidgets.size() > 0) {
|
||||
float maxTableHeight =
|
||||
(mLauncher.getDeviceProfile().heightPx - mTabsHeight - getHeaderViewHeight())
|
||||
* RECOMMENDATION_TABLE_HEIGHT_RATIO;
|
||||
List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
|
||||
WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
|
||||
mMaxSpansPerRow);
|
||||
table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
|
||||
} else {
|
||||
table.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void open(boolean animate) {
|
||||
if (animate) {
|
||||
if (getPopupContainer().getInsets().bottom > 0) {
|
||||
@@ -534,20 +596,29 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mWidgetsRecyclerView.setEdgeEffectFactory(
|
||||
((TopRoundedCornerView) mContent).createEdgeEffectFactory());
|
||||
mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
|
||||
mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
|
||||
}
|
||||
}
|
||||
|
||||
final class SearchAndRecommendationViewHolder {
|
||||
final View mContainer;
|
||||
final ViewGroup mContainer;
|
||||
final View mCollapseHandle;
|
||||
final WidgetsSearchBar mSearchBar;
|
||||
final TextView mHeaderTitle;
|
||||
final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
|
||||
|
||||
SearchAndRecommendationViewHolder(View searchAndRecommendationContainer) {
|
||||
SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
|
||||
mContainer = searchAndRecommendationContainer;
|
||||
mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
import com.android.launcher3.widget.WidgetImageView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** A {@link TableLayout} for showing recommended widgets. */
|
||||
public final class WidgetsRecommendationTableLayout extends TableLayout {
|
||||
private static final float SCALE_DOWN_RATIO = 0.9f;
|
||||
private final DeviceProfile mDeviceProfile;
|
||||
private final float mWidgetCellTextViewsHeight;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
|
||||
// There are 1 row for title, 1 row for dimension and 2 rows for description.
|
||||
mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
|
||||
}
|
||||
|
||||
/** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
|
||||
public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
|
||||
mWidgetCellOnLongClickListener = onLongClickListener;
|
||||
}
|
||||
|
||||
/** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */
|
||||
public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) {
|
||||
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}.
|
||||
*
|
||||
* <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
|
||||
* last row from the {@code recommendedWidgets} until it fits or only one row left. If the only
|
||||
* row still doesn't fit, we scale down the preview image.
|
||||
*/
|
||||
public void setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
|
||||
float recommendationTableMaxHeight) {
|
||||
mRecommendationTableMaxHeight = recommendationTableMaxHeight;
|
||||
RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
|
||||
recommendedWidgets);
|
||||
bindData(data);
|
||||
}
|
||||
|
||||
private void bindData(RecommendationTableData data) {
|
||||
if (data.mRecommendationTable.size() == 0) {
|
||||
setVisibility(GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
removeAllViews();
|
||||
|
||||
for (int i = 0; i < data.mRecommendationTable.size(); i++) {
|
||||
List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
|
||||
TableRow tableRow = new TableRow(getContext());
|
||||
tableRow.setGravity(Gravity.TOP);
|
||||
|
||||
for (WidgetItem widgetItem : widgetItems) {
|
||||
WidgetCell widgetCell = addItemCell(tableRow);
|
||||
widgetCell.setPreviewSize(widgetItem.spanX, widgetItem.spanY, data.mPreviewScale);
|
||||
widgetCell.applyFromCellItem(widgetItem,
|
||||
LauncherAppState.getInstance(getContext()).getWidgetCache());
|
||||
widgetCell.ensurePreview();
|
||||
}
|
||||
addView(tableRow);
|
||||
}
|
||||
setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private WidgetCell addItemCell(ViewGroup parent) {
|
||||
WidgetCell widget = (WidgetCell) LayoutInflater.from(
|
||||
getContext()).inflate(R.layout.widget_cell, parent, false);
|
||||
|
||||
widget.setOnTouchListener(mWidgetCellOnTouchListener);
|
||||
WidgetImageView preview = widget.findViewById(R.id.widget_preview);
|
||||
preview.setOnClickListener(mWidgetCellOnClickListener);
|
||||
preview.setOnLongClickListener(mWidgetCellOnLongClickListener);
|
||||
widget.setAnimatePreview(false);
|
||||
|
||||
parent.addView(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
private RecommendationTableData fitRecommendedWidgetsToTableSpace(
|
||||
float previewScale,
|
||||
List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
|
||||
// A naive estimation of the widgets recommendation table height without inflation.
|
||||
float totalHeight = 0;
|
||||
for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
|
||||
List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
|
||||
float rowHeight = 0;
|
||||
for (int j = 0; j < widgetItems.size(); j++) {
|
||||
float previewHeight = widgetItems.get(j).spanY * mDeviceProfile.allAppsCellHeightPx
|
||||
* previewScale;
|
||||
rowHeight = Math.max(rowHeight, previewHeight + mWidgetCellTextViewsHeight);
|
||||
}
|
||||
totalHeight += rowHeight;
|
||||
}
|
||||
|
||||
if (totalHeight < mRecommendationTableMaxHeight) {
|
||||
return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
|
||||
}
|
||||
|
||||
if (recommendedWidgetsInTable.size() > 1) {
|
||||
// We don't want to scale down widgets preview unless we really need to. Reduce the
|
||||
// num of row by 1 to see if it fits.
|
||||
return fitRecommendedWidgetsToTableSpace(
|
||||
previewScale,
|
||||
recommendedWidgetsInTable.subList(/* fromIndex= */0,
|
||||
/* toIndex= */recommendedWidgetsInTable.size() - 1));
|
||||
}
|
||||
|
||||
float nextPreviewScale = previewScale * SCALE_DOWN_RATIO;
|
||||
return fitRecommendedWidgetsToTableSpace(nextPreviewScale, recommendedWidgetsInTable);
|
||||
}
|
||||
|
||||
/** Data class for the widgets recommendation table and widgets preview scaling. */
|
||||
private class RecommendationTableData {
|
||||
private final List<ArrayList<WidgetItem>> mRecommendationTable;
|
||||
private final float mPreviewScale;
|
||||
|
||||
RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
|
||||
float previewScale) {
|
||||
mRecommendationTable = recommendationTable;
|
||||
mPreviewScale = previewScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import androidx.test.uiautomator.Until;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* All widgets container.
|
||||
@@ -106,7 +105,8 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
|
||||
final Point displaySize = mLauncher.getRealDisplaySize();
|
||||
|
||||
final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer();
|
||||
Rect headerRect = new Rect();
|
||||
final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer(headerRect);
|
||||
mLauncher.assertTrue("Can't locate widgets list for the test app: "
|
||||
+ mLauncher.getLauncherPackageName(),
|
||||
widgetsContainer != null);
|
||||
@@ -132,7 +132,12 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
|
||||
mLauncher.assertTrue("Too many attempts", ++i <= 40);
|
||||
final int scroll = getWidgetsScroll();
|
||||
mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, tableRows, 0);
|
||||
mLauncher.scroll(
|
||||
fullWidgetsPicker,
|
||||
Direction.DOWN,
|
||||
headerRect,
|
||||
10,
|
||||
true);
|
||||
final int newScroll = getWidgetsScroll();
|
||||
mLauncher.assertTrue(
|
||||
"Scrolled in a wrong direction in Widgets: from " + scroll + " to "
|
||||
@@ -143,7 +148,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
}
|
||||
|
||||
/** Finds the widgets list of this test app from the collapsed full widgets picker. */
|
||||
private UiObject2 findTestAppWidgetsTableContainer() {
|
||||
private UiObject2 findTestAppWidgetsTableContainer(Rect outHeaderRect) {
|
||||
final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
|
||||
"widgets_list_header");
|
||||
final BySelector targetAppSelector = By.clazz("android.widget.TextView").text(
|
||||
@@ -156,6 +161,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
UiObject2 fullWidgetsPicker = verifyActiveContainer();
|
||||
|
||||
UiObject2 header = fullWidgetsPicker.findObject(headerSelector);
|
||||
outHeaderRect.set(0, 0, 0, header.getVisibleBounds().height());
|
||||
mLauncher.assertTrue("Can't find a widget header", header != null);
|
||||
|
||||
// Look for a header that has the test app name.
|
||||
@@ -179,11 +185,14 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
if (widgetsContainer != null) {
|
||||
return widgetsContainer;
|
||||
}
|
||||
mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, List.of(headerTitle), 0);
|
||||
} else {
|
||||
mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, fullWidgetsPicker.getChildren(),
|
||||
0);
|
||||
|
||||
}
|
||||
mLauncher.scroll(
|
||||
fullWidgetsPicker,
|
||||
Direction.DOWN,
|
||||
outHeaderRect,
|
||||
/* steps= */ 10,
|
||||
/* slowDown= */ true);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user