1 /*
2  * Copyright (C) 2015 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.common.split;
18 
19 import static com.android.wm.shell.common.split.DividerView.TOUCH_ANIMATION_DURATION;
20 import static com.android.wm.shell.common.split.DividerView.TOUCH_RELEASE_ANIMATION_DURATION;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.AnimatorSet;
25 import android.animation.ObjectAnimator;
26 import android.annotation.Nullable;
27 import android.content.Context;
28 import android.graphics.Canvas;
29 import android.graphics.Paint;
30 import android.util.AttributeSet;
31 import android.util.Property;
32 import android.view.View;
33 
34 import com.android.wm.shell.R;
35 import com.android.wm.shell.animation.Interpolators;
36 
37 /**
38  * View for the handle in the docked stack divider.
39  */
40 public class DividerHandleView extends View {
41 
42     private static final Property<DividerHandleView, Integer> WIDTH_PROPERTY =
43             new Property<DividerHandleView, Integer>(Integer.class, "width") {
44                 @Override
45                 public Integer get(DividerHandleView object) {
46                     return object.mCurrentWidth;
47                 }
48 
49                 @Override
50                 public void set(DividerHandleView object, Integer value) {
51                     object.mCurrentWidth = value;
52                     object.invalidate();
53                 }
54             };
55 
56     private static final Property<DividerHandleView, Integer> HEIGHT_PROPERTY =
57             new Property<DividerHandleView, Integer>(Integer.class, "height") {
58                 @Override
59                 public Integer get(DividerHandleView object) {
60                     return object.mCurrentHeight;
61                 }
62 
63                 @Override
64                 public void set(DividerHandleView object, Integer value) {
65                     object.mCurrentHeight = value;
66                     object.invalidate();
67                 }
68             };
69 
70     private final Paint mPaint = new Paint();
71     private final int mWidth;
72     private final int mHeight;
73     private final int mTouchingWidth;
74     private final int mTouchingHeight;
75     private int mCurrentWidth;
76     private int mCurrentHeight;
77     private AnimatorSet mAnimator;
78     private boolean mTouching;
79     private boolean mHovering;
80     private final int mHoveringWidth;
81     private final int mHoveringHeight;
82 
DividerHandleView(Context context, @Nullable AttributeSet attrs)83     public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
84         super(context, attrs);
85         mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
86         mPaint.setAntiAlias(true);
87         mWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_width);
88         mHeight = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_height);
89         mCurrentWidth = mWidth;
90         mCurrentHeight = mHeight;
91         mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
92         mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
93         mHoveringWidth = mWidth > mHeight ? ((int) (mWidth * 1.5f)) : mWidth;
94         mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
95     }
96 
97     /** Sets touching state for this handle view. */
setTouching(boolean touching, boolean animate)98     public void setTouching(boolean touching, boolean animate) {
99         if (touching == mTouching) {
100             return;
101         }
102         setInputState(touching, animate, mTouchingWidth, mTouchingHeight);
103         mTouching = touching;
104     }
105 
106     /** Sets hovering state for this handle view. */
setHovering(boolean hovering, boolean animate)107     public void setHovering(boolean hovering, boolean animate) {
108         if (hovering == mHovering) {
109             return;
110         }
111         setInputState(hovering, animate, mHoveringWidth, mHoveringHeight);
112         mHovering = hovering;
113     }
114 
setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight)115     private void setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight) {
116         if (mAnimator != null) {
117             mAnimator.cancel();
118             mAnimator = null;
119         }
120         if (!animate) {
121             mCurrentWidth = stateOn ? stateWidth : mWidth;
122             mCurrentHeight = stateOn ? stateHeight : mHeight;
123             invalidate();
124         } else {
125             animateToTarget(stateOn ? stateWidth : mWidth,
126                     stateOn ? stateHeight : mHeight, stateOn);
127         }
128     }
129 
animateToTarget(int targetWidth, int targetHeight, boolean touching)130     private void animateToTarget(int targetWidth, int targetHeight, boolean touching) {
131         ObjectAnimator widthAnimator = ObjectAnimator.ofInt(this, WIDTH_PROPERTY,
132                 mCurrentWidth, targetWidth);
133         ObjectAnimator heightAnimator = ObjectAnimator.ofInt(this, HEIGHT_PROPERTY,
134                 mCurrentHeight, targetHeight);
135         mAnimator = new AnimatorSet();
136         mAnimator.playTogether(widthAnimator, heightAnimator);
137         mAnimator.setDuration(touching
138                 ? TOUCH_ANIMATION_DURATION
139                 : TOUCH_RELEASE_ANIMATION_DURATION);
140         mAnimator.setInterpolator(touching
141                 ? Interpolators.TOUCH_RESPONSE
142                 : Interpolators.FAST_OUT_SLOW_IN);
143         mAnimator.addListener(new AnimatorListenerAdapter() {
144             @Override
145             public void onAnimationEnd(Animator animation) {
146                 mAnimator = null;
147             }
148         });
149         mAnimator.start();
150     }
151 
152     @Override
onDraw(Canvas canvas)153     protected void onDraw(Canvas canvas) {
154         int left = getWidth() / 2 - mCurrentWidth / 2;
155         int top = getHeight() / 2 - mCurrentHeight / 2;
156         int radius = Math.min(mCurrentWidth, mCurrentHeight) / 2;
157         canvas.drawRoundRect(left, top, left + mCurrentWidth, top + mCurrentHeight,
158                 radius, radius, mPaint);
159     }
160 
161     @Override
hasOverlappingRendering()162     public boolean hasOverlappingRendering() {
163         return false;
164     }
165 }
166