1 /* 2 * Copyright (C) 2018 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 android.content.Context; 19 import android.graphics.Canvas; 20 import android.support.animation.DynamicAnimation; 21 import android.support.animation.FloatPropertyCompat; 22 import android.support.animation.SpringAnimation; 23 import android.support.animation.SpringForce; 24 import android.support.annotation.NonNull; 25 import android.support.v7.widget.RecyclerView; 26 import android.support.v7.widget.RecyclerView.EdgeEffectFactory; 27 import android.util.AttributeSet; 28 import android.util.SparseBooleanArray; 29 import android.view.View; 30 import android.widget.EdgeEffect; 31 import android.widget.RelativeLayout; 32 33 import static android.support.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY; 34 import static android.support.animation.SpringForce.STIFFNESS_LOW; 35 import static android.support.animation.SpringForce.STIFFNESS_MEDIUM; 36 37 public class SpringRelativeLayout extends RelativeLayout { 38 39 private static final float STIFFNESS = (STIFFNESS_MEDIUM + STIFFNESS_LOW) / 2; 40 private static final float DAMPING_RATIO = DAMPING_RATIO_MEDIUM_BOUNCY; 41 private static final float VELOCITY_MULTIPLIER = 0.3f; 42 43 private static final FloatPropertyCompat<SpringRelativeLayout> DAMPED_SCROLL = 44 new FloatPropertyCompat<SpringRelativeLayout>("value") { 45 46 @Override 47 public float getValue(SpringRelativeLayout object) { 48 return object.mDampedScrollShift; 49 } 50 51 @Override 52 public void setValue(SpringRelativeLayout object, float value) { 53 object.setDampedScrollShift(value); 54 } 55 }; 56 57 private final SparseBooleanArray mSpringViews = new SparseBooleanArray(); 58 private final SpringAnimation mSpring; 59 60 private float mDampedScrollShift = 0; 61 private SpringEdgeEffect mActiveEdge; 62 SpringRelativeLayout(Context context)63 public SpringRelativeLayout(Context context) { 64 this(context, null); 65 } 66 SpringRelativeLayout(Context context, AttributeSet attrs)67 public SpringRelativeLayout(Context context, AttributeSet attrs) { 68 this(context, attrs, 0); 69 } 70 SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr)71 public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 72 super(context, attrs, defStyleAttr); 73 mSpring = new SpringAnimation(this, DAMPED_SCROLL, 0); 74 mSpring.setSpring(new SpringForce(0) 75 .setStiffness(STIFFNESS) 76 .setDampingRatio(DAMPING_RATIO)); 77 } 78 addSpringView(int id)79 public void addSpringView(int id) { 80 mSpringViews.put(id, true); 81 } 82 removeSpringView(int id)83 public void removeSpringView(int id) { 84 mSpringViews.delete(id); 85 invalidate(); 86 } 87 88 @Override drawChild(Canvas canvas, View child, long drawingTime)89 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 90 if (mDampedScrollShift != 0 && mSpringViews.get(child.getId())) { 91 canvas.translate(0, mDampedScrollShift); 92 boolean result = super.drawChild(canvas, child, drawingTime); 93 canvas.translate(0, -mDampedScrollShift); 94 return result; 95 } 96 return super.drawChild(canvas, child, drawingTime); 97 } 98 setActiveEdge(SpringEdgeEffect edge)99 private void setActiveEdge(SpringEdgeEffect edge) { 100 if (mActiveEdge != edge && mActiveEdge != null) { 101 mActiveEdge.mDistance = 0; 102 } 103 mActiveEdge = edge; 104 } 105 setDampedScrollShift(float shift)106 protected void setDampedScrollShift(float shift) { 107 if (shift != mDampedScrollShift) { 108 mDampedScrollShift = shift; 109 invalidate(); 110 } 111 } 112 finishScrollWithVelocity(float velocity)113 private void finishScrollWithVelocity(float velocity) { 114 mSpring.setStartVelocity(velocity); 115 mSpring.setStartValue(mDampedScrollShift); 116 mSpring.start(); 117 } 118 finishWithShiftAndVelocity(float shift, float velocity, DynamicAnimation.OnAnimationEndListener listener)119 protected void finishWithShiftAndVelocity(float shift, float velocity, 120 DynamicAnimation.OnAnimationEndListener listener) { 121 setDampedScrollShift(shift); 122 mSpring.addEndListener(listener); 123 finishScrollWithVelocity(velocity); 124 } 125 createEdgeEffectFactory()126 public EdgeEffectFactory createEdgeEffectFactory() { 127 return new SpringEdgeEffectFactory(); 128 } 129 130 private class SpringEdgeEffectFactory extends EdgeEffectFactory { 131 132 @NonNull @Override createEdgeEffect(RecyclerView view, int direction)133 protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) { 134 switch (direction) { 135 case DIRECTION_TOP: 136 return new SpringEdgeEffect(getContext(), +VELOCITY_MULTIPLIER); 137 case DIRECTION_BOTTOM: 138 return new SpringEdgeEffect(getContext(), -VELOCITY_MULTIPLIER); 139 } 140 return super.createEdgeEffect(view, direction); 141 } 142 } 143 144 private class SpringEdgeEffect extends EdgeEffect { 145 146 private final float mVelocityMultiplier; 147 148 private float mDistance; 149 SpringEdgeEffect(Context context, float velocityMultiplier)150 public SpringEdgeEffect(Context context, float velocityMultiplier) { 151 super(context); 152 mVelocityMultiplier = velocityMultiplier; 153 } 154 155 @Override draw(Canvas canvas)156 public boolean draw(Canvas canvas) { 157 return false; 158 } 159 160 @Override onAbsorb(int velocity)161 public void onAbsorb(int velocity) { 162 finishScrollWithVelocity(velocity * mVelocityMultiplier); 163 } 164 165 @Override onPull(float deltaDistance, float displacement)166 public void onPull(float deltaDistance, float displacement) { 167 setActiveEdge(this); 168 mDistance += deltaDistance * (mVelocityMultiplier / 3f); 169 setDampedScrollShift(mDistance * getHeight()); 170 } 171 172 @Override onRelease()173 public void onRelease() { 174 mDistance = 0; 175 finishScrollWithVelocity(0); 176 } 177 } 178 }