1 /* 2 * Copyright (C) 2017 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 package com.android.launcher3.views; 17 18 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; 19 20 import android.animation.Animator; 21 import android.animation.AnimatorListenerAdapter; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.content.Context; 25 import android.util.AttributeSet; 26 import android.util.Property; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.animation.Interpolator; 30 31 import com.android.launcher3.AbstractFloatingView; 32 import com.android.launcher3.Launcher; 33 import com.android.launcher3.LauncherAnimUtils; 34 import com.android.launcher3.Utilities; 35 import com.android.launcher3.anim.Interpolators; 36 import com.android.launcher3.touch.SwipeDetector; 37 38 /** 39 * Extension of AbstractFloatingView with common methods for sliding in from bottom 40 */ 41 public abstract class AbstractSlideInView extends AbstractFloatingView 42 implements SwipeDetector.Listener { 43 44 protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT = 45 new Property<AbstractSlideInView, Float>(Float.class, "translationShift") { 46 47 @Override 48 public Float get(AbstractSlideInView view) { 49 return view.mTranslationShift; 50 } 51 52 @Override 53 public void set(AbstractSlideInView view, Float value) { 54 view.setTranslationShift(value); 55 } 56 }; 57 protected static final float TRANSLATION_SHIFT_CLOSED = 1f; 58 protected static final float TRANSLATION_SHIFT_OPENED = 0f; 59 60 protected final Launcher mLauncher; 61 protected final SwipeDetector mSwipeDetector; 62 protected final ObjectAnimator mOpenCloseAnimator; 63 64 protected View mContent; 65 protected Interpolator mScrollInterpolator; 66 67 // range [0, 1], 0=> completely open, 1=> completely closed 68 protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED; 69 70 protected boolean mNoIntercept; 71 72 public AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr) { 73 super(context, attrs, defStyleAttr); 74 mLauncher = Launcher.getLauncher(context); 75 76 mScrollInterpolator = Interpolators.SCROLL_CUBIC; 77 mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL); 78 79 mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this); 80 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { 81 @Override 82 public void onAnimationEnd(Animator animation) { 83 mSwipeDetector.finishedScrolling(); 84 announceAccessibilityChanges(); 85 } 86 }); 87 } 88 89 protected void setTranslationShift(float translationShift) { 90 mTranslationShift = translationShift; 91 mContent.setTranslationY(mTranslationShift * mContent.getHeight()); 92 } 93 94 @Override 95 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 96 if (mNoIntercept) { 97 return false; 98 } 99 100 int directionsToDetectScroll = mSwipeDetector.isIdleState() ? 101 SwipeDetector.DIRECTION_NEGATIVE : 0; 102 mSwipeDetector.setDetectableScrollConditions( 103 directionsToDetectScroll, false); 104 mSwipeDetector.onTouchEvent(ev); 105 return mSwipeDetector.isDraggingOrSettling() 106 || !mLauncher.getDragLayer().isEventOverView(mContent, ev); 107 } 108 109 @Override 110 public boolean onControllerTouchEvent(MotionEvent ev) { 111 mSwipeDetector.onTouchEvent(ev); 112 if (ev.getAction() == MotionEvent.ACTION_UP && mSwipeDetector.isIdleState()) { 113 // If we got ACTION_UP without ever starting swipe, close the panel. 114 if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) { 115 close(true); 116 } 117 } 118 return true; 119 } 120 121 /* SwipeDetector.Listener */ 122 123 @Override 124 public void onDragStart(boolean start) { } 125 126 @Override 127 public boolean onDrag(float displacement, float velocity) { 128 float range = mContent.getHeight(); 129 displacement = Utilities.boundToRange(displacement, 0, range); 130 setTranslationShift(displacement / range); 131 return true; 132 } 133 134 @Override 135 public void onDragEnd(float velocity, boolean fling) { 136 if ((fling && velocity > 0) || mTranslationShift > 0.5f) { 137 mScrollInterpolator = scrollInterpolatorForVelocity(velocity); 138 mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration( 139 velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift)); 140 close(true); 141 } else { 142 mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat( 143 TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); 144 mOpenCloseAnimator.setDuration( 145 SwipeDetector.calculateDuration(velocity, mTranslationShift)) 146 .setInterpolator(Interpolators.DEACCEL); 147 mOpenCloseAnimator.start(); 148 } 149 } 150 151 protected void handleClose(boolean animate, long defaultDuration) { 152 if (mIsOpen && !animate) { 153 mOpenCloseAnimator.cancel(); 154 setTranslationShift(TRANSLATION_SHIFT_CLOSED); 155 onCloseComplete(); 156 return; 157 } 158 if (!mIsOpen || mOpenCloseAnimator.isRunning()) { 159 return; 160 } 161 mOpenCloseAnimator.setValues( 162 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED)); 163 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { 164 @Override 165 public void onAnimationEnd(Animator animation) { 166 onCloseComplete(); 167 } 168 }); 169 if (mSwipeDetector.isIdleState()) { 170 mOpenCloseAnimator 171 .setDuration(defaultDuration) 172 .setInterpolator(Interpolators.ACCEL); 173 } else { 174 mOpenCloseAnimator.setInterpolator(mScrollInterpolator); 175 } 176 mOpenCloseAnimator.start(); 177 } 178 179 protected void onCloseComplete() { 180 mIsOpen = false; 181 mLauncher.getDragLayer().removeView(this); 182 } 183 } 184