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