1 /* 2 * Copyright (C) 2011 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; 18 19 import static com.android.launcher3.ButtonDropTarget.TOOLTIP_DEFAULT; 20 import static com.android.launcher3.ButtonDropTarget.TOOLTIP_LEFT; 21 import static com.android.launcher3.ButtonDropTarget.TOOLTIP_RIGHT; 22 import static com.android.launcher3.anim.AlphaUpdateListener.updateVisibility; 23 24 import android.animation.TimeInterpolator; 25 import android.content.Context; 26 import android.graphics.Rect; 27 import android.util.AttributeSet; 28 import android.util.Log; 29 import android.util.TypedValue; 30 import android.view.Gravity; 31 import android.view.View; 32 import android.view.ViewDebug; 33 import android.view.ViewPropertyAnimator; 34 import android.widget.FrameLayout; 35 36 import androidx.annotation.NonNull; 37 38 import com.android.launcher3.anim.Interpolators; 39 import com.android.launcher3.dragndrop.DragController; 40 import com.android.launcher3.dragndrop.DragController.DragListener; 41 import com.android.launcher3.dragndrop.DragOptions; 42 import com.android.launcher3.testing.TestProtocol; 43 44 /* 45 * The top bar containing various drop targets: Delete/App Info/Uninstall. 46 */ 47 public class DropTargetBar extends FrameLayout 48 implements DragListener, Insettable { 49 50 protected static final int DEFAULT_DRAG_FADE_DURATION = 175; 51 protected static final TimeInterpolator DEFAULT_INTERPOLATOR = Interpolators.ACCEL; 52 53 private final Runnable mFadeAnimationEndRunnable = 54 () -> updateVisibility(DropTargetBar.this); 55 56 @ViewDebug.ExportedProperty(category = "launcher") 57 protected boolean mDeferOnDragEnd; 58 59 @ViewDebug.ExportedProperty(category = "launcher") 60 protected boolean mVisible = false; 61 62 private ButtonDropTarget[] mDropTargets; 63 private ViewPropertyAnimator mCurrentAnimation; 64 65 private boolean mIsVertical = true; 66 DropTargetBar(Context context, AttributeSet attrs)67 public DropTargetBar(Context context, AttributeSet attrs) { 68 super(context, attrs); 69 } 70 DropTargetBar(Context context, AttributeSet attrs, int defStyle)71 public DropTargetBar(Context context, AttributeSet attrs, int defStyle) { 72 super(context, attrs, defStyle); 73 } 74 75 @Override onFinishInflate()76 protected void onFinishInflate() { 77 super.onFinishInflate(); 78 mDropTargets = new ButtonDropTarget[getChildCount()]; 79 for (int i = 0; i < mDropTargets.length; i++) { 80 mDropTargets[i] = (ButtonDropTarget) getChildAt(i); 81 mDropTargets[i].setDropTargetBar(this); 82 } 83 } 84 85 @Override setInsets(Rect insets)86 public void setInsets(Rect insets) { 87 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); 88 DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); 89 mIsVertical = grid.isVerticalBarLayout(); 90 91 lp.leftMargin = insets.left; 92 lp.topMargin = insets.top; 93 lp.bottomMargin = insets.bottom; 94 lp.rightMargin = insets.right; 95 int tooltipLocation = TOOLTIP_DEFAULT; 96 97 if (grid.isVerticalBarLayout()) { 98 lp.width = grid.dropTargetBarSizePx; 99 lp.height = grid.availableHeightPx - 2 * grid.edgeMarginPx; 100 lp.gravity = grid.isSeascape() ? Gravity.RIGHT : Gravity.LEFT; 101 tooltipLocation = grid.isSeascape() ? TOOLTIP_LEFT : TOOLTIP_RIGHT; 102 } else { 103 int gap; 104 if (grid.isTablet) { 105 // XXX: If the icon size changes across orientations, we will have to take 106 // that into account here too. 107 gap = ((grid.widthPx - 2 * grid.edgeMarginPx 108 - (grid.inv.numColumns * grid.cellWidthPx)) 109 / (2 * (grid.inv.numColumns + 1))) 110 + grid.edgeMarginPx; 111 } else { 112 gap = getContext().getResources() 113 .getDimensionPixelSize(R.dimen.drop_target_bar_margin_horizontal); 114 } 115 lp.width = grid.availableWidthPx - 2 * gap; 116 117 lp.topMargin += grid.edgeMarginPx; 118 lp.height = grid.dropTargetBarSizePx; 119 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; 120 } 121 setLayoutParams(lp); 122 for (ButtonDropTarget button : mDropTargets) { 123 button.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.dropTargetTextSizePx); 124 button.setToolTipLocation(tooltipLocation); 125 } 126 } 127 setup(DragController dragController)128 public void setup(DragController dragController) { 129 dragController.addDragListener(this); 130 for (int i = 0; i < mDropTargets.length; i++) { 131 dragController.addDragListener(mDropTargets[i]); 132 dragController.addDropTarget(mDropTargets[i]); 133 } 134 } 135 136 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)137 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 138 int width = MeasureSpec.getSize(widthMeasureSpec); 139 int height = MeasureSpec.getSize(heightMeasureSpec); 140 141 int visibleCount = getVisibleButtonsCount(); 142 if (visibleCount == 0) { 143 // do nothing 144 } else if (mIsVertical) { 145 int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 146 int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); 147 148 for (ButtonDropTarget button : mDropTargets) { 149 if (button.getVisibility() != GONE) { 150 button.setTextVisible(false); 151 button.measure(widthSpec, heightSpec); 152 } 153 } 154 } else { 155 int availableWidth = width / visibleCount; 156 boolean textVisible = true; 157 for (ButtonDropTarget buttons : mDropTargets) { 158 if (buttons.getVisibility() != GONE) { 159 textVisible = textVisible && !buttons.isTextTruncated(availableWidth); 160 } 161 } 162 163 int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST); 164 int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 165 for (ButtonDropTarget button : mDropTargets) { 166 if (button.getVisibility() != GONE) { 167 button.setTextVisible(textVisible); 168 button.measure(widthSpec, heightSpec); 169 } 170 } 171 } 172 setMeasuredDimension(width, height); 173 } 174 175 @Override onLayout(boolean changed, int left, int top, int right, int bottom)176 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 177 int visibleCount = getVisibleButtonsCount(); 178 if (visibleCount == 0) { 179 // do nothing 180 } else if (mIsVertical) { 181 int gap = getResources().getDimensionPixelSize(R.dimen.drop_target_vertical_gap); 182 int start = gap; 183 int end; 184 185 for (ButtonDropTarget button : mDropTargets) { 186 if (button.getVisibility() != GONE) { 187 end = start + button.getMeasuredHeight(); 188 button.layout(0, start, button.getMeasuredWidth(), end); 189 start = end + gap; 190 } 191 } 192 } else { 193 int frameSize = (right - left) / visibleCount; 194 195 int start = frameSize / 2; 196 int halfWidth; 197 for (ButtonDropTarget button : mDropTargets) { 198 if (button.getVisibility() != GONE) { 199 halfWidth = button.getMeasuredWidth() / 2; 200 button.layout(start - halfWidth, 0, 201 start + halfWidth, button.getMeasuredHeight()); 202 start = start + frameSize; 203 } 204 } 205 } 206 } 207 getVisibleButtonsCount()208 private int getVisibleButtonsCount() { 209 int visibleCount = 0; 210 for (ButtonDropTarget buttons : mDropTargets) { 211 if (buttons.getVisibility() != GONE) { 212 visibleCount++; 213 } 214 } 215 return visibleCount; 216 } 217 animateToVisibility(boolean isVisible)218 public void animateToVisibility(boolean isVisible) { 219 if (TestProtocol.sDebugTracing) { 220 Log.d(TestProtocol.NO_DROP_TARGET, "8"); 221 } 222 if (mVisible != isVisible) { 223 mVisible = isVisible; 224 225 // Cancel any existing animation 226 if (mCurrentAnimation != null) { 227 mCurrentAnimation.cancel(); 228 mCurrentAnimation = null; 229 } 230 231 float finalAlpha = mVisible ? 1 : 0; 232 if (Float.compare(getAlpha(), finalAlpha) != 0) { 233 setVisibility(View.VISIBLE); 234 mCurrentAnimation = animate().alpha(finalAlpha) 235 .setInterpolator(DEFAULT_INTERPOLATOR) 236 .setDuration(DEFAULT_DRAG_FADE_DURATION) 237 .withEndAction(mFadeAnimationEndRunnable); 238 } 239 240 } 241 } 242 243 /* 244 * DragController.DragListener implementation 245 */ 246 @Override onDragStart(DropTarget.DragObject dragObject, DragOptions options)247 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { 248 if (TestProtocol.sDebugTracing) { 249 Log.d(TestProtocol.NO_DROP_TARGET, "7"); 250 } 251 animateToVisibility(true); 252 } 253 254 /** 255 * This is called to defer hiding the delete drop target until the drop animation has completed, 256 * instead of hiding immediately when the drag has ended. 257 */ deferOnDragEnd()258 protected void deferOnDragEnd() { 259 mDeferOnDragEnd = true; 260 } 261 262 @Override onDragEnd()263 public void onDragEnd() { 264 if (!mDeferOnDragEnd) { 265 animateToVisibility(false); 266 } else { 267 mDeferOnDragEnd = false; 268 } 269 } 270 getDropTargets()271 public ButtonDropTarget[] getDropTargets() { 272 return mDropTargets; 273 } 274 275 @Override onVisibilityChanged(@onNull View changedView, int visibility)276 protected void onVisibilityChanged(@NonNull View changedView, int visibility) { 277 super.onVisibilityChanged(changedView, visibility); 278 if (TestProtocol.sDebugTracing) { 279 if (visibility == VISIBLE) { 280 Log.d(TestProtocol.NO_DROP_TARGET, "9"); 281 } else { 282 Log.d(TestProtocol.NO_DROP_TARGET, "Hiding drop target", new Exception()); 283 } 284 } 285 } 286 } 287