blob: 724a130ef52d4c4a54e7146684ab351c99e6a95e [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.draganddrop;
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
/**
* Renders a drop zone area for items being dragged.
*/
public class DropZoneView extends FrameLayout {
private static final float SPLASHSCREEN_ALPHA = 0.90f;
private static final float HIGHLIGHT_ALPHA = 1f;
private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;
private static final FloatProperty<DropZoneView> INSETS =
new FloatProperty<DropZoneView>("insets") {
@Override
public void setValue(DropZoneView v, float percent) {
v.setMarginPercent(percent);
}
@Override
public Float get(DropZoneView v) {
return v.getMarginPercent();
}
};
private final Path mPath = new Path();
private final float[] mContainerMargin = new float[4];
private float mCornerRadius;
private float mBottomInset;
private boolean mIgnoreBottomMargin;
private int mMarginColor; // i.e. color used for negative space like the container insets
private boolean mShowingHighlight;
private boolean mShowingSplash;
private boolean mShowingMargin;
private int mSplashScreenColor;
private int mHighlightColor;
private ObjectAnimator mBackgroundAnimator;
private ObjectAnimator mMarginAnimator;
private float mMarginPercent;
// Renders a highlight or neutral transparent color
private ColorDrawable mColorDrawable;
// Renders the translucent splashscreen with the app icon in the middle
private ImageView mSplashScreenView;
// Renders the margin / insets around the dropzone container
private MarginView mMarginView;
public DropZoneView(Context context) {
this(context, null);
}
public DropZoneView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setContainerMargin(0, 0, 0, 0); // make sure it's populated
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
int c = getResources().getColor(android.R.color.system_accent1_500);
mHighlightColor = Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
mColorDrawable = new ColorDrawable();
setBackgroundDrawable(mColorDrawable);
final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
mSplashScreenView = new ImageView(context);
mSplashScreenView.setScaleType(ImageView.ScaleType.FIT_CENTER);
addView(mSplashScreenView,
new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
mSplashScreenView.setAlpha(0f);
mMarginView = new MarginView(context);
addView(mMarginView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
public void onThemeChange() {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(getContext());
mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
}
/** Sets the desired margins around the drop zone container when fully showing. */
public void setContainerMargin(float left, float top, float right, float bottom) {
mContainerMargin[0] = left;
mContainerMargin[1] = top;
mContainerMargin[2] = right;
mContainerMargin[3] = bottom;
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
}
/** Ignores the bottom margin provided by the insets. */
public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) {
mIgnoreBottomMargin = ignoreBottomMargin;
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
}
/** Sets the bottom inset so the drop zones are above bottom navigation. */
public void setBottomInset(float bottom) {
mBottomInset = bottom;
((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom;
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
}
/** Sets the color and icon to use for the splashscreen when shown. */
public void setAppInfo(int color, Drawable appIcon) {
Color c = Color.valueOf(color);
mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue());
mSplashScreenView.setImageDrawable(appIcon);
}
/** @return an active animator for this view if one exists. */
@Nullable
public Animator getAnimator() {
if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
return mMarginAnimator;
} else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) {
return mBackgroundAnimator;
}
return null;
}
/** Animates between highlight and splashscreen depending on current state. */
public void animateSwitch() {
mShowingHighlight = !mShowingHighlight;
mShowingSplash = !mShowingHighlight;
final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
animateBackground(mColorDrawable.getColor(), newColor);
animateSplashScreenIcon();
}
/** Animates the highlight indicating the zone is hovered on or not. */
public void setShowingHighlight(boolean showingHighlight) {
mShowingHighlight = showingHighlight;
mShowingSplash = !mShowingHighlight;
final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
animateBackground(Color.TRANSPARENT, newColor);
animateSplashScreenIcon();
}
/** Animates the margins around the drop zone to show or hide. */
public void setShowingMargin(boolean visible) {
if (mShowingMargin != visible) {
mShowingMargin = visible;
animateMarginToState();
}
if (!mShowingMargin) {
mShowingHighlight = false;
mShowingSplash = false;
animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT);
animateSplashScreenIcon();
}
}
private void animateBackground(int startColor, int endColor) {
if (mBackgroundAnimator != null) {
mBackgroundAnimator.cancel();
}
mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable,
"color",
startColor,
endColor);
if (!mShowingSplash && !mShowingHighlight) {
mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
}
mBackgroundAnimator.start();
}
private void animateSplashScreenIcon() {
mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
}
private void animateMarginToState() {
if (mMarginAnimator != null) {
mMarginAnimator.cancel();
}
mMarginAnimator = ObjectAnimator.ofFloat(this, INSETS,
mMarginPercent,
mShowingMargin ? 1f : 0f);
mMarginAnimator.setInterpolator(FAST_OUT_SLOW_IN);
mMarginAnimator.setDuration(mShowingMargin
? MARGIN_ANIMATION_ENTER_DURATION
: MARGIN_ANIMATION_EXIT_DURATION);
mMarginAnimator.start();
}
private void setMarginPercent(float percent) {
if (percent != mMarginPercent) {
mMarginPercent = percent;
mMarginView.invalidate();
}
}
private float getMarginPercent() {
return mMarginPercent;
}
/** Simple view that draws a rounded rect margin around its contents. **/
private class MarginView extends View {
MarginView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
mPath.addRoundRect(mContainerMargin[0] * mMarginPercent,
mContainerMargin[1] * mMarginPercent,
getWidth() - (mContainerMargin[2] * mMarginPercent),
getHeight() - (mContainerMargin[3] * mMarginPercent)
- (mIgnoreBottomMargin ? 0 : mBottomInset),
mCornerRadius * mMarginPercent,
mCornerRadius * mMarginPercent,
Path.Direction.CW);
mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
canvas.clipPath(mPath);
canvas.drawColor(mMarginColor);
}
}
}