• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.wm.shell.draganddrop;
18 
19 import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
20 
21 import android.animation.Animator;
22 import android.animation.ObjectAnimator;
23 import android.content.Context;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Path;
27 import android.graphics.drawable.ColorDrawable;
28 import android.graphics.drawable.Drawable;
29 import android.util.AttributeSet;
30 import android.util.FloatProperty;
31 import android.view.Gravity;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.FrameLayout;
35 import android.widget.ImageView;
36 
37 import androidx.annotation.Nullable;
38 
39 import com.android.internal.policy.ScreenDecorationsUtils;
40 import com.android.wm.shell.R;
41 
42 /**
43  * Renders a drop zone area for items being dragged.
44  */
45 public class DropZoneView extends FrameLayout {
46 
47     private static final float SPLASHSCREEN_ALPHA = 0.90f;
48     private static final float HIGHLIGHT_ALPHA = 1f;
49     private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
50     private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;
51 
52     private static final FloatProperty<DropZoneView> INSETS =
53             new FloatProperty<DropZoneView>("insets") {
54                 @Override
55                 public void setValue(DropZoneView v, float percent) {
56                     v.setMarginPercent(percent);
57                 }
58 
59                 @Override
60                 public Float get(DropZoneView v) {
61                     return v.getMarginPercent();
62                 }
63             };
64 
65     private final Path mPath = new Path();
66     private final float[] mContainerMargin = new float[4];
67     private float mCornerRadius;
68     private float mBottomInset;
69     private boolean mIgnoreBottomMargin;
70     private int mMarginColor; // i.e. color used for negative space like the container insets
71 
72     private boolean mShowingHighlight;
73     private boolean mShowingSplash;
74     private boolean mShowingMargin;
75 
76     private int mSplashScreenColor;
77     private int mHighlightColor;
78 
79     private ObjectAnimator mBackgroundAnimator;
80     private ObjectAnimator mMarginAnimator;
81     private float mMarginPercent;
82 
83     // Renders a highlight or neutral transparent color
84     private ColorDrawable mColorDrawable;
85     // Renders the translucent splashscreen with the app icon in the middle
86     private ImageView mSplashScreenView;
87     // Renders the margin / insets around the dropzone container
88     private MarginView mMarginView;
89 
DropZoneView(Context context)90     public DropZoneView(Context context) {
91         this(context, null);
92     }
93 
DropZoneView(Context context, AttributeSet attrs)94     public DropZoneView(Context context, AttributeSet attrs) {
95         this(context, attrs, 0);
96     }
97 
DropZoneView(Context context, AttributeSet attrs, int defStyleAttr)98     public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr) {
99         this(context, attrs, defStyleAttr, 0);
100     }
101 
DropZoneView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)102     public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
103         super(context, attrs, defStyleAttr, defStyleRes);
104         setContainerMargin(0, 0, 0, 0); // make sure it's populated
105 
106         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
107         mMarginColor = getResources().getColor(R.color.taskbar_background);
108         int c = getResources().getColor(android.R.color.system_accent1_500);
109         mHighlightColor =  Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
110         mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
111         mColorDrawable = new ColorDrawable();
112         setBackgroundDrawable(mColorDrawable);
113 
114         final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
115         mSplashScreenView = new ImageView(context);
116         mSplashScreenView.setScaleType(ImageView.ScaleType.FIT_CENTER);
117         addView(mSplashScreenView,
118                 new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
119         mSplashScreenView.setAlpha(0f);
120 
121         mMarginView = new MarginView(context);
122         addView(mMarginView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
123                 ViewGroup.LayoutParams.MATCH_PARENT));
124     }
125 
onThemeChange()126     public void onThemeChange() {
127         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(getContext());
128         mMarginColor = getResources().getColor(R.color.taskbar_background);
129         mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
130 
131         if (mMarginPercent > 0) {
132             mMarginView.invalidate();
133         }
134     }
135 
136     /** Sets the desired margins around the drop zone container when fully showing. */
setContainerMargin(float left, float top, float right, float bottom)137     public void setContainerMargin(float left, float top, float right, float bottom) {
138         mContainerMargin[0] = left;
139         mContainerMargin[1] = top;
140         mContainerMargin[2] = right;
141         mContainerMargin[3] = bottom;
142         if (mMarginPercent > 0) {
143             mMarginView.invalidate();
144         }
145     }
146 
147     /** Ignores the bottom margin provided by the insets. */
setForceIgnoreBottomMargin(boolean ignoreBottomMargin)148     public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) {
149         mIgnoreBottomMargin = ignoreBottomMargin;
150         if (mMarginPercent > 0) {
151             mMarginView.invalidate();
152         }
153     }
154 
155     /** Sets the bottom inset so the drop zones are above bottom navigation. */
setBottomInset(float bottom)156     public void setBottomInset(float bottom) {
157         mBottomInset = bottom;
158         ((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom;
159         if (mMarginPercent > 0) {
160             mMarginView.invalidate();
161         }
162     }
163 
164     /** Sets the color and icon to use for the splashscreen when shown. */
setAppInfo(int color, Drawable appIcon)165     public void setAppInfo(int color, Drawable appIcon) {
166         Color c = Color.valueOf(color);
167         mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue());
168         mSplashScreenView.setImageDrawable(appIcon);
169     }
170 
171     /** @return an active animator for this view if one exists. */
172     @Nullable
getAnimator()173     public Animator getAnimator() {
174         if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
175             return mMarginAnimator;
176         } else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) {
177             return mBackgroundAnimator;
178         }
179         return null;
180     }
181 
182     /** Animates between highlight and splashscreen depending on current state. */
animateSwitch()183     public void animateSwitch() {
184         mShowingHighlight = !mShowingHighlight;
185         mShowingSplash = !mShowingHighlight;
186         final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
187         animateBackground(mColorDrawable.getColor(), newColor);
188         animateSplashScreenIcon();
189     }
190 
191     /** Animates the highlight indicating the zone is hovered on or not. */
setShowingHighlight(boolean showingHighlight)192     public void setShowingHighlight(boolean showingHighlight) {
193         mShowingHighlight = showingHighlight;
194         mShowingSplash = !mShowingHighlight;
195         final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
196         animateBackground(Color.TRANSPARENT, newColor);
197         animateSplashScreenIcon();
198     }
199 
200     /** Animates the margins around the drop zone to show or hide. */
setShowingMargin(boolean visible)201     public void setShowingMargin(boolean visible) {
202         if (mShowingMargin != visible) {
203             mShowingMargin = visible;
204             animateMarginToState();
205         }
206         if (!mShowingMargin) {
207             mShowingHighlight = false;
208             mShowingSplash = false;
209             animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT);
210             animateSplashScreenIcon();
211         }
212     }
213 
animateBackground(int startColor, int endColor)214     private void animateBackground(int startColor, int endColor) {
215         if (mBackgroundAnimator != null) {
216             mBackgroundAnimator.cancel();
217         }
218         mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable,
219                 "color",
220                 startColor,
221                 endColor);
222         if (!mShowingSplash && !mShowingHighlight) {
223             mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
224         }
225         mBackgroundAnimator.start();
226     }
227 
animateSplashScreenIcon()228     private void animateSplashScreenIcon() {
229         mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
230     }
231 
animateMarginToState()232     private void animateMarginToState() {
233         if (mMarginAnimator != null) {
234             mMarginAnimator.cancel();
235         }
236         mMarginAnimator = ObjectAnimator.ofFloat(this, INSETS,
237                 mMarginPercent,
238                 mShowingMargin ? 1f : 0f);
239         mMarginAnimator.setInterpolator(FAST_OUT_SLOW_IN);
240         mMarginAnimator.setDuration(mShowingMargin
241                 ? MARGIN_ANIMATION_ENTER_DURATION
242                 : MARGIN_ANIMATION_EXIT_DURATION);
243         mMarginAnimator.start();
244     }
245 
setMarginPercent(float percent)246     private void setMarginPercent(float percent) {
247         if (percent != mMarginPercent) {
248             mMarginPercent = percent;
249             mMarginView.invalidate();
250         }
251     }
252 
getMarginPercent()253     private float getMarginPercent() {
254         return mMarginPercent;
255     }
256 
257     /** Simple view that draws a rounded rect margin around its contents. **/
258     private class MarginView extends View {
259 
MarginView(Context context)260         MarginView(Context context) {
261             super(context);
262         }
263 
264         @Override
onDraw(Canvas canvas)265         protected void onDraw(Canvas canvas) {
266             super.onDraw(canvas);
267             mPath.reset();
268             mPath.addRoundRect(mContainerMargin[0] * mMarginPercent,
269                     mContainerMargin[1] * mMarginPercent,
270                     getWidth() - (mContainerMargin[2] * mMarginPercent),
271                     getHeight() - (mContainerMargin[3] * mMarginPercent)
272                             - (mIgnoreBottomMargin ? 0 : mBottomInset),
273                     mCornerRadius * mMarginPercent,
274                     mCornerRadius * mMarginPercent,
275                     Path.Direction.CW);
276             mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
277             canvas.clipPath(mPath);
278             canvas.drawColor(mMarginColor);
279         }
280     }
281 }
282