• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }