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 int mWidth;
72     private int mHeight;
73     private int mTouchingWidth;
74     private int mTouchingHeight;
75     private int mCurrentWidth;
76     private int mCurrentHeight;
77     private AnimatorSet mAnimator;
78     private boolean mTouching;
79     private boolean mHovering;
80     private int mHoveringWidth;
81     private int mHoveringHeight;
82     private boolean mIsLeftRightSplit;
83 
DividerHandleView(Context context, @Nullable AttributeSet attrs)84     public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
85         super(context, attrs);
86         mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
87         mPaint.setAntiAlias(true);
88         updateDimens();
89     }
90 
updateDimens()91     private void updateDimens() {
92         mWidth = getResources().getDimensionPixelSize(mIsLeftRightSplit
93                 ? R.dimen.split_divider_handle_height
94                 : R.dimen.split_divider_handle_width);
95         mHeight = getResources().getDimensionPixelSize(mIsLeftRightSplit
96                 ? R.dimen.split_divider_handle_width
97                 : R.dimen.split_divider_handle_height);
98         mCurrentWidth = mWidth;
99         mCurrentHeight = mHeight;
100         mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
101         mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
102         mHoveringWidth = mWidth > mHeight ? ((int) (mWidth * 1.5f)) : mWidth;
103         mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
104     }
105 
setIsLeftRightSplit(boolean isLeftRightSplit)106     void setIsLeftRightSplit(boolean isLeftRightSplit) {
107         mIsLeftRightSplit = isLeftRightSplit;
108         updateDimens();
109     }
110 
111     /** Sets touching state for this handle view. */
setTouching(boolean touching, boolean animate)112     public void setTouching(boolean touching, boolean animate) {
113         if (touching == mTouching) {
114             return;
115         }
116         setInputState(touching, animate, mTouchingWidth, mTouchingHeight);
117         mTouching = touching;
118     }
119 
120     /** Sets hovering state for this handle view. */
setHovering(boolean hovering, boolean animate)121     public void setHovering(boolean hovering, boolean animate) {
122         if (hovering == mHovering) {
123             return;
124         }
125         setInputState(hovering, animate, mHoveringWidth, mHoveringHeight);
126         mHovering = hovering;
127     }
128 
setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight)129     private void setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight) {
130         if (mAnimator != null) {
131             mAnimator.cancel();
132             mAnimator = null;
133         }
134         if (!animate) {
135             mCurrentWidth = stateOn ? stateWidth : mWidth;
136             mCurrentHeight = stateOn ? stateHeight : mHeight;
137             invalidate();
138         } else {
139             animateToTarget(stateOn ? stateWidth : mWidth,
140                     stateOn ? stateHeight : mHeight, stateOn);
141         }
142     }
143 
animateToTarget(int targetWidth, int targetHeight, boolean touching)144     private void animateToTarget(int targetWidth, int targetHeight, boolean touching) {
145         ObjectAnimator widthAnimator = ObjectAnimator.ofInt(this, WIDTH_PROPERTY,
146                 mCurrentWidth, targetWidth);
147         ObjectAnimator heightAnimator = ObjectAnimator.ofInt(this, HEIGHT_PROPERTY,
148                 mCurrentHeight, targetHeight);
149         mAnimator = new AnimatorSet();
150         mAnimator.playTogether(widthAnimator, heightAnimator);
151         mAnimator.setDuration(touching
152                 ? TOUCH_ANIMATION_DURATION
153                 : TOUCH_RELEASE_ANIMATION_DURATION);
154         mAnimator.setInterpolator(touching
155                 ? Interpolators.TOUCH_RESPONSE
156                 : Interpolators.FAST_OUT_SLOW_IN);
157         mAnimator.addListener(new AnimatorListenerAdapter() {
158             @Override
159             public void onAnimationEnd(Animator animation) {
160                 mAnimator = null;
161             }
162         });
163         mAnimator.start();
164     }
165 
166     @Override
onDraw(Canvas canvas)167     protected void onDraw(Canvas canvas) {
168         int left = getWidth() / 2 - mCurrentWidth / 2;
169         int top = getHeight() / 2 - mCurrentHeight / 2;
170         int radius = Math.min(mCurrentWidth, mCurrentHeight) / 2;
171         canvas.drawRoundRect(left, top, left + mCurrentWidth, top + mCurrentHeight,
172                 radius, radius, mPaint);
173     }
174 
175     @Override
hasOverlappingRendering()176     public boolean hasOverlappingRendering() {
177         return false;
178     }
179 }
180