1 /* 2 * Copyright (C) 2018 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.quickstep.views; 17 18 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; 19 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 20 import static com.android.launcher3.LauncherState.OVERVIEW; 21 import static com.android.launcher3.anim.Interpolators.ACCEL; 22 import static com.android.launcher3.anim.Interpolators.ACCEL_2; 23 import static com.android.launcher3.anim.Interpolators.LINEAR; 24 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; 25 26 import android.content.Context; 27 import android.graphics.Canvas; 28 import android.graphics.Color; 29 import android.graphics.Paint; 30 import android.graphics.Path; 31 import android.graphics.Path.Direction; 32 import android.graphics.Path.Op; 33 import android.graphics.Rect; 34 import android.util.AttributeSet; 35 import android.view.animation.Interpolator; 36 37 import com.android.launcher3.DeviceProfile; 38 import com.android.launcher3.R; 39 import com.android.launcher3.Utilities; 40 import com.android.launcher3.anim.Interpolators; 41 import com.android.launcher3.uioverrides.states.OverviewState; 42 import com.android.launcher3.util.Themes; 43 import com.android.launcher3.views.ScrimView; 44 import com.android.quickstep.SysUINavigationMode; 45 import com.android.quickstep.SysUINavigationMode.Mode; 46 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; 47 48 /** 49 * Scrim used for all-apps and shelf in Overview 50 * In transposed layout, it behaves as a simple color scrim. 51 * In portrait layout, it draws a rounded rect such that 52 * From normal state to overview state, the shelf just fades in and does not move 53 * From overview state to all-apps state the shelf moves up and fades in to cover the screen 54 */ 55 public class ShelfScrimView extends ScrimView implements NavigationModeChangeListener { 56 57 // If the progress is more than this, shelf follows the finger, otherwise it moves faster to 58 // cover the whole screen 59 private static final float SCRIM_CATCHUP_THRESHOLD = 0.2f; 60 61 // Temporarily needed until android.R.attr.bottomDialogCornerRadius becomes public 62 private static final float BOTTOM_CORNER_RADIUS_RATIO = 2f; 63 64 // In transposed layout, we simply draw a flat color. 65 private boolean mDrawingFlatColor; 66 67 // For shelf mode 68 private final int mEndAlpha; 69 private final float mRadius; 70 private final int mMaxScrimAlpha; 71 private final Paint mPaint; 72 73 // Mid point where the alpha changes 74 private int mMidAlpha; 75 private float mMidProgress; 76 77 private Interpolator mBeforeMidProgressColorInterpolator = ACCEL; 78 private Interpolator mAfterMidProgressColorInterpolator = ACCEL; 79 80 private float mShiftRange; 81 82 private final float mShelfOffset; 83 private float mTopOffset; 84 private float mShelfTop; 85 private float mShelfTopAtThreshold; 86 87 private int mShelfColor; 88 private int mRemainingScreenColor; 89 90 private final Path mTempPath = new Path(); 91 private final Path mRemainingScreenPath = new Path(); 92 private boolean mRemainingScreenPathValid = false; 93 94 private Mode mSysUINavigationMode; 95 ShelfScrimView(Context context, AttributeSet attrs)96 public ShelfScrimView(Context context, AttributeSet attrs) { 97 super(context, attrs); 98 mMaxScrimAlpha = Math.round(OVERVIEW.getWorkspaceScrimAlpha(mLauncher) * 255); 99 100 mEndAlpha = Color.alpha(mEndScrim); 101 mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context); 102 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 103 104 mShelfOffset = context.getResources().getDimension(R.dimen.shelf_surface_offset); 105 // Just assume the easiest UI for now, until we have the proper layout information. 106 mDrawingFlatColor = true; 107 } 108 109 @Override onSizeChanged(int w, int h, int oldw, int oldh)110 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 111 super.onSizeChanged(w, h, oldw, oldh); 112 mRemainingScreenPathValid = false; 113 } 114 115 @Override onAttachedToWindow()116 protected void onAttachedToWindow() { 117 super.onAttachedToWindow(); 118 onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(getContext()) 119 .addModeChangeListener(this)); 120 } 121 122 @Override onDetachedFromWindow()123 protected void onDetachedFromWindow() { 124 super.onDetachedFromWindow(); 125 SysUINavigationMode.INSTANCE.get(getContext()).removeModeChangeListener(this); 126 } 127 128 @Override onNavigationModeChanged(Mode newMode)129 public void onNavigationModeChanged(Mode newMode) { 130 mSysUINavigationMode = newMode; 131 // Note that these interpolators are inverted because progress goes 1 to 0. 132 if (mSysUINavigationMode == Mode.NO_BUTTON) { 133 // Show the shelf more quickly before reaching overview progress. 134 mBeforeMidProgressColorInterpolator = ACCEL_2; 135 mAfterMidProgressColorInterpolator = ACCEL; 136 } else { 137 mBeforeMidProgressColorInterpolator = ACCEL; 138 mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f); 139 } 140 } 141 142 @Override reInitUi()143 public void reInitUi() { 144 DeviceProfile dp = mLauncher.getDeviceProfile(); 145 mDrawingFlatColor = dp.isVerticalBarLayout(); 146 147 if (!mDrawingFlatColor) { 148 mRemainingScreenPathValid = false; 149 mShiftRange = mLauncher.getAllAppsController().getShiftRange(); 150 151 if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) { 152 mMidProgress = 1; 153 mMidAlpha = 0; 154 } else { 155 mMidAlpha = Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha); 156 Rect hotseatPadding = dp.getHotseatLayoutPadding(); 157 int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom 158 - hotseatPadding.bottom - hotseatPadding.top; 159 float arrowTop = Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(dp)); 160 mMidProgress = 1 - (arrowTop / mShiftRange); 161 162 } 163 mTopOffset = dp.getInsets().top - mShelfOffset; 164 mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset; 165 } 166 updateColors(); 167 updateDragHandleAlpha(); 168 invalidate(); 169 } 170 171 @Override updateColors()172 public void updateColors() { 173 super.updateColors(); 174 if (mDrawingFlatColor) { 175 mDragHandleOffset = 0; 176 return; 177 } 178 179 mDragHandleOffset = mShelfOffset - mDragHandleSize; 180 if (mProgress >= SCRIM_CATCHUP_THRESHOLD) { 181 mShelfTop = mShiftRange * mProgress + mTopOffset; 182 } else { 183 mShelfTop = Utilities.mapRange(mProgress / SCRIM_CATCHUP_THRESHOLD, -mRadius, 184 mShelfTopAtThreshold); 185 } 186 187 if (mProgress >= 1) { 188 mRemainingScreenColor = 0; 189 mShelfColor = 0; 190 if (mSysUINavigationMode == Mode.NO_BUTTON 191 && mLauncher.getStateManager().getState() == BACKGROUND_APP) { 192 // Show the shelf background when peeking during swipe up. 193 mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha); 194 } 195 } else if (mProgress >= mMidProgress) { 196 mRemainingScreenColor = 0; 197 198 int alpha = Math.round(Utilities.mapToRange( 199 mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator)); 200 mShelfColor = setColorAlphaBound(mEndScrim, alpha); 201 } else { 202 mDragHandleOffset += mShiftRange * (mMidProgress - mProgress); 203 204 // Note that these ranges and interpolators are inverted because progress goes 1 to 0. 205 int alpha = Math.round( 206 Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha, 207 (float) mMidAlpha, mAfterMidProgressColorInterpolator)); 208 mShelfColor = setColorAlphaBound(mEndScrim, alpha); 209 210 int remainingScrimAlpha = Math.round( 211 Utilities.mapToRange(mProgress, (float) 0, mMidProgress, mMaxScrimAlpha, 212 (float) 0, LINEAR)); 213 mRemainingScreenColor = setColorAlphaBound(mScrimColor, remainingScrimAlpha); 214 } 215 } 216 217 @Override onDraw(Canvas canvas)218 protected void onDraw(Canvas canvas) { 219 drawBackground(canvas); 220 drawDragHandle(canvas); 221 } 222 drawBackground(Canvas canvas)223 private void drawBackground(Canvas canvas) { 224 if (mDrawingFlatColor) { 225 if (mCurrentFlatColor != 0) { 226 canvas.drawColor(mCurrentFlatColor); 227 } 228 return; 229 } 230 231 if (Color.alpha(mShelfColor) == 0) { 232 return; 233 } else if (mProgress <= 0) { 234 canvas.drawColor(mShelfColor); 235 return; 236 } 237 238 int height = getHeight(); 239 int width = getWidth(); 240 // Draw the scrim over the remaining screen if needed. 241 if (mRemainingScreenColor != 0) { 242 if (!mRemainingScreenPathValid) { 243 mTempPath.reset(); 244 // Using a arbitrary '+10' in the bottom to avoid any left-overs at the 245 // corners due to rounding issues. 246 mTempPath.addRoundRect(0, height - mRadius, width, height + mRadius + 10, 247 mRadius, mRadius, Direction.CW); 248 mRemainingScreenPath.reset(); 249 mRemainingScreenPath.addRect(0, 0, width, height, Direction.CW); 250 mRemainingScreenPath.op(mTempPath, Op.DIFFERENCE); 251 } 252 253 float offset = height - mRadius - mShelfTop; 254 canvas.translate(0, -offset); 255 mPaint.setColor(mRemainingScreenColor); 256 canvas.drawPath(mRemainingScreenPath, mPaint); 257 canvas.translate(0, offset); 258 } 259 260 mPaint.setColor(mShelfColor); 261 canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint); 262 } 263 } 264