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