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