Files
Lawnchair/src/com/android/launcher3/widget/WidgetsBottomSheet.java
T
Shamali P 933a05a2be Align items in the center within each table row within suggestions.
When differently-sized widgets OR widgets of same size but not filling
space in previews are shown side by side, the top alignment looks odd.

Besides, in the updated UX, we want them to be center aligned.

Bug: 318410881
Bug: 319152349
Flag: N/A
Test: Screenshot update in cl chain.
Change-Id: I52cd7d6a1b5397a3c4c9dea8b072792b3211a8d3
2024-02-15 18:32:21 +00:00

290 lines
11 KiB
Java

/*
* Copyright (C) 2017 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;
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.IntProperty;
import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import androidx.annotation.Px;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.util.WidgetsTableUtils;
import java.util.List;
/**
* Bottom sheet for the "Widgets" system shortcut in the long-press popup.
*/
public class WidgetsBottomSheet extends BaseWidgetSheet {
private static final String TAG = "WidgetsBottomSheet";
private static final IntProperty<View> PADDING_BOTTOM =
new IntProperty<View>("paddingBottom") {
@Override
public void setValue(View view, int paddingBottom) {
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
view.getPaddingRight(), paddingBottom);
}
@Override
public Integer get(View view) {
return view.getPaddingBottom();
}
};
private static final int DEFAULT_CLOSE_DURATION = 200;
private static final long EDUCATION_TIP_DELAY_MS = 300;
private ItemInfo mOriginalItemInfo;
@Px private int mMaxHorizontalSpan;
private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (hasSeenEducationTip()) {
removeOnLayoutChangeListener(this);
return;
}
// Widgets are loaded asynchronously, We are adding a delay because we only want
// to show the tip when the widget preview has finished loading and rendering in
// this view.
removeCallbacks(mShowEducationTipTask);
postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
}
};
private final Runnable mShowEducationTipTask = () -> {
if (hasSeenEducationTip()) {
removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
return;
}
View viewForTip = ((ViewGroup) ((TableLayout) findViewById(R.id.widgets_table))
.getChildAt(0)).getChildAt(0);
if (showEducationTipOnViewIfPossible(viewForTip) != null) {
removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
}
};
public WidgetsBottomSheet(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
if (!hasSeenEducationTip()) {
addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.widgets_bottom_sheet);
setContentBackgroundWithParent(
getContext().getDrawable(R.drawable.bg_rounded_corner_bottom_sheet), mContent);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
if (updateMaxSpansPerRow()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
/** Returns {@code true} if the max spans have been updated. */
private boolean updateMaxSpansPerRow() {
if (getMeasuredWidth() == 0) return false;
@Px int maxHorizontalSpan = mContent.getMeasuredWidth() - (2 * mContentHorizontalMargin);
if (mMaxHorizontalSpan != maxHorizontalSpan) {
// Ensure the table layout is showing widgets in the right column after measure.
mMaxHorizontalSpan = maxHorizontalSpan;
onWidgetsBound();
return true;
}
return false;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
// Content is laid out as center bottom aligned.
int contentWidth = mContent.getMeasuredWidth();
int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
contentLeft + contentWidth, height);
setTranslationShift(mTranslationShift);
ScrollView widgetsTableScrollView = findViewById(R.id.widgets_table_scroll_view);
TableLayout widgetsTable = findViewById(R.id.widgets_table);
if (widgetsTable.getMeasuredHeight() > widgetsTableScrollView.getMeasuredHeight()) {
findViewById(R.id.collapse_handle).setVisibility(VISIBLE);
}
}
public void populateAndShow(ItemInfo itemInfo) {
mOriginalItemInfo = itemInfo;
((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
onWidgetsBound();
attachToContainer();
mIsOpen = false;
animateOpen();
}
@Override
public void onWidgetsBound() {
List<WidgetItem> widgets = mActivityContext.getPopupDataProvider().getWidgetsForPackageUser(
new PackageUserKey(
mOriginalItemInfo.getTargetComponent().getPackageName(),
mOriginalItemInfo.user));
TableLayout widgetsTable = findViewById(R.id.widgets_table);
widgetsTable.removeAllViews();
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgets, mActivityContext,
mActivityContext.getDeviceProfile(), mMaxHorizontalSpan,
mWidgetCellHorizontalPadding)
.forEach(row -> {
TableRow tableRow = new TableRow(getContext());
if (enableCategorizedWidgetSuggestions()) {
// Vertically center align items, so that even if they don't fill bounds,
// they can look organized when placed together in a row.
tableRow.setGravity(Gravity.CENTER_VERTICAL);
} else {
tableRow.setGravity(Gravity.TOP);
}
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
widget.applyFromCellItem(widgetItem);
});
widgetsTable.addView(tableRow);
});
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = false;
ScrollView scrollView = findViewById(R.id.widgets_table_scroll_view);
if (getPopupContainer().isEventOverView(scrollView, ev)
&& scrollView.getScrollY() > 0) {
mNoIntercept = true;
}
}
return super.onControllerInterceptTouchEvent(ev);
}
protected WidgetCell addItemCell(ViewGroup parent) {
WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
.inflate(R.layout.widget_cell, parent, false);
View previewContainer = widget.findViewById(R.id.widget_preview_container);
previewContainer.setOnClickListener(this);
previewContainer.setOnLongClickListener(this);
widget.setAnimatePreview(false);
widget.setSourceContainer(CONTAINER_BOTTOM_WIDGETS_TRAY);
parent.addView(widget);
return widget;
}
private void animateOpen() {
if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
return;
}
mIsOpen = true;
setupNavBarColor();
setUpDefaultOpenAnimation().start();
}
@Override
protected void handleClose(boolean animate) {
handleClose(animate, DEFAULT_CLOSE_DURATION);
}
@Override
protected boolean isOfType(@FloatingViewType int type) {
return (type & TYPE_WIDGETS_BOTTOM_SHEET) != 0;
}
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight);
View widgetsTable = findViewById(R.id.widgets_table);
widgetsTable.setPadding(
widgetsTable.getPaddingLeft(),
widgetsTable.getPaddingTop(),
widgetsTable.getPaddingRight(),
bottomPadding);
if (bottomPadding > 0) {
setupNavBarColor();
} else {
clearNavBarColor();
}
}
@Override
protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
ViewGroup.MarginLayoutParams layoutParams =
((ViewGroup.MarginLayoutParams) findViewById(R.id.widgets_table).getLayoutParams());
layoutParams.setMarginStart(contentHorizontalMarginInPx);
layoutParams.setMarginEnd(contentHorizontalMarginInPx);
}
@Override
protected Pair<View, String> getAccessibilityTarget() {
return Pair.create(findViewById(R.id.title), getContext().getString(
mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
}
@Override
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) {
target.setInt(this, PADDING_BOTTOM, (int) (distanceToMove + mInsets.bottom), interpolator);
}
}