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