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 package com.android.launcher3.widget; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.animation.PropertyValuesHolder; 21 import android.content.Context; 22 import android.graphics.Rect; 23 import android.util.AttributeSet; 24 import android.util.Pair; 25 import android.view.LayoutInflater; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.animation.AnimationUtils; 29 30 import com.android.launcher3.Insettable; 31 import com.android.launcher3.Launcher; 32 import com.android.launcher3.LauncherAppState; 33 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener; 34 import com.android.launcher3.R; 35 import com.android.launcher3.views.RecyclerViewFastScroller; 36 import com.android.launcher3.views.TopRoundedCornerView; 37 38 /** 39 * Popup for showing the full list of available widgets 40 */ 41 public class WidgetsFullSheet extends BaseWidgetSheet 42 implements Insettable, ProviderChangedListener { 43 44 private static final long DEFAULT_OPEN_DURATION = 267; 45 private static final long FADE_IN_DURATION = 150; 46 private static final float VERTICAL_START_POSITION = 0.3f; 47 48 private final Rect mInsets = new Rect(); 49 50 private final WidgetsListAdapter mAdapter; 51 52 private WidgetsRecyclerView mRecyclerView; 53 54 public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) { 55 super(context, attrs, defStyleAttr); 56 LauncherAppState apps = LauncherAppState.getInstance(context); 57 mAdapter = new WidgetsListAdapter(context, 58 LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(), 59 this, this); 60 61 } 62 63 public WidgetsFullSheet(Context context, AttributeSet attrs) { 64 this(context, attrs, 0); 65 } 66 67 @Override 68 protected void onFinishInflate() { 69 super.onFinishInflate(); 70 mContent = findViewById(R.id.container); 71 72 mRecyclerView = findViewById(R.id.widgets_list_view); 73 mRecyclerView.setAdapter(mAdapter); 74 mAdapter.setApplyBitmapDeferred(true, mRecyclerView); 75 76 TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent; 77 springLayout.addSpringView(R.id.widgets_list_view); 78 mRecyclerView.setEdgeEffectFactory(springLayout.createEdgeEffectFactory()); 79 onWidgetsBound(); 80 } 81 82 @Override 83 protected Pair<View, String> getAccessibilityTarget() { 84 return Pair.create(mRecyclerView, getContext().getString( 85 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed)); 86 } 87 88 @Override 89 protected void onAttachedToWindow() { 90 super.onAttachedToWindow(); 91 mLauncher.getAppWidgetHost().addProviderChangeListener(this); 92 notifyWidgetProvidersChanged(); 93 } 94 95 @Override 96 protected void onDetachedFromWindow() { 97 super.onDetachedFromWindow(); 98 mLauncher.getAppWidgetHost().removeProviderChangeListener(this); 99 } 100 101 @Override 102 public void setInsets(Rect insets) { 103 mInsets.set(insets); 104 105 mRecyclerView.setPadding( 106 mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(), 107 mRecyclerView.getPaddingRight(), insets.bottom); 108 if (insets.bottom > 0) { 109 setupNavBarColor(); 110 } else { 111 clearNavBarColor(); 112 } 113 114 ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom); 115 requestLayout(); 116 } 117 118 @Override 119 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 120 int widthUsed; 121 if (mInsets.bottom > 0) { 122 widthUsed = 0; 123 } else { 124 Rect padding = mLauncher.getDeviceProfile().workspacePadding; 125 widthUsed = Math.max(padding.left + padding.right, 126 2 * (mInsets.left + mInsets.right)); 127 } 128 129 int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx; 130 measureChildWithMargins(mContent, widthMeasureSpec, 131 widthUsed, heightMeasureSpec, heightUsed); 132 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 133 MeasureSpec.getSize(heightMeasureSpec)); 134 } 135 136 @Override 137 protected void onLayout(boolean changed, int l, int t, int r, int b) { 138 int width = r - l; 139 int height = b - t; 140 141 // Content is laid out as center bottom aligned 142 int contentWidth = mContent.getMeasuredWidth(); 143 int contentLeft = (width - contentWidth) / 2; 144 mContent.layout(contentLeft, height - mContent.getMeasuredHeight(), 145 contentLeft + contentWidth, height); 146 147 setTranslationShift(mTranslationShift); 148 } 149 150 @Override 151 public void notifyWidgetProvidersChanged() { 152 mLauncher.refreshAndBindWidgetsForPackageUser(null); 153 } 154 155 @Override 156 protected void onWidgetsBound() { 157 mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets()); 158 } 159 160 private void open(boolean animate) { 161 if (animate) { 162 if (mLauncher.getDragLayer().getInsets().bottom > 0) { 163 mContent.setAlpha(0); 164 setTranslationShift(VERTICAL_START_POSITION); 165 } 166 mOpenCloseAnimator.setValues( 167 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); 168 mOpenCloseAnimator 169 .setDuration(DEFAULT_OPEN_DURATION) 170 .setInterpolator(AnimationUtils.loadInterpolator( 171 getContext(), android.R.interpolator.linear_out_slow_in)); 172 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { 173 @Override 174 public void onAnimationEnd(Animator animation) { 175 mRecyclerView.setLayoutFrozen(false); 176 mAdapter.setApplyBitmapDeferred(false, mRecyclerView); 177 mOpenCloseAnimator.removeListener(this); 178 } 179 }); 180 post(() -> { 181 mRecyclerView.setLayoutFrozen(true); 182 mOpenCloseAnimator.start(); 183 mContent.animate().alpha(1).setDuration(FADE_IN_DURATION); 184 }); 185 } else { 186 setTranslationShift(TRANSLATION_SHIFT_OPENED); 187 mAdapter.setApplyBitmapDeferred(false, mRecyclerView); 188 post(this::announceAccessibilityChanges); 189 } 190 } 191 192 @Override 193 protected void handleClose(boolean animate) { 194 handleClose(animate, DEFAULT_OPEN_DURATION); 195 } 196 197 @Override 198 protected boolean isOfType(int type) { 199 return (type & TYPE_WIDGETS_FULL_SHEET) != 0; 200 } 201 202 @Override 203 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 204 // Disable swipe down when recycler view is scrolling 205 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 206 mNoIntercept = false; 207 RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar(); 208 if (scroller.getThumbOffsetY() >= 0 && 209 mLauncher.getDragLayer().isEventOverView(scroller, ev)) { 210 mNoIntercept = true; 211 } else if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) { 212 mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer()); 213 } 214 } 215 return super.onControllerInterceptTouchEvent(ev); 216 } 217 218 public static WidgetsFullSheet show(Launcher launcher, boolean animate) { 219 WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater() 220 .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false); 221 sheet.mIsOpen = true; 222 launcher.getDragLayer().addView(sheet); 223 sheet.open(animate); 224 return sheet; 225 } 226 227 @Override 228 protected int getElementsRowCount() { 229 return mAdapter.getItemCount(); 230 } 231 } 232