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 android.view.ViewGroup.LayoutParams.MATCH_PARENT; 19 20 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.ObjectAnimator; 25 import android.animation.PropertyValuesHolder; 26 import android.content.Context; 27 import android.util.AttributeSet; 28 import android.util.Property; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.animation.Interpolator; 32 33 import com.android.launcher3.AbstractFloatingView; 34 import com.android.launcher3.Launcher; 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.anim.Interpolators; 37 import com.android.launcher3.touch.BaseSwipeDetector; 38 import com.android.launcher3.touch.SingleAxisSwipeDetector; 39 40 /** 41 * Extension of AbstractFloatingView with common methods for sliding in from bottom 42 */ 43 public abstract class AbstractSlideInView extends AbstractFloatingView 44 implements SingleAxisSwipeDetector.Listener { 45 46 protected static final Property<AbstractSlideInView, Float> TRANSLATION_SHIFT = 47 new Property<AbstractSlideInView, Float>(Float.class, "translationShift") { 48 49 @Override 50 public Float get(AbstractSlideInView view) { 51 return view.mTranslationShift; 52 } 53 54 @Override 55 public void set(AbstractSlideInView view, Float value) { 56 view.setTranslationShift(value); 57 } 58 }; 59 protected static final float TRANSLATION_SHIFT_CLOSED = 1f; 60 protected static final float TRANSLATION_SHIFT_OPENED = 0f; 61 62 protected final Launcher mLauncher; 63 protected final SingleAxisSwipeDetector mSwipeDetector; 64 protected final ObjectAnimator mOpenCloseAnimator; 65 66 protected View mContent; 67 private final View mColorScrim; 68 protected Interpolator mScrollInterpolator; 69 70 // range [0, 1], 0=> completely open, 1=> completely closed 71 protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED; 72 73 protected boolean mNoIntercept; 74 AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr)75 public AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr) { 76 super(context, attrs, defStyleAttr); 77 mLauncher = Launcher.getLauncher(context); 78 79 mScrollInterpolator = Interpolators.SCROLL_CUBIC; 80 mSwipeDetector = new SingleAxisSwipeDetector(context, this, 81 SingleAxisSwipeDetector.VERTICAL); 82 83 mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this); 84 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { 85 @Override 86 public void onAnimationEnd(Animator animation) { 87 mSwipeDetector.finishedScrolling(); 88 announceAccessibilityChanges(); 89 } 90 }); 91 int scrimColor = getScrimColor(mLauncher); 92 mColorScrim = scrimColor != -1 ? createColorScrim(mLauncher, scrimColor) : null; 93 } 94 attachToContainer()95 protected void attachToContainer() { 96 if (mColorScrim != null) { 97 getPopupContainer().addView(mColorScrim); 98 } 99 getPopupContainer().addView(this); 100 } 101 102 /** 103 * Returns a scrim color for a sliding view. if returned value is -1, no scrim is added. 104 */ getScrimColor(Context context)105 protected int getScrimColor(Context context) { 106 return -1; 107 } 108 setTranslationShift(float translationShift)109 protected void setTranslationShift(float translationShift) { 110 mTranslationShift = translationShift; 111 mContent.setTranslationY(mTranslationShift * mContent.getHeight()); 112 if (mColorScrim != null) { 113 mColorScrim.setAlpha(1 - mTranslationShift); 114 } 115 } 116 117 @Override onControllerInterceptTouchEvent(MotionEvent ev)118 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 119 if (mNoIntercept) { 120 return false; 121 } 122 123 int directionsToDetectScroll = mSwipeDetector.isIdleState() ? 124 SingleAxisSwipeDetector.DIRECTION_NEGATIVE : 0; 125 mSwipeDetector.setDetectableScrollConditions( 126 directionsToDetectScroll, false); 127 mSwipeDetector.onTouchEvent(ev); 128 return mSwipeDetector.isDraggingOrSettling() 129 || !getPopupContainer().isEventOverView(mContent, ev); 130 } 131 132 @Override onControllerTouchEvent(MotionEvent ev)133 public boolean onControllerTouchEvent(MotionEvent ev) { 134 mSwipeDetector.onTouchEvent(ev); 135 if (ev.getAction() == MotionEvent.ACTION_UP && mSwipeDetector.isIdleState() 136 && !isOpeningAnimationRunning()) { 137 // If we got ACTION_UP without ever starting swipe, close the panel. 138 if (!getPopupContainer().isEventOverView(mContent, ev)) { 139 close(true); 140 } 141 } 142 return true; 143 } 144 isOpeningAnimationRunning()145 private boolean isOpeningAnimationRunning() { 146 return mIsOpen && mOpenCloseAnimator.isRunning(); 147 } 148 149 /* SingleAxisSwipeDetector.Listener */ 150 151 @Override onDragStart(boolean start, float startDisplacement)152 public void onDragStart(boolean start, float startDisplacement) { } 153 154 @Override onDrag(float displacement)155 public boolean onDrag(float displacement) { 156 float range = mContent.getHeight(); 157 displacement = Utilities.boundToRange(displacement, 0, range); 158 setTranslationShift(displacement / range); 159 return true; 160 } 161 162 @Override onDragEnd(float velocity)163 public void onDragEnd(float velocity) { 164 if ((mSwipeDetector.isFling(velocity) && velocity > 0) || mTranslationShift > 0.5f) { 165 mScrollInterpolator = scrollInterpolatorForVelocity(velocity); 166 mOpenCloseAnimator.setDuration(BaseSwipeDetector.calculateDuration( 167 velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift)); 168 close(true); 169 } else { 170 mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat( 171 TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); 172 mOpenCloseAnimator.setDuration( 173 BaseSwipeDetector.calculateDuration(velocity, mTranslationShift)) 174 .setInterpolator(Interpolators.DEACCEL); 175 mOpenCloseAnimator.start(); 176 } 177 } 178 handleClose(boolean animate, long defaultDuration)179 protected void handleClose(boolean animate, long defaultDuration) { 180 if (!mIsOpen) { 181 return; 182 } 183 if (!animate) { 184 mOpenCloseAnimator.cancel(); 185 setTranslationShift(TRANSLATION_SHIFT_CLOSED); 186 onCloseComplete(); 187 return; 188 } 189 mOpenCloseAnimator.setValues( 190 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED)); 191 mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { 192 @Override 193 public void onAnimationEnd(Animator animation) { 194 onCloseComplete(); 195 } 196 }); 197 if (mSwipeDetector.isIdleState()) { 198 mOpenCloseAnimator 199 .setDuration(defaultDuration) 200 .setInterpolator(Interpolators.ACCEL); 201 } else { 202 mOpenCloseAnimator.setInterpolator(mScrollInterpolator); 203 } 204 mOpenCloseAnimator.start(); 205 } 206 onCloseComplete()207 protected void onCloseComplete() { 208 mIsOpen = false; 209 getPopupContainer().removeView(this); 210 if (mColorScrim != null) { 211 getPopupContainer().removeView(mColorScrim); 212 } 213 } 214 getPopupContainer()215 protected BaseDragLayer getPopupContainer() { 216 return mLauncher.getDragLayer(); 217 } 218 219 createColorScrim(Context context, int bgColor)220 protected static View createColorScrim(Context context, int bgColor) { 221 View view = new View(context); 222 view.forceHasOverlappingRendering(false); 223 view.setBackgroundColor(bgColor); 224 225 BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(MATCH_PARENT, MATCH_PARENT); 226 lp.ignoreInsets = true; 227 view.setLayoutParams(lp); 228 229 return view; 230 } 231 } 232