1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.widget; 18 19 import static com.android.launcher3.Utilities.ATLEAST_R; 20 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 21 import static com.android.launcher3.widget.BaseWidgetSheet.MAX_WIDTH_SCALE_FOR_LARGER_SCREEN; 22 23 import android.animation.PropertyValuesHolder; 24 import android.annotation.SuppressLint; 25 import android.content.Context; 26 import android.graphics.Insets; 27 import android.graphics.Rect; 28 import android.util.AttributeSet; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.ViewParent; 33 import android.view.WindowInsets; 34 import android.widget.ScrollView; 35 36 import com.android.launcher3.DeviceProfile; 37 import com.android.launcher3.R; 38 import com.android.launcher3.dragndrop.AddItemActivity; 39 import com.android.launcher3.views.AbstractSlideInView; 40 41 /** 42 * Bottom sheet for the pin widget. 43 */ 44 public class AddItemWidgetsBottomSheet extends AbstractSlideInView<AddItemActivity> implements 45 View.OnApplyWindowInsetsListener { 46 47 private static final int DEFAULT_CLOSE_DURATION = 200; 48 49 private final Rect mInsets; 50 private ScrollView mWidgetPreviewScrollView; 51 52 private int mContentHorizontalMarginInPx; 53 AddItemWidgetsBottomSheet(Context context, AttributeSet attrs)54 public AddItemWidgetsBottomSheet(Context context, AttributeSet attrs) { 55 this(context, attrs, 0); 56 } 57 AddItemWidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr)58 public AddItemWidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) { 59 super(context, attrs, defStyleAttr); 60 mInsets = new Rect(); 61 mContentHorizontalMarginInPx = getResources().getDimensionPixelSize( 62 R.dimen.widget_list_horizontal_margin); 63 } 64 65 /** 66 * Attaches to activity container and animates open the bottom sheet. 67 */ show()68 public void show() { 69 ViewParent parent = getParent(); 70 if (parent instanceof ViewGroup) { 71 ((ViewGroup) parent).removeView(this); 72 } 73 attachToContainer(); 74 setOnApplyWindowInsetsListener(this); 75 animateOpen(); 76 } 77 78 @Override onControllerInterceptTouchEvent(MotionEvent ev)79 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 80 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 81 mNoIntercept = false; 82 // Suppress drag to dismiss gesture if the scroll view is being scrolled. 83 if (getPopupContainer().isEventOverView(mWidgetPreviewScrollView, ev) 84 && mWidgetPreviewScrollView.getScrollY() > 0) { 85 mNoIntercept = true; 86 } 87 } 88 return super.onControllerInterceptTouchEvent(ev); 89 } 90 91 @Override onLayout(boolean changed, int l, int t, int r, int b)92 protected void onLayout(boolean changed, int l, int t, int r, int b) { 93 int width = r - l; 94 int height = b - t; 95 96 // Lay out content as center bottom aligned. 97 int contentWidth = mContent.getMeasuredWidth(); 98 int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left; 99 mContent.layout(contentLeft, height - mContent.getMeasuredHeight(), 100 contentLeft + contentWidth, height); 101 102 setTranslationShift(mTranslationShift); 103 } 104 105 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)106 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 107 DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); 108 int widthUsed; 109 if (mInsets.bottom > 0) { 110 widthUsed = mInsets.left + mInsets.right; 111 } else { 112 Rect padding = deviceProfile.workspacePadding; 113 widthUsed = Math.max(padding.left + padding.right, 114 2 * (mInsets.left + mInsets.right)); 115 } 116 117 if (deviceProfile.isTablet || deviceProfile.isTwoPanels) { 118 // In large screen devices, we restrict the width of the widgets picker to show part of 119 // the home screen. Let's ensure the minimum width used is at least the minimum width 120 // that isn't taken by the widgets picker. 121 int minUsedWidth = (int) (deviceProfile.availableWidthPx 122 * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN)); 123 widthUsed = Math.max(widthUsed, minUsedWidth); 124 } 125 126 int heightUsed = mInsets.top + deviceProfile.edgeMarginPx; 127 measureChildWithMargins(mContent, widthMeasureSpec, 128 widthUsed, heightMeasureSpec, heightUsed); 129 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 130 MeasureSpec.getSize(heightMeasureSpec)); 131 } 132 133 @Override onFinishInflate()134 protected void onFinishInflate() { 135 super.onFinishInflate(); 136 mContent = findViewById(R.id.add_item_bottom_sheet_content); 137 mWidgetPreviewScrollView = findViewById(R.id.widget_preview_scroll_view); 138 } 139 animateOpen()140 private void animateOpen() { 141 if (mIsOpen || mOpenCloseAnimator.isRunning()) { 142 return; 143 } 144 mIsOpen = true; 145 mOpenCloseAnimator.setValues( 146 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); 147 mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN); 148 mOpenCloseAnimator.start(); 149 } 150 151 @Override handleClose(boolean animate)152 protected void handleClose(boolean animate) { 153 handleClose(animate, DEFAULT_CLOSE_DURATION); 154 } 155 156 @Override isOfType(@loatingViewType int type)157 protected boolean isOfType(@FloatingViewType int type) { 158 return (type & TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP) != 0; 159 } 160 161 @Override getScrimColor(Context context)162 protected int getScrimColor(Context context) { 163 return context.getResources().getColor(R.color.widgets_picker_scrim); 164 } 165 166 @SuppressLint("NewApi") // Already added API check. 167 @Override onApplyWindowInsets(View view, WindowInsets windowInsets)168 public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) { 169 if (ATLEAST_R) { 170 Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars()); 171 mInsets.set(insets.left, insets.top, insets.right, insets.bottom); 172 } else { 173 mInsets.set(windowInsets.getSystemWindowInsetLeft(), 174 windowInsets.getSystemWindowInsetTop(), 175 windowInsets.getSystemWindowInsetRight(), 176 windowInsets.getSystemWindowInsetBottom()); 177 } 178 mContent.setPadding(mContent.getPaddingStart(), 179 mContent.getPaddingTop(), mContent.getPaddingEnd(), mInsets.bottom); 180 181 int contentHorizontalMarginInPx = getResources().getDimensionPixelSize( 182 R.dimen.widget_list_horizontal_margin); 183 if (contentHorizontalMarginInPx != mContentHorizontalMarginInPx) { 184 setContentHorizontalMargin(findViewById(R.id.widget_appName), 185 contentHorizontalMarginInPx); 186 setContentHorizontalMargin(findViewById(R.id.widget_drag_instruction), 187 contentHorizontalMarginInPx); 188 setContentHorizontalMargin(findViewById(R.id.widget_cell), contentHorizontalMarginInPx); 189 setContentHorizontalMargin(findViewById(R.id.actions_container), 190 contentHorizontalMarginInPx); 191 mContentHorizontalMarginInPx = contentHorizontalMarginInPx; 192 } 193 return windowInsets; 194 } 195 setContentHorizontalMargin(View view, int contentHorizontalMargin)196 private static void setContentHorizontalMargin(View view, int contentHorizontalMargin) { 197 ViewGroup.MarginLayoutParams layoutParams = 198 ((ViewGroup.MarginLayoutParams) view.getLayoutParams()); 199 layoutParams.setMarginStart(contentHorizontalMargin); 200 layoutParams.setMarginEnd(contentHorizontalMargin); 201 } 202 } 203